refactor: reorganize GitOps control plane

This commit is contained in:
donghyeon-ka
2026-07-25 23:55:31 +09:00
parent d507ac6ee9
commit 293ee6fc97
191 changed files with 7046 additions and 9034 deletions
+209
View File
@@ -0,0 +1,209 @@
# Bootstrap an empty dev-k3s cluster
이 runbook은 폐기 가능한 개발 클러스터만 대상으로 합니다. production에
사용하지 않습니다.
## 1. Preflight
```bash
kubectl config current-context
kubectl cluster-info
make validate
```
의도한 dev cluster가 아니면 중단합니다. 내부 Gitea가 private이면 Argo CD가
root repository를 읽을 수 있는 read-only credential을 외부 secret
authority에서 먼저 provision해야 합니다. credential은 이 저장소에
commit하지 않습니다.
remote state backend 파일을 준비합니다.
```bash
mkdir -p .local/terraform-backend/dev-k3s
cp iac/terraform/backend/dev-k3s/vault-core.s3.hcl.example \
.local/terraform-backend/dev-k3s/vault-core.s3.hcl
cp iac/terraform/backend/dev-k3s/vault-database.s3.hcl.example \
.local/terraform-backend/dev-k3s/vault-database.s3.hcl
```
실제 bucket, endpoint와 workload identity를 설정합니다. backend credential은
파일에 넣지 않습니다.
## 2. Argo CD와 root Application
```bash
make bootstrap KUBE_CONTEXT="$(kubectl config current-context)"
kubectl -n argocd get application project-gitops-dev-k3s
```
이 명령이 수행하는 직접 cluster mutation은 Argo CD 설치와 root seed뿐입니다.
Child Application은 root가 생성합니다.
## 3. Dev Vault 초기화
Vault Pod가 생성될 때까지 기다린 뒤 operator workstation에서 forward합니다.
이 port-forward는 최초 dev bootstrap용이며 routine runner 모델이 아닙니다.
```bash
kubectl -n vault wait --for=create pod -l app=vault --timeout=300s
kubectl -n vault port-forward deployment/vault 8200:8200
```
별도 terminal:
```bash
export VAULT_ADDR=http://127.0.0.1:8200
./hack/vault-init.sh init
```
`.local/vault/dev-k3s-init.json`을 즉시 encrypted custody로 복사합니다.
dev-only 1-of-1 unseal key와 initial root token이 있으므로 일반 backup과
분리합니다.
## 4. Vault core
초기 root token을 shell history에 직접 적지 않습니다.
```bash
export TF_VAR_vault_addr="$VAULT_ADDR"
export TF_VAR_vault_token="$(
jq -r '.root_token' .local/vault/dev-k3s-init.json
)"
make terraform-plan \
TF_ROOT=vault-core \
BACKEND_CONFIG=.local/terraform-backend/dev-k3s/vault-core.s3.hcl
make terraform-apply \
TF_ROOT=vault-core \
BACKEND_CONFIG=.local/terraform-backend/dev-k3s/vault-core.s3.hcl \
APPROVE_APPLY=dev-k3s/vault-core
```
plan에서 mount, auth backend, policy, role, JWT Transit key 이외 객체가
나오면 apply하지 않습니다.
## 5. Runtime secret seed
Secret 값은 Git/Terraform을 통과하지 않습니다. 아래 변수는 terminal
session에만 유지합니다.
```bash
read -r -s -p "PostgreSQL superuser password: " POSTGRES_SUPERUSER_PASSWORD
echo
read -r -s -p "Auth database password: " AUTH_DB_PASSWORD
echo
read -r -s -p "Keycloak database password: " KEYCLOAK_DB_PASSWORD
echo
read -r -s -p "Keycloak bootstrap admin password: " KEYCLOAK_ADMIN_PASSWORD
echo
read -r -s -p "Auth-server Keycloak client secret: " KEYCLOAK_CLIENT_SECRET
echo
secret_file="$(mktemp)"
trap 'rm -f "$secret_file"' EXIT
chmod 0600 "$secret_file"
jq -n --arg password "$POSTGRES_SUPERUSER_PASSWORD" \
'{POSTGRES_SUPERUSER_PASSWORD: $password}' >"$secret_file"
vault kv put kv/dev/platform/postgres/superuser @"$secret_file"
jq -n \
--arg password "$AUTH_DB_PASSWORD" \
'{AUTH_DB_PASSWORD: $password, APP_DATASOURCE_USERNAME: "project_auth", APP_DATASOURCE_PASSWORD: $password}' >"$secret_file"
vault kv put kv/dev/platform/postgres/auth-server @"$secret_file"
jq -n --arg password "$KEYCLOAK_DB_PASSWORD" \
'{KEYCLOAK_DB_PASSWORD: $password}' >"$secret_file"
vault kv put kv/dev/platform/postgres/keycloak @"$secret_file"
jq -n --arg password "$KEYCLOAK_ADMIN_PASSWORD" \
'{KC_BOOTSTRAP_ADMIN_PASSWORD: $password}' >"$secret_file"
vault kv put kv/dev/platform/keycloak/bootstrap-admin @"$secret_file"
jq -n --arg secret "$KEYCLOAK_CLIENT_SECRET" \
'{KEYCLOAK_CLIENT_SECRET: $secret, APP_SECURITY_OAUTH2_KEYCLOAK_CLIENT_SECRET: $secret}' >"$secret_file"
vault kv put kv/dev/platform/keycloak/client-auth-server @"$secret_file"
rm -f "$secret_file"
trap - EXIT
```
PostgreSQL이 Vault Agent 주입 후 시작하는지 확인합니다.
```bash
kubectl -n platform rollout status statefulset/postgres --timeout=600s
```
## 6. Vault database state
초기 root token으로 TTL이 짧은 database 전용 token을 발급합니다.
```bash
export TF_VAR_vault_token="$(
vault token create \
-policy=vault-database-automation-dev \
-ttl=30m \
-format=json |
jq -r '.auth.client_token'
)"
export TF_VAR_postgres_admin_password="$POSTGRES_SUPERUSER_PASSWORD"
export TF_VAR_postgres_admin_password_version=1
make terraform-plan \
TF_ROOT=vault-database \
BACKEND_CONFIG=.local/terraform-backend/dev-k3s/vault-database.s3.hcl
make terraform-apply \
TF_ROOT=vault-database \
BACKEND_CONFIG=.local/terraform-backend/dev-k3s/vault-database.s3.hcl \
APPROVE_APPLY=dev-k3s/vault-database
```
## 7. Root token 폐기
Kubernetes auth operator login이 동작하는지 먼저 검증합니다.
```bash
operator_jwt="$(
kubectl -n vault create token vault-operator \
--audience=vault \
--duration=10m
)"
operator_token="$(
VAULT_TOKEN= vault write \
-format=json \
auth/kubernetes/login \
role=vault-operator-dev \
jwt="$operator_jwt" |
jq -r '.auth.client_token'
)"
VAULT_TOKEN="$operator_token" vault token lookup >/dev/null
```
검증 후 initial root token을 폐기합니다.
```bash
./hack/vault-init.sh revoke-root
unset operator_jwt operator_token
unset TF_VAR_vault_token TF_VAR_postgres_admin_password
unset POSTGRES_SUPERUSER_PASSWORD AUTH_DB_PASSWORD KEYCLOAK_DB_PASSWORD
unset KEYCLOAK_ADMIN_PASSWORD KEYCLOAK_CLIENT_SECRET
```
encrypted custody로 옮긴 init material의 local working copy는 조직의
dev recovery 정책에 따라 제거합니다.
## 8. 확인
```bash
kubectl -n argocd get applications
kubectl -n vault get pods
kubectl -n platform get pods
kubectl -n auth-dev get pods
kubectl -n api-dev get pods
```
모든 Application의 sync/health를 확인하고 DB migration 및 Keycloak client
sync hook 결과를 검토합니다. 실패한 hook을 고치기 위해 child manifest를
직접 apply하지 말고 Git PR을 사용합니다.
+46
View File
@@ -0,0 +1,46 @@
# Sealed Secrets backup and recovery
Existing SealedSecrets are decryptable only with a controller private key.
Back up every key after first install and after rotation.
## Backup
Use an encrypted operator workstation:
```bash
umask 077
kubectl -n kube-system get secret \
-l sealedsecrets.bitnami.com/sealed-secrets-key \
-o json > .local/sealed-secrets-keys.json
chmod 0600 .local/sealed-secrets-keys.json
```
Encrypt the file with the organization's recovery mechanism, store at least
two independently controlled copies, and delete the plaintext working copy.
Record cluster, date and checksum without recording private key data.
## Restore
Restore keys before application SealedSecrets sync:
```bash
kubectl -n kube-system apply -f .local/sealed-secrets-keys.json
kubectl -n kube-system rollout restart deployment/sealed-secrets-controller
kubectl -n kube-system rollout status deployment/sealed-secrets-controller
```
Verify both `auth-dev/ghcr-regcred` and `api-dev/ghcr-regcred` are created.
If no key backup exists, old ciphertext cannot be recovered. Create a new
controller key and reseal every Secret from the original credential source.
## Rotation
The chart requests periodic key renewal. Old keys must remain until all
ciphertext has been resealed and verified. GHCR credential rotation requires:
1. issue an organization-owned read-only package credential;
2. create namespace-scoped SealedSecrets with the current controller cert;
3. merge and verify image pulls;
4. revoke the previous credential;
5. update the encrypted key backup.
@@ -0,0 +1,88 @@
# Terraform v2 state migration
새 클러스터에는 이 runbook이 필요하지 않습니다. legacy local state 또는
이전 `provider-foundation`, `workload-foundation`, `workload-config`,
`database-config` remote state가 실제로 존재할 때만 수행합니다.
State 이동은 live object 삭제보다 위험할 수 있습니다. maintenance window와
독립 backup 없이 진행하지 않습니다.
## 목표
| 이전 state | 목표 |
|---|---|
| provider Transit Vault state | archive 후 provider Vault 폐기 절차에서 별도 처리 |
| workload foundation | `vault-core`의 기준 state |
| workload config | 소유 객체를 `vault-core`로 이동 |
| database config | `vault-database`로 backend key migration |
동일 클러스터 Transit Vault는 더 이상 desired state가 아닙니다. Terraform
state에서 먼저 삭제하거나 destroy하지 않습니다. snapshot과 seal dependency
해제 확인 후 별도 decommission 승인을 받아 처리합니다.
## 1. Inventory와 backup
모든 operator machine, runner, remote backend에서 state 위치를 확인합니다.
```bash
find . -type f \
\( -name 'terraform.tfstate*' -o -name '*.tfplan' \) \
-not -path './.git/*'
```
각 state를 `terraform state pull`로 encrypted offline custody에 저장하고
checksum을 기록합니다. backup에는 secret data가 포함될 수 있습니다.
## 2. Backend key migration
`workload-foundation` backend에 연결한 상태에서 새 `vault-core` backend
configuration으로 `terraform init -migrate-state`를 수행합니다.
`database-config`도 같은 방식으로 `vault-database` key로 이동합니다.
실제 backend 파일과 이전 key는 환경마다 다르므로 명령에 값을 하드코딩하지
않습니다. migration 전후 `terraform state pull` checksum과 `state list`
비교합니다.
## 3. Workload configuration ownership 이동
이전 `workload-config` state의 다음 객체를 `vault-core` state의 선언된
address로 이동합니다.
- application Vault policies
- workload Kubernetes auth roles
`terraform state mv -state=<source-backup> -state-out=<target-working-copy>`
사용해 offline copy에서 먼저 연습합니다. target address는 현재
`module.workload_policies``module.workload_roles``terraform state list`
결과를 기준으로 합니다. resource 이름을 추측하지 않습니다.
이동 후 두 state 모두 plan합니다.
- `vault-core`: 변경 없음 또는 address-only 이동
- legacy workload-config: 삭제할 live object 없음
두 plan 중 하나라도 destroy를 제안하면 중단하고 backup state를 복원합니다.
## 4. Database state
기존 database state가 없고 live Vault 객체만 존재할 때만 다음 import ID를
사용합니다.
```text
vault_database_secret_backend_connection.platform_postgres database/config/platform-postgres-dev
vault_database_secret_backend_role.auth_db_migration database/roles/auth-db-migration-dev
vault_database_secret_backend_role.postgres_operator database/roles/postgres-operator-dev
```
이미 다른 state에 address가 있으면 import하지 말고 state ownership을 먼저
이동합니다.
## 5. Cutover 완료 조건
- 두 목표 state가 remote backend와 locking을 사용
- 동일 Vault path가 두 state list에 나타나지 않음
- plan에 예상하지 않은 create/delete가 없음
- legacy state와 backup은 immutable archive
- repo와 runner에 local state/provider directory가 없음
검증이 끝나기 전 legacy backend를 삭제하지 않습니다.
+44
View File
@@ -0,0 +1,44 @@
# Vault backup and recovery
`dev-k3s` Vault는 single-node integrated Raft입니다. upgrade, Terraform
state migration, storage 작업 전에 snapshot을 생성합니다.
## Snapshot
short-lived authorized token과 Vault API 연결을 준비합니다.
```bash
umask 077
vault operator raft snapshot save .local/vault/dev-k3s.snap
sha256sum .local/vault/dev-k3s.snap
chmod 0600 .local/vault/dev-k3s.snap
```
즉시 암호화해 init/unseal material과 다른 위치에 보관합니다. snapshot에는
secret payload가 포함됩니다.
## Recovery material
- Raft snapshot
- unseal key
- 현재 manifest revision
- Terraform remote state backup
- Sealed Secrets controller key
Initial root token은 recovery material이 아닙니다. bootstrap 완료 후
폐기해야 합니다.
## Restore exercise
분기마다 isolated disposable cluster에서 다음을 검증합니다.
1. 동일 Vault version과 storage 설정 배포
2. Vault init 후 snapshot restore
3. 복구 key로 unseal
4. Kubernetes auth 재연결 확인
5. KV read, JWT signing, dynamic database lease issue/revoke 확인
6. recovery time과 누락된 의존성 기록
현재 dev 구성은 production recovery 설계가 아닙니다. production은
independent HA Vault, TLS, auto-unseal과 더 엄격한 backup custody가
필요합니다.