refactor(gitops): establish platform ownership boundaries

This commit is contained in:
donghyeon-ka
2026-07-26 01:34:29 +09:00
parent 293ee6fc97
commit a6f6c663e0
121 changed files with 2801 additions and 1204 deletions
+203 -71
View File
@@ -1,7 +1,11 @@
# Bootstrap an empty dev-k3s cluster
이 runbook은 폐기 가능한 개발 클러스터만 대상으로 합니다. production
사용하지 않습니다.
이 runbook은 폐기 가능한 개발 클러스터만 대상으로 합니다. Production
기존 live cluster migration에는 사용하지 않습니다.
> 2026-07-26 repository 리팩터링 중에는 아래 절차를 실행하지 않았습니다.
> 이 문서는 승인된 future bootstrap 절차이며 명령 예시는 자동 실행 대상이
> 아닙니다.
## 1. Preflight
@@ -11,38 +15,59 @@ kubectl cluster-info
make validate
```
의도한 dev cluster가 아니면 중단합니다. 내부 Gitea가 private이면 Argo CD가
root repository를 읽을 수 있는 read-only credential을 외부 secret
authority에서 먼저 provision해야 합니다. credential은 이 저장소에
의도한 dev cluster가 아니면 중단합니다. 내부 Gitea가 private이면 Argo
CD가 root repository를 읽을 수 있는 read-only credential을 외부 secret
authority에서 먼저 provision해야 합니다. Credential은 이 저장소에
commit하지 않습니다.
remote state backend 파일을 준비합니다.
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-foundation.s3.hcl.example \
.local/terraform-backend/dev-k3s/vault-foundation.s3.hcl
cp iac/terraform/backend/dev-k3s/vault-workloads.s3.hcl.example \
.local/terraform-backend/dev-k3s/vault-workloads.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은
파일에 넣지 않습니다.
실제 bucket, endpoint와 workload identity를 설정합니다. Backend
credential은 파일에 넣지 않습니다. 세 backend key가 서로 다르고 locking이
활성화됐는지 확인합니다.
## 2. Argo CD와 root Application
```bash
make bootstrap KUBE_CONTEXT="$(kubectl config current-context)"
kubectl -n argocd get application project-gitops-dev-k3s
kubectl -n argocd get appproject gitops-control-plane
kubectl -n argocd get application project-gitops-control-plane
kubectl -n argocd get applicationsets
```
이 명령이 수행하는 직접 cluster mutation은 Argo CD 설치와 root seed뿐입니다.
Child Application은 root가 생성합니다.
직접 cluster mutation은 pinned Argo CD 설치, 제한된
`gitops-control-plane` AppProject, root Application seed뿐입니다.
Bootstrap script는 이 순서를 지킵니다. Root가 네 AppProject와 네
ApplicationSet을 만들고, ApplicationSet이 child Application을 생성합니다.
초기 inventory에서 Sealed Secrets와 Vault만 `autoSync: "true"`입니다.
Vault Agent Injector, `auth-system`, `auth-server`, `api-server` gate는
닫힌 상태여야 합니다.
### Sealed Secrets key 준비
Checked-in `ghcr-regcred` ciphertext는 암호화에 사용한 controller private
key로만 복호화할 수 있습니다. 기존 key backup이 있으면 workload gate를
열기 전에 복원하고, 없으면 새 controller certificate와 원본 credential
authority를 사용해 두 SealedSecret을 다시 seal한 PR을 merge합니다.
[Sealed Secrets recovery runbook](sealed-secrets-recovery.md)을 따르며 평문
GHCR credential을 Git이나 log에 남기지 않습니다.
## 3. Dev Vault 초기화
Vault Pod가 생성될 때까지 기다린 뒤 operator workstation에서 forward합니다.
port-forward는 최초 dev bootstrap용이며 routine runner 모델이 아닙니다.
Vault Pod가 생성될 때까지 기다린 뒤 operator workstation에서
port-forward합니다. 이는 최초 dev bootstrap용이며 routine runner 모델이
아닙니다.
```bash
kubectl -n vault wait --for=create pod -l app=vault --timeout=300s
@@ -56,13 +81,14 @@ 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과
분리합니다.
`.local/vault/dev-k3s-init.json`을 즉시 encrypted custody 복사합니다.
Dev-only 1-of-1 unseal key와 initial root token이 있으므로 일반 backup과
분리합니다. Local working copy는 root revoke 때까지 mode `0600`으로
유지하며 shell history나 CI log에 token을 출력하지 않습니다.
## 4. Vault core
## 4. Vault foundation
초기 root token을 shell history에 직접 적지 않습니다.
Foundation은 초기 root token으로 한 번 적용합니다.
```bash
export TF_VAR_vault_addr="$VAULT_ADDR"
@@ -71,24 +97,89 @@ export TF_VAR_vault_token="$(
)"
make terraform-plan \
TF_ROOT=vault-core \
BACKEND_CONFIG=.local/terraform-backend/dev-k3s/vault-core.s3.hcl
TF_ROOT=vault-foundation \
BACKEND_CONFIG=.local/terraform-backend/dev-k3s/vault-foundation.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
TF_ROOT=vault-foundation \
BACKEND_CONFIG=.local/terraform-backend/dev-k3s/vault-foundation.s3.hcl \
APPROVE_APPLY=dev-k3s/vault-foundation
```
plan에 mount, auth backend, policy, role, JWT Transit key 이외 객체가
나오면 apply하지 않습니다.
Plan에 mounts, auth configuration, workloads/database automation
policy와, OIDC/JWT를 명시적으로 구성한 경우에만 분리된 CI JWT role이
있어야 합니다. Workload runtime policy, workload Kubernetes role,
application Transit key, database connection이 보이면 중단합니다.
## 5. Runtime secret seed
CI JWT를 구성할 때 workloads/database exact claim map은 repository와
protected ref를 묶고, 최소 한 공통 job discriminator key에 서로 다른 값을
가져야 합니다. 실제 issuer token payload로 그 claim을 확인하지 못하면
OIDC/JWT 입력을 비워 둡니다.
Secret 값은 Git/Terraform을 통과하지 않습니다. 아래 변수는 terminal
session에만 유지합니다.
CI JWT auth를 구성했다면 Foundation이 만든 두 delegated identity에 실제
로그인해 허용/거부 capability를 확인합니다. 구성하지 않았다면 정책
capability를 검사하고 승인된 관리자가 발급한 bootstrap용 short-lived
token을 사용합니다.
- Workloads identity는 승인된 workload policy/role와
`transit/keys/project-auth-jwt`만 변경할 수 있어야 합니다.
- Database identity는 승인된 `database/config``database/roles` 경로만
변경할 수 있어야 합니다.
- 둘 다 mount, auth backend, 임의 policy, token 발급 경로를 변경할 수
없어야 합니다.
장기 token이나 임의 AppRole을 대신 만들지 않습니다. 실제 Gitea
OIDC/JWT가 검증되지 않았다면 승인된 관리자가 발급한 short-lived bootstrap
token을 사용합니다.
## 5. Vault workload access
`TF_VAR_vault_token`을 workloads 전용 short-lived token으로 교체한 뒤
적용합니다. 다음은 OIDC/JWT를 아직 구성하지 않은 빈 dev lab의 bootstrap
예시입니다. Root token의 child revocation에 묶이지 않도록 짧은 TTL orphan
token을 발급하며, routine automation에서는 사용하지 않습니다.
```bash
export VAULT_WORKLOADS_TOKEN="$(
VAULT_TOKEN="$TF_VAR_vault_token" \
vault token create \
-orphan \
-no-default-policy \
-renewable=false \
-explicit-max-ttl=2h \
-policy=vault-workloads-automation-dev \
-ttl=2h \
-format=json |
jq -r '.auth.client_token'
)"
export TF_VAR_vault_token="$VAULT_WORKLOADS_TOKEN"
make terraform-plan \
TF_ROOT=vault-workloads \
BACKEND_CONFIG=.local/terraform-backend/dev-k3s/vault-workloads.s3.hcl
make terraform-apply \
TF_ROOT=vault-workloads \
BACKEND_CONFIG=.local/terraform-backend/dev-k3s/vault-workloads.s3.hcl \
APPROVE_APPLY=dev-k3s/vault-workloads
```
Plan에는 workload ACL, Kubernetes auth role와 `project-auth-jwt` Transit
key만 있어야 합니다. `auth-system-dev``auth-dev`의 정확한
ServiceAccount binding과 audience `vault`를 검토합니다. Vault를 사용하지
않는 `api-dev`에는 role이 생성되지 않아야 합니다.
## 6. Runtime secret seed
Secret 값은 Git/Terraform을 통과하지 않습니다. 아래 변수는 operator
terminal session에만 유지합니다. 빈 dev lab의 최초 seed는 initial root
token을 잠깐 사용하고 seed 직후 CLI 환경에서 제거합니다.
```bash
export VAULT_TOKEN="$(
jq -r '.root_token' .local/vault/dev-k3s-init.json
)"
read -r -s -p "PostgreSQL superuser password: " POSTGRES_SUPERUSER_PASSWORD
echo
read -r -s -p "Auth database password: " AUTH_DB_PASSWORD
@@ -106,47 +197,72 @@ 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"
vault kv put kv/dev/systems/auth-system/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"
vault kv put kv/dev/systems/auth-system/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"
vault kv put kv/dev/systems/auth-system/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"
vault kv put kv/dev/systems/auth-system/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"
vault kv put kv/dev/workloads/auth-server/keycloak-client @"$secret_file"
rm -f "$secret_file"
trap - EXIT
unset VAULT_TOKEN
```
PostgreSQL이 Vault Agent 주입 후 시작하는지 확인합니다.
## 7. Vault Agent Injector gate
Vault auth, workload policy/role와 secret metadata를 확인한 뒤 Git PR에서
`vault-agent-injector` element만 `autoSync: "true"`로 바꿉니다. Merge 후
injector Deployment와 webhook health를 확인합니다. 이 단계에서도
`auth-system`과 두 workload gate는 닫혀 있어야 합니다.
## 8. Auth system gate
Secret metadata와 workload policy를 확인한 뒤 Git PR에서 `auth-system`
inventory element만 `autoSync: "true"`로 바꿉니다. PR에는 현재 전체 Argo
diff를 첨부합니다. Child manifest를 직접 apply하거나 Argo UI에서 임의로
Sync하지 않습니다.
Merge 후 PostgreSQL과 Keycloak health를 확인합니다.
```bash
kubectl -n platform rollout status statefulset/postgres --timeout=600s
kubectl -n auth-system-dev rollout status statefulset/postgres --timeout=600s
kubectl -n auth-system-dev rollout status deployment/keycloak --timeout=600s
```
## 6. Vault database state
## 9. Vault database state
초기 root token으로 TTL이 짧은 database 전용 token을 발급합니다.
`TF_VAR_vault_token`을 database 전용 short-lived token으로 교체하고
PostgreSQL credential을 실행 시점에만 전달합니다.
```bash
export TF_VAR_vault_token="$(
vault token create \
-policy=vault-database-automation-dev \
-ttl=30m \
-format=json |
export VAULT_DATABASE_TOKEN="$(
VAULT_TOKEN="$(
jq -r '.root_token' .local/vault/dev-k3s-init.json
)" \
vault token create \
-orphan \
-no-default-policy \
-renewable=false \
-explicit-max-ttl=2h \
-policy=vault-database-automation-dev \
-ttl=2h \
-format=json |
jq -r '.auth.client_token'
)"
export TF_VAR_vault_token="$VAULT_DATABASE_TOKEN"
export TF_VAR_postgres_admin_password="$POSTGRES_SUPERUSER_PASSWORD"
export TF_VAR_postgres_admin_password_version=1
@@ -160,50 +276,66 @@ make terraform-apply \
APPROVE_APPLY=dev-k3s/vault-database
```
## 7. Root token 폐기
Plan에는 `database/config/auth-system-postgres-dev` connection과
`auth-db-migration-dev` dynamic role만 있어야 합니다. Dynamic credential
발급과 revoke를 검증합니다.
Kubernetes auth operator login이 동작하는지 먼저 검증합니다.
## 10. Root token 폐기
```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
```
Database state와 delegated login/recovery 절차를 검증한 뒤, application
gate를 열기 전에 initial root token을 폐기합니다.
Script는 두 replacement token을 반드시 요구하고 다음을 자동 검증합니다.
검증 후 initial root token을 폐기합니다.
- `VAULT_WORKLOADS_TOKEN``sys/policies/acl/auth-server-dev``update`
capability를 가짐
- `VAULT_DATABASE_TOKEN`
`database/config/auth-system-postgres-dev``update` capability를 가짐
- 어느 replacement token도 `root` policy를 갖지 않음
이후 foundation을 다시 적용할 routine administrator는 없습니다. Encrypted
unseal custody의 담당자와 승인된 Vault generated-root recovery 절차를
확인할 수 없으면 root를 폐기하지 않습니다.
```bash
./hack/vault-init.sh revoke-root
unset operator_jwt operator_token
VAULT_TOKEN="$VAULT_WORKLOADS_TOKEN" vault token revoke -self
VAULT_TOKEN="$VAULT_DATABASE_TOKEN" vault token revoke -self
unset TF_VAR_vault_token TF_VAR_postgres_admin_password
unset VAULT_WORKLOADS_TOKEN VAULT_DATABASE_TOKEN
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 정책에 따라 제거합니다.
`revoke-root`는 local init JSON에서도 root token field를 제거합니다. 두
bootstrap orphan token도 검증 직후 self-revoke하며 routine credential로
재사용하지 않습니다.
## 8. 확인
## 11. Workload gates
Keycloak realm/client sync와 database migration credential이 준비된 것을
확인한 뒤 단계별 PR을 사용합니다.
1. `auth-server``autoSync: "true"`로 변경합니다.
2. `auth-dev/ghcr-regcred` Secret 생성, migration hook 성공과 Deployment
health를 확인합니다.
3. `api-server``autoSync: "true"`로 변경합니다.
4. `api-dev/ghcr-regcred` Secret 생성, north-south와 service-to-service
경로를 확인합니다.
한 PR에서 모든 gate를 동시에 열지 않습니다.
## 12. 최종 확인
```bash
kubectl -n argocd get applications
kubectl -n vault get pods
kubectl -n platform get pods
kubectl -n auth-system-dev 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을 사용합니다.
Encrypted custody로 옮긴 init material의 local working copy는 조직의 dev
recovery 정책에 따라 제거합니다. 실패를 고치기 위해 live child manifest를
직접 수정하지 말고 Git PR을 사용합니다.