refactor(gitops): establish platform ownership boundaries
This commit is contained in:
@@ -1,88 +1,244 @@
|
||||
# Terraform v2 state migration
|
||||
# Terraform state migration to three Vault states
|
||||
|
||||
새 클러스터에는 이 runbook이 필요하지 않습니다. legacy local state 또는
|
||||
이전 `provider-foundation`, `workload-foundation`, `workload-config`,
|
||||
`database-config` remote state가 실제로 존재할 때만 수행합니다.
|
||||
새 클러스터에는 이 runbook이 필요하지 않습니다. Legacy `vault-core` 또는
|
||||
더 오래된 `provider-foundation`, `workload-foundation`, `workload-config`,
|
||||
`database-config` state가 실제로 존재할 때만 사용합니다.
|
||||
|
||||
State 이동은 live object 삭제보다 위험할 수 있습니다. maintenance window와
|
||||
독립 backup 없이 진행하지 않습니다.
|
||||
> 2026-07-26 리팩터링에서는 이 절차를 실제 backend나 cluster에 실행하지
|
||||
> 않았습니다. Maintenance window, 독립 backup과 승인 없이 시작하지
|
||||
> 않습니다.
|
||||
|
||||
## 목표
|
||||
State 이동은 live object 삭제보다 위험할 수 있습니다. State split,
|
||||
Terraform module refactor, Vault KV path/namespace cutover를 한 apply에
|
||||
섞지 않습니다.
|
||||
|
||||
| 이전 state | 목표 |
|
||||
## 목표 ownership
|
||||
|
||||
| Legacy ownership | 목표 state |
|
||||
|---|---|
|
||||
| provider Transit Vault state | archive 후 provider Vault 폐기 절차에서 별도 처리 |
|
||||
| workload foundation | `vault-core`의 기준 state |
|
||||
| workload config | 소유 객체를 `vault-core`로 이동 |
|
||||
| database config | `vault-database`로 backend key migration |
|
||||
| `vault-core`의 mounts/auth/delegation 객체 | `vault-foundation` |
|
||||
| `vault-core` 또는 `workload-config`의 workload policy/role와 Transit key | `vault-workloads` |
|
||||
| `vault-database` 또는 `database-config`의 auth-system connection과 migration role | `vault-database` |
|
||||
| 별도 same-cluster Transit provider Vault | State archive 후 별도 decommission 절차 |
|
||||
|
||||
동일 클러스터 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를
|
||||
사용합니다.
|
||||
목표 backend key는 각각 달라야 합니다.
|
||||
|
||||
```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
|
||||
dev-k3s/vault-foundation.tfstate
|
||||
dev-k3s/vault-workloads.tfstate
|
||||
dev-k3s/vault-database.tfstate
|
||||
```
|
||||
|
||||
이미 다른 state에 address가 있으면 import하지 말고 state ownership을 먼저
|
||||
동일 Vault API path가 두 state에 동시에 남아 있으면 cutover가 끝난 것이
|
||||
아닙니다. State끼리 `terraform_remote_state`를 추가하지 않습니다.
|
||||
|
||||
## 1. Freeze, inventory, backup
|
||||
|
||||
모든 Terraform apply와 관련 image/config promotion을 중단합니다.
|
||||
ApplicationSet의 injector, `auth-system`, `auth-server`, `api-server`
|
||||
autoSync gate도 닫습니다. 이 gate는 Argo의 자동 sync만 멈추며 이미 실행
|
||||
중인 Pod의 재시작, node drain 또는 controller 동작을 막지 않습니다.
|
||||
Migration 중 legacy path를 병행 유지하고, 완전한 quiesce가 필요하면
|
||||
workload별 scale/maintenance 절차를 별도로 승인합니다.
|
||||
|
||||
각 legacy backend에서 다음을 확보합니다.
|
||||
|
||||
- `terraform state pull` 원본
|
||||
- state checksum
|
||||
- `terraform state list`
|
||||
- 이동할 각 address의 `terraform state show`
|
||||
- 현재 Vault object ID/path와 provider version
|
||||
|
||||
Backup에는 credential과 secret data가 포함될 수 있으므로 encrypted
|
||||
offline custody에 보관합니다. Backend lock이 작동하는지 확인하고 source
|
||||
state를 수정할 runner를 하나로 제한합니다.
|
||||
|
||||
## 2. Migration code 준비
|
||||
|
||||
먼저 실제로 배포된 legacy Git revision에서 임시 migration branch를
|
||||
만듭니다. 그 revision의 policy 문서, namespace binding, role payload를
|
||||
그대로 유지한 `vault-workloads` root와 import block만 추가합니다. 현재
|
||||
branch의 새 KV path와 `auth-system-dev` binding을 이 단계에 복사하면
|
||||
ownership 이동과 live policy 변경이 섞이므로 사용할 수 없습니다.
|
||||
|
||||
Source와 destination은 같은 legacy object payload를 선언해야 합니다.
|
||||
실제 import ID는 backup의 `state show`로 확정하며 이름을 추측하지
|
||||
않습니다. Ownership split이 양쪽 no-op으로 끝난 뒤에만 현재 desired
|
||||
revision을 별도 path/namespace cutover PR로 적용합니다.
|
||||
|
||||
일반적인 import ID 형식은 다음과 같습니다.
|
||||
|
||||
```text
|
||||
vault_policy <policy-name>
|
||||
vault_kubernetes_auth_backend_role auth/kubernetes/role/<role-name>
|
||||
vault_transit_secret_backend_key transit/keys/project-auth-jwt
|
||||
```
|
||||
|
||||
Legacy core/foundation source에는 이동 대상별 `removed` block을 둡니다.
|
||||
|
||||
```hcl
|
||||
removed {
|
||||
from = module.workload_policies
|
||||
|
||||
lifecycle {
|
||||
destroy = false
|
||||
}
|
||||
}
|
||||
|
||||
removed {
|
||||
from = module.workload_roles
|
||||
|
||||
lifecycle {
|
||||
destroy = false
|
||||
}
|
||||
}
|
||||
|
||||
removed {
|
||||
from = vault_transit_secret_backend_key.project_auth_jwt
|
||||
|
||||
lifecycle {
|
||||
destroy = false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
실제 address가 다르면 현재 state list를 사용합니다. 위 예를 그대로
|
||||
복사하지 않습니다.
|
||||
|
||||
Module 구조 개선은 아직 하지 않습니다. 우선 기존 address/선언으로
|
||||
ownership만 옮기고 후속 PR에서 `moved` block을 사용합니다.
|
||||
|
||||
## 3. 양쪽 plan 검토
|
||||
|
||||
Source와 destination을 같은 revision에서 plan합니다.
|
||||
|
||||
| Plan | 허용 결과 |
|
||||
|---|---|
|
||||
| Source core/foundation | 대상 object를 state에서만 제거, live destroy `0` |
|
||||
| Destination workloads | 기존 live object import, create/change/destroy `0` |
|
||||
|
||||
둘 중 하나라도 live create, update, delete를 제안하면 중단합니다. Policy
|
||||
내용이나 Vault path rename은 이 단계에 포함하지 않습니다.
|
||||
|
||||
## 4. Workload ownership split
|
||||
|
||||
승인된 maintenance window에서 다음 순서로 진행합니다.
|
||||
|
||||
1. Source의 `removed { destroy = false }` apply
|
||||
2. 즉시 destination import apply
|
||||
3. 양쪽 `state list`에서 object가 정확히 한 번만 나타나는지 확인
|
||||
4. 양쪽 plan이 no-op인지 확인
|
||||
|
||||
Manual remote `state push`나 offline `state mv -state/-state-out`을 primary
|
||||
절차로 사용하지 않습니다. Source 제거와 destination import 사이에 문제가
|
||||
생기면 다른 apply를 진행하지 말고 backup과 승인된 rollback 절차를
|
||||
사용합니다.
|
||||
|
||||
## 5. Foundation backend key 전환
|
||||
|
||||
Workload object를 분리한 뒤 남은 legacy `vault-core` state 전체를
|
||||
`vault-foundation` backend key로 `terraform init -migrate-state` 합니다.
|
||||
Migration 전후의 state list와 serial/lineage, checksum을 기록합니다.
|
||||
|
||||
`vault-database`의 state 경계는 유지하지만 connection address와 live
|
||||
object 이름은 모두 바뀝니다.
|
||||
|
||||
```text
|
||||
vault_database_secret_backend_connection.platform_postgres
|
||||
-> vault_database_secret_backend_connection.auth_system_postgres
|
||||
|
||||
database/config/platform-postgres-dev
|
||||
-> database/config/auth-system-postgres-dev
|
||||
```
|
||||
|
||||
Checked-in `moved` block은 Terraform address만 이관합니다. Vault connection
|
||||
이름 변경은 replacement이며 `create_before_destroy`도 생성/삭제 사이에
|
||||
operator 승인 대기 시간을 만들지 않습니다. 기존 cluster에서는 final
|
||||
configuration을 바로 apply하지 말고 별도 blue/green cutover를 준비합니다.
|
||||
|
||||
1. 임시 migration revision에서 old connection을 유지하고 new connection을
|
||||
별도 resource로 추가합니다.
|
||||
2. Database runner policy가 maintenance window 동안 old/new 두 exact
|
||||
`database/config` path를 모두 허용하게 합니다.
|
||||
3. New connection 검증 뒤 `auth-db-migration-dev` role을 new connection으로
|
||||
전환하고 credential issue/revoke를 시험합니다.
|
||||
4. Old connection에 연결된 lease를 inventory하고 만료 또는 명시적 revoke를
|
||||
확인합니다.
|
||||
5. 후속 승인에서 old connection과 임시 policy path를 제거합니다.
|
||||
|
||||
기존 이름이 `database-config`이거나 backend key가 다를 때는 이 cutover와
|
||||
분리해 `vault-database` backend key로 migration합니다.
|
||||
|
||||
Database object를 새로 import해야 하는 경우의 target address와 ID는
|
||||
다음과 같습니다.
|
||||
|
||||
```text
|
||||
vault_database_secret_backend_connection.auth_system_postgres
|
||||
database/config/auth-system-postgres-dev
|
||||
vault_database_secret_backend_role.auth_db_migration
|
||||
database/roles/auth-db-migration-dev
|
||||
```
|
||||
|
||||
이미 다른 state에 address가 있으면 import하지 말고 ownership을 먼저
|
||||
이동합니다.
|
||||
|
||||
## 5. Cutover 완료 조건
|
||||
## 6. Retired broad/unused access
|
||||
|
||||
- 두 목표 state가 remote backend와 locking을 사용
|
||||
- 동일 Vault path가 두 state list에 나타나지 않음
|
||||
- plan에 예상하지 않은 create/delete가 없음
|
||||
- legacy state와 backup은 immutable archive
|
||||
- repo와 runner에 local state/provider directory가 없음
|
||||
다음 legacy object는 새 state의 desired ownership이 아닙니다.
|
||||
|
||||
검증이 끝나기 전 legacy backend를 삭제하지 않습니다.
|
||||
- Broad `platform-admin-dev` policy와 이를 사용한 `vault-operator-dev` role
|
||||
- Legacy 단일 `project-gitops-dev` CI JWT role
|
||||
- 미사용 `keycloak-operator-dev`, `postgres-operator-dev` policies
|
||||
- 미사용 `database/roles/postgres-operator-dev` dynamic role
|
||||
|
||||
State split 중 자동 destroy하지 않습니다. 우선 `removed { destroy = false }`
|
||||
로 legacy source ownership에서 분리하고, Vault audit/consumer inventory로
|
||||
사용자가 없음을 확인합니다. Token/lease revoke와 live object 삭제는
|
||||
별도 보안 decommission 승인으로 수행합니다.
|
||||
|
||||
기존 `jwt-ci` auth mount가 state에 있으면 ownership 이동 동안 실제
|
||||
issuer/discovery 입력을 유지합니다. 입력을 누락해 `count = 0`이 되어도
|
||||
`prevent_destroy`가 mount 삭제를 차단해야 합니다. Mount와 그 하위 role을
|
||||
제거할 때만 token/accessor와 consumer를 확인한 별도 decommission
|
||||
revision에서 명시적으로 보호를 해제합니다.
|
||||
|
||||
## 7. Vault path와 namespace cutover
|
||||
|
||||
State split이 no-op인 것을 확인한 뒤 별도 PR/maintenance window에서
|
||||
다음 legacy path를 새 owner path로 이관합니다.
|
||||
|
||||
```text
|
||||
kv/dev/platform/postgres/* -> kv/dev/systems/auth-system/postgres/*
|
||||
kv/dev/platform/keycloak/bootstrap-admin
|
||||
-> kv/dev/systems/auth-system/keycloak/bootstrap-admin
|
||||
kv/dev/platform/keycloak/client-auth-server
|
||||
-> kv/dev/workloads/auth-server/keycloak-client
|
||||
```
|
||||
|
||||
KV payload는 Terraform으로 이동하지 않습니다. 승인된 operator가 값을
|
||||
노출하지 않는 secret procedure로 새 path에 기록하고 metadata/version을
|
||||
확인합니다. Policy, Kubernetes role, Agent annotation, namespace/DNS
|
||||
변경을 render와 Vault capability test로 검증합니다.
|
||||
|
||||
`platform`에서 `auth-system-dev`로의 live namespace 이동은 Kubernetes
|
||||
state/data migration입니다. Terraform state split과 별도로 backup,
|
||||
non-cascading ownership transfer, rollback 계획을 가져야 합니다.
|
||||
|
||||
새 consumer가 정상 동작하고 rollback 기간이 끝날 때까지 legacy KV value와
|
||||
policy를 삭제하지 않습니다. 삭제는 별도 승인 작업입니다.
|
||||
|
||||
## 8. 완료 조건
|
||||
|
||||
- `vault-foundation`, `vault-workloads`, `vault-database`가 서로 다른 remote
|
||||
backend와 lock을 사용
|
||||
- 각 Vault API path가 정확히 한 state list에만 존재
|
||||
- 세 plan에 예상하지 않은 create/change/delete가 없음
|
||||
- Delegated state가 자신의 automation policy/login role을 소유하지 않음
|
||||
- Workload/database identity의 허용·거부 capability test 통과
|
||||
- Legacy state와 backup이 immutable archive에 있음
|
||||
- Repository와 runner working directory에 local state, plan, provider
|
||||
directory가 없음
|
||||
- New KV path와 `auth-system-dev` cutover 전에는 관련 autoSync gate가 닫힘
|
||||
|
||||
검증이 끝나기 전 legacy backend, Vault path, namespace 또는 PVC를
|
||||
삭제하지 않습니다.
|
||||
|
||||
Reference in New Issue
Block a user