refactor(gitops): establish platform ownership boundaries
This commit is contained in:
@@ -2,23 +2,54 @@
|
||||
|
||||
Status: accepted
|
||||
|
||||
Terraform은 VM/네트워크뿐 아니라 provider가 제공되는 Vault API 객체도
|
||||
관리할 수 있다. 현재 저장소의 Terraform 범위는 Vault API이고 실제
|
||||
machine provisioning은 provider가 확정될 때 별도 root로 추가한다.
|
||||
Updated: 2026-07-26
|
||||
|
||||
`dev-k3s`는 두 state만 사용한다.
|
||||
Terraform의 현재 범위는 Vault API 객체입니다. Kubernetes 리소스는 Argo
|
||||
CD가 소유하며, machine/network provisioning은 provider와 운영 경계가
|
||||
확정될 때 별도 root로 추가합니다.
|
||||
|
||||
- `vault-core`: mounts, auth backends, policies, Kubernetes/JWT roles,
|
||||
application Transit key
|
||||
- `vault-database`: PostgreSQL connection과 dynamic roles
|
||||
`dev-k3s`는 정확히 세 state를 사용합니다.
|
||||
|
||||
resource/API path 하나는 한 state에만 속한다. state는 암호화, versioning,
|
||||
access control, locking이 가능한 remote backend에 저장한다.
|
||||
| State | 소유 객체 |
|
||||
|---|---|
|
||||
| `vault-foundation` | KV/database/Transit mounts, Kubernetes auth backend/config, delegated automation policy와 선택적 분리 CI JWT auth role |
|
||||
| `vault-workloads` | workload ACL policy, Kubernetes auth role, `project-auth-jwt` Transit key |
|
||||
| `vault-database` | `database/config/auth-system-postgres-dev` connection과 `auth-db-migration-dev` dynamic role |
|
||||
|
||||
`vault-core`는 privilege-escalation 가능한 객체를 포함하므로 제한된
|
||||
관리자 실행만 허용한다. `vault-database`는 core가 생성한
|
||||
`vault-database-automation-dev` 정책의 short-lived identity로 실행한다.
|
||||
Resource 또는 Vault API path 하나는 한 state에만 속합니다. State 사이는
|
||||
이름 contract와 실행 순서만 공유하며 `terraform_remote_state`로 서로의
|
||||
snapshot을 읽지 않습니다. Backend는 encryption, versioning, access
|
||||
control, locking을 제공해야 합니다.
|
||||
|
||||
Secret payload는 Terraform resource/data source로 관리하지 않는다.
|
||||
필수 credential은 ephemeral variable과 provider write-only argument를
|
||||
통해서만 apply에 전달한다.
|
||||
Privilege delegation의 경계는 다음과 같습니다.
|
||||
|
||||
- `vault-foundation`은 bootstrap 또는 보안 관리자 승인 때만 실행합니다.
|
||||
Routine CI identity를 두지 않습니다.
|
||||
- `vault-foundation`이 workload/database 전용 automation policy와,
|
||||
OIDC/JWT trust가 검증된 경우 서로 분리된 CI JWT login role을 생성합니다.
|
||||
두 role의 exact claim map은 최소 한 공통 discriminator key에서 서로 다른
|
||||
값을 가져야 하므로 동일 scalar-claim JWT가 둘 다 선택할 수 없습니다.
|
||||
- `vault-workloads`와 `vault-database`는 각각의 short-lived identity를
|
||||
소비할 뿐 자신에게 권한을 부여하는 객체를 소유하지 않습니다.
|
||||
- Delegated identity는 자신이 맡은 정확한 policy, auth role, database
|
||||
path만 CRUD할 수 있습니다.
|
||||
- Broad `platform-admin` 또는 상시 cluster-internal Vault administrator를
|
||||
routine automation에 연결하지 않습니다.
|
||||
|
||||
실제 CI issuer가 repository, protected ref와 job discriminator claim을
|
||||
어떤 형식으로 발행하는지 먼저 검증합니다. 그 계약을 확인할 수 없으면 JWT
|
||||
auth를 활성화하지 않고 bootstrap용 short-lived token만 사용합니다.
|
||||
Foundation의 future change는 routine identity가 아니라 encrypted unseal
|
||||
custody를 사용한 승인된 generated-root ceremony가 필요합니다.
|
||||
|
||||
Secret payload는 Terraform resource/data source로 관리하지 않습니다.
|
||||
Provider token과 PostgreSQL credential은 ephemeral variable과 write-only
|
||||
argument를 통해 실행 시점에만 전달합니다. Vault init material, token,
|
||||
password, plan과 state를 Git에 저장하지 않습니다.
|
||||
|
||||
기존 `vault-core`에서 세 state로 바꾸는 작업은 선언 이동과 state ownership
|
||||
이관을 분리해 수행합니다. Source에서는 `removed { destroy = false }`,
|
||||
destination에서는 import를 사용하고, 양쪽 plan의 destroy가 0인지 확인하기
|
||||
전에는 apply하지 않습니다. Broad `platform-admin`/`vault-operator`와
|
||||
미사용 Keycloak/PostgreSQL operator policy/role은 새 state로 옮기지
|
||||
않으며 consumer가 없음을 확인한 별도 decommission에서 제거합니다.
|
||||
|
||||
@@ -1,18 +1,43 @@
|
||||
# ADR 0004: Single Argo CD root
|
||||
# ADR 0004: Single Argo CD root and stage-gated ApplicationSets
|
||||
|
||||
Status: accepted
|
||||
|
||||
Argo CD 설치 후 `bootstrap/argocd/root-application.yaml` 하나만 seed한다.
|
||||
root는 `clusters/dev-k3s`의 AppProject와 모든 child Application을 소유한다.
|
||||
Updated: 2026-07-26
|
||||
|
||||
반복 `kubectl apply`와 foundation/platform/application별 root wrapper는
|
||||
제거한다. routine deployment는 Git merge만으로 시작한다.
|
||||
Argo CD 설치 후 bootstrap 전용 `gitops-control-plane` AppProject와 단일
|
||||
root Application을 순서대로 수동 seed합니다. Root는
|
||||
`platform/control-plane/argocd`의 AppProject와 ApplicationSet을 소유하고,
|
||||
ApplicationSet이 platform addon/shared service, system, workload
|
||||
Application을 생성합니다. Bootstrap Project는 canonical repository,
|
||||
`argocd` namespace와 AppProject/ApplicationSet kind만 허용합니다.
|
||||
|
||||
Child Application의 sync wave는 객체 생성 순서를 가독성 있게 표현하지만
|
||||
서로 다른 Application의 readiness dependency로 간주하지 않는다.
|
||||
Workload와 hook은 Vault/DB가 늦게 준비되는 상황을 retry할 수 있어야 한다.
|
||||
반복 `kubectl apply`와 category별 root wrapper는 사용하지 않습니다.
|
||||
Routine deployment는 Git merge만으로 시작합니다.
|
||||
|
||||
Root가 child Application을 prune하거나 삭제하려면 확인이 필요하다.
|
||||
shared resource 소유권 충돌은 sync를 실패시킨다. 현재 규모에서는 명시적
|
||||
Application을 사용하고 두 번째 클러스터가 생길 때 ApplicationSet을
|
||||
검토한다.
|
||||
각 ApplicationSet inventory 항목은 다음 계약을 명시합니다.
|
||||
|
||||
- 고유 이름, AppProject, source, destination namespace
|
||||
- component/cluster/path와 automated reconciliation 허용 여부인 quoted
|
||||
string `autoSync`
|
||||
|
||||
새 항목과 외부 준비 조건이 있는 항목은 `autoSync: "false"`로 시작합니다.
|
||||
현재 bootstrap은 Sealed Secrets와 Vault만 열린 상태에서 시작해
|
||||
foundation/workloads state와 secret seed, injector, auth-system, database,
|
||||
first-party workload 순서로 별도 PR gate를 엽니다. Template은
|
||||
`autoSync: "true"`인 항목에만 automated sync, prune, self-heal을
|
||||
생성합니다.
|
||||
Gate가 닫힌 Application의 수동 sync도 change record와 명시적 operator
|
||||
판단을 요구합니다.
|
||||
|
||||
Sync wave는 AppProject(`-10`)를 ApplicationSet(`-5`)보다 먼저 생성합니다.
|
||||
모든 ApplicationSet은 같은 wave이며 element별 stage field는 없습니다.
|
||||
서로 다른 generated Application의 readiness는 gate와 runbook이
|
||||
제어합니다. Workload와 hook은 Vault/DB가 늦게 준비되는 상황을 retry할
|
||||
수 있고 idempotent해야 합니다.
|
||||
|
||||
Root는 AppProject와 ApplicationSet만 prune 대상으로 봅니다. Generated
|
||||
Application의 owner는 ApplicationSet이며 `create-update`에서는 element
|
||||
제거만으로 삭제되지 않습니다. Application/resource 해체는 별도
|
||||
decommission runbook과 확인 승인을 사용합니다. Shared resource 소유권
|
||||
충돌은 sync를 실패시킵니다. Sync hook을 사용하는 Application에는
|
||||
`ApplyOutOfSyncOnly=true`를 사용하지 않습니다.
|
||||
|
||||
@@ -2,16 +2,31 @@
|
||||
|
||||
Status: accepted
|
||||
|
||||
현재는 하나의 platform 팀, 하나의 dev cluster와 소수 workload를 가지므로
|
||||
GitOps configuration monorepo를 유지한다. application source repository와
|
||||
deployment configuration repository는 분리한다.
|
||||
Updated: 2026-07-26
|
||||
|
||||
- `platform/`, `workloads/`: 환경 중립 base
|
||||
- `clusters/<cluster>/manifests`: cluster-specific final composition
|
||||
- `clusters/<cluster>/applications`: Argo reconciliation inventory
|
||||
현재는 단일 `dev-k3s`와 소수 workload를 다루므로 GitOps configuration
|
||||
monorepo를 유지합니다. Application source repository와 deployment
|
||||
configuration repository는 분리합니다. 이 저장소 자체는 독립 reference
|
||||
lab이며 범용 platform product로 간주하지 않습니다.
|
||||
|
||||
- `platform/`, `systems/`, `workloads/`: ownership별 base; 환경 중립은
|
||||
목표 contract
|
||||
- `clusters/<cluster>/overlays`: cluster-specific final composition
|
||||
- `platform/control-plane/argocd/projects`: Argo 권한 경계
|
||||
- `platform/control-plane/argocd/application-sets`: reconciliation inventory
|
||||
- `iac/terraform`: Kubernetes manifest와 분리된 external API IaC
|
||||
- `bootstrap`: controller가 존재하기 전의 최소 seed
|
||||
|
||||
production 접근권한, 소유 팀, Terraform backend 또는 release cadence가
|
||||
실제로 갈라질 때 platform GitOps, workload GitOps, IaC repo 분리를
|
||||
재검토한다. 존재하지 않는 환경의 skeleton은 유지하지 않는다.
|
||||
`clusters`가 배포 가능한 최종 상태를 소유합니다. Argo CD는 top-level
|
||||
base를 직접 source로 사용하지 않습니다. `foundation`은 directory
|
||||
taxonomy가 아니라 bootstrap ordering/stage이고, 구체적인 ownership 분류는
|
||||
ADR 0007을 따릅니다.
|
||||
|
||||
현재 Keycloak base의 `start-dev`와 Vault base의
|
||||
TLS-off/single-node identity는 이 contract를 위반하는 알려진 리팩터링
|
||||
부채입니다. 다른 환경을 추가하기 전에 해당 값을 component overlay나
|
||||
configuration input으로 분리합니다.
|
||||
|
||||
Production 접근권한, 소유 팀, Terraform backend 또는 release cadence가
|
||||
실제로 갈라질 때 platform GitOps, workload GitOps, IaC repository 분리를
|
||||
재검토합니다. 존재하지 않는 환경의 skeleton은 유지하지 않습니다.
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
# ADR 0007: Repository ownership boundaries
|
||||
|
||||
Status: accepted
|
||||
|
||||
Date: 2026-07-26
|
||||
|
||||
## Context
|
||||
|
||||
기존 layout은 Vault, PostgreSQL, Keycloak과 Project Auth 구성을 모두
|
||||
`platform` 또는 `foundation`으로 표현했습니다. 이 이름은 설치 순서를
|
||||
보여 주지만 누가 소비하고 변경을 책임지는지 구분하지 못했습니다.
|
||||
클러스터별 최종 구성도 `manifests`라는 일반 이름 아래 섞여 있어 base와
|
||||
overlay의 관계가 불명확했습니다.
|
||||
|
||||
이 저장소는 하나의 실제 사내 플랫폼을 배포하는 저장소가 아니라 Project
|
||||
Auth를 예제로 한 독립 GitOps reference lab입니다. 따라서 존재하지 않는
|
||||
팀/환경을 가정한 추상화보다 현재 리소스의 실제 owner와 lifecycle을
|
||||
명확히 해야 합니다.
|
||||
|
||||
## Decision
|
||||
|
||||
최상위 Kubernetes desired state를 다음 소유권으로 분류합니다.
|
||||
|
||||
- `platform`: 여러 system이 사용할 수 있고 독립 lifecycle을 가진 cluster
|
||||
capability
|
||||
- `systems`: 특정 bounded context가 소유하는 backing services와 domain
|
||||
configuration
|
||||
- `workloads`: 별도 source repository와 release digest를 가진 first-party
|
||||
실행 애플리케이션
|
||||
- `clusters/<cluster>/overlays`: 위 base에 namespace, image, host, secret
|
||||
reference, network boundary를 결합한 최종 구성
|
||||
|
||||
Vault는 `platform/shared-services/vault`에 둡니다. Sealed Secrets와 Vault
|
||||
Agent Injector는 cluster addon inventory로 관리합니다. PostgreSQL,
|
||||
Keycloak, realm/client sync는 Project Auth 전용이므로
|
||||
`systems/auth-system`으로 이동합니다. `auth-server`와 `api-server`는
|
||||
`workloads`에 유지합니다.
|
||||
|
||||
Project Auth backing system의 namespace는 `auth-system-dev`로 정하고,
|
||||
Vault KV 경로도 `systems/auth-system` 또는 실제 workload owner를
|
||||
반영하도록 바꿉니다.
|
||||
|
||||
`foundation`은 ownership directory로 사용하지 않습니다. 준비 순서는
|
||||
분리된 ApplicationSet category, 명시적 `autoSync` gate, runbook과 workload
|
||||
retry/idempotency로 표현합니다.
|
||||
|
||||
## Consequences
|
||||
|
||||
- 디렉터리 경로만 보고 owner와 blast radius를 추론할 수 있습니다.
|
||||
- Base는 환경 중립 contract를 목표로 하고 Argo CD는 cluster overlay만
|
||||
source로 사용합니다. 현재 Keycloak/Vault base의 dev-only 값은 알려진
|
||||
후속 리팩터링 대상입니다.
|
||||
- auth-system 이동은 namespace, DNS, NetworkPolicy, Vault policy/path와
|
||||
Terraform role binding을 함께 바꾸는 migration입니다. 단순 파일 이동으로
|
||||
취급하면 안 됩니다.
|
||||
- ApplicationSet 도입은 반복 YAML을 줄이지만 각 파일에 project를 고정하고
|
||||
Git 항목에는 component, cluster, destination, path와 quoted `autoSync`를
|
||||
명시하도록 요구합니다. Git revision은 template의 `main`으로 고정합니다.
|
||||
- 새 capability가 공용인지 system 전용인지 애매하면 소비자 수, owner,
|
||||
release cadence가 분리되는지를 먼저 검토합니다.
|
||||
- 실제 production 요구가 생기기 전에는 production skeleton을 만들지
|
||||
않습니다.
|
||||
|
||||
세부 path와 예시는
|
||||
`docs/architecture/repository-taxonomy.md`를 따른다.
|
||||
Reference in New Issue
Block a user