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
+150 -29
View File
@@ -1,38 +1,159 @@
# Argo CD layout
# Argo CD architecture
`bootstrap/argocd/root-application.yaml`이 유일한 수동 seed입니다. 이
Application은 `clusters/dev-k3s`를 source로 사용하고 다음 리소스를
소유합니다.
## Control-plane ownership
Controller 설치 후 다음 두 bootstrap object를 순서대로 수동 seed합니다.
1. `bootstrap/argocd/control-plane-project.yaml`
2. `bootstrap/argocd/root-application.yaml`
`gitops-control-plane` AppProject는 canonical Gitea repository와 in-cluster
`argocd` namespace, AppProject/ApplicationSet kind만 허용합니다. 단일 root
Application은 이 Project를 사용하고 다음 control-plane 구성을 source로
사용합니다.
```text
clusters/dev-k3s
platform/control-plane/argocd
├── projects
└── applications
├── foundation
│ ├── sealed-secrets
── vault
│ └── vault-agent-injector
├── platform
│ └── auth-system
── workloads
├── auth-server
└── api-server
│ ├── platform-addons.yaml
├── platform-services.yaml
│ ├── systems.yaml
── workloads.yaml
└── application-sets
├── platform-addons.yaml
├── platform-services.yaml
── systems.yaml
└── workloads.yaml
```
AppProject는 root sync wave `-10`, foundation은 `0~1`, platform은 `10`,
workload는 `20`입니다. 이 wave는 child Application 객체 생성 순서만
표현하며 서로 다른 Application의 readiness dependency로 사용하지
않습니다. Vault Agent와 workload는 필요한 Vault/DB API가 준비될 때까지
자체 retry 가능한 형태여야 합니다.
Root는 AppProject와 ApplicationSet까지만 직접 소유합니다. 각
ApplicationSet의 list inventory가 실제 child Application을 생성합니다.
Routine 변경에 category별 root나 직접 `kubectl apply`를 추가하지 않습니다.
모든 child Application은 auto-sync, prune, self-heal을 사용합니다.
Application 삭제와 parent prune은 확인이 필요하며, shared resource
소유권 충돌은 `FailOnSharedResource=true`로 실패시킵니다.
## AppProject boundary
Sync hook이 있는 `auth-server``auth-system`에는 selective sync 옵션을
사용하지 않습니다. DB migration과 Keycloak client sync는 같은
Application 내부 wave로 순서를 제어합니다.
| AppProject | 소유 범위 | 허용 destination |
|---|---|---|
| `platform-addons` | Sealed Secrets, Vault Agent Injector 같은 외부 cluster addon | `kube-system`, `vault` |
| `platform-services` | 저장소가 소유하는 Vault shared service | `vault` |
| `systems` | Project Auth 전용 PostgreSQL, Keycloak, sync job | `auth-system-dev` |
| `workloads` | first-party `auth-server`, `api-server` | `auth-dev`, `api-dev` |
클러스터가 하나이고 child Application 수가 적으므로 현재는 명시적
Application을 사용합니다. 두 번째 클러스터나 실제 production이 생길
때 foundation/platform/workload별 ApplicationSet 도입을 검토합니다.
Project는 ApplicationSet 파일마다 고정하며 inventory 값으로 template하지
않습니다. 이렇게 해야 element 변경으로 권한 경계를 넘을 수 없습니다.
각 Project는 필요한 source repository, destination, resource kind만
allowlist합니다.
## ApplicationSet contract
ApplicationSet은 strict Go template와 list generator를 사용합니다.
- `goTemplate: true`
- `goTemplateOptions: ["missingkey=error"]`
- 공통 element: `component`, `cluster`, `server`, `namespace`, quoted string
`autoSync`
- Git source element: ownership grammar를 따르는 `path`; `targetRevision`
template의 `main`으로 고정
- Helm addon element: allowlisted `repoURL`, `chart`, chart `revision`,
`helmValues`
- 파일별 고정 project
필수 key가 빠지면 빈 문자열로 잘못 배포하지 않고 render가 실패해야 합니다.
Application 이름, destination, source path는 같은 element에서 파생하되
project와 Git repository/revision trust boundary는 template하지 않습니다.
외부 addon의 Helm `repoURL`은 element에서 template되지만 고정 AppProject의
`sourceRepos` allowlist 밖 URL은 sync할 수 없습니다.
## `autoSync` stage gate
`autoSync`는 bootstrap 준비 상태와 routine reconciliation을 분리합니다.
Inventory schema는 Go template 비교를 위해 boolean이 아닌 quoted string
`"true"`/`"false"`를 사용합니다. Template patch는 값이 `"true"`
element에만 다음 정책을 추가합니다.
```yaml
syncPolicy:
automated:
enabled: true
prune: true
selfHeal: true
```
초기 gate는 다음과 같습니다.
| Application | 초기 `autoSync` | 열기 전 확인 |
|---|---:|---|
| Sealed Secrets | `"true"` | Argo가 chart source를 읽을 수 있음 |
| Vault | `"true"` | dev PVC와 NetworkPolicy 변경 검토 |
| Vault Agent Injector | `"false"` | Vault init, `vault-foundation`, `vault-workloads`, runtime secret seed 완료 |
| `auth-system` | `"false"` | Injector Healthy와 Vault login/secret capability 확인 |
| `auth-server` | `"false"` | PostgreSQL Healthy, `vault-database`, Keycloak/sync 준비 완료 |
| `api-server` | `"false"` | `auth-server` Healthy와 호출 경로 확인 |
Gate는 단계별 PR로 하나씩 엽니다. `false`여도 Application 생성과 diff
표시는 계속되며 수동 Sync 자체를 기술적으로 막지는 않습니다. 따라서
Argo RBAC에서 sync 권한을 제한하고, 수동 Sync에는 명시적 change record를
요구합니다.
비활성 기간 동안 쌓인 모든 diff가 gate를 여는 순간 함께 반영됩니다.
`autoSync: "true"` PR은 현재 live-to-desired 전체 diff를 검토한 뒤
승인해야 합니다.
## Ordering과 failure handling
Control-plane sync wave는 AppProject(`-10`)를 ApplicationSet(`-5`)보다
먼저 생성합니다. 네 ApplicationSet은 모두 같은 wave이고 element별
stage/wave field는 없습니다. Generated Application의 실제 readiness
순서는 `autoSync` 전환과 health 확인이 담당합니다.
Vault Agent, workload와 hook은 선행 API가 늦게 준비될 때 retry할 수 있어야
합니다. `auth-system`의 Keycloak client sync와 `auth-server`의 database
migration은 idempotent Sync hook입니다. Hook을 사용하는 Application에는
`ApplyOutOfSyncOnly=true`를 설정하지 않습니다.
Generated Application은 automated 상태에서 `PruneLast=true`
`FailOnSharedResource=true`를 사용합니다. ApplicationSet은
`applicationsSync: create-update``preserveResourcesOnDeletion: true`
사용합니다. Parent prune과 Application 삭제에는 확인을 요구합니다.
CRD와 cluster-wide RBAC를 포함할 수 있는 `platform-addons`
Application-level `Prune=confirm`도 사용하므로 chart upgrade의 삭제는
별도 승인이 필요합니다.
Stateful path rename이나 ownership 이동은 별도 migration으로 수행하며
ApplicationSet element를 먼저 삭제하지 않습니다.
`create-update`에서는 generator element를 제거해도 기존 generated
Application이 자동 삭제되지 않고 stale 상태로 남습니다. Element 제거와
Application/resource decommission은
[application decommission runbook](../runbooks/application-decommission.md)의
inventory, gate, backup, 명시적 삭제 절차를 따릅니다.
## Generator 확장 기준
현재는 cluster가 `dev-k3s` 하나이므로 네 ApplicationSet의 explicit List
generator가 가장 쉽게 검토됩니다. 존재하지 않는 production이나 미래
cluster를 위해 Matrix abstraction을 미리 만들지 않습니다.
두 번째 실제 cluster가 생겨 `cluster`, `server`와 cluster별 gate를 여러
component에서 반복하게 될 때 `clusters/<cluster>/config.yaml`을 Git files
generator로 읽고 component inventory와 Matrix generator로 결합합니다.
그때도 AppProject는 ApplicationSet template에 고정하고, cluster별
`autoSync`는 quoted string과 승인 gate로 유지합니다.
## Further reading
- Argo CD: [cluster bootstrapping](https://argo-cd.readthedocs.io/en/stable/operator-manual/cluster-bootstrapping/),
[ApplicationSet modification policy](https://argo-cd.readthedocs.io/en/stable/operator-manual/applicationset/Controlling-Resource-Modification/),
[ApplicationSet deletion](https://argo-cd.readthedocs.io/en/stable/operator-manual/applicationset/Application-Deletion/),
[automated sync semantics](https://argo-cd.readthedocs.io/en/stable/user-guide/auto_sync/)
- Kubernetes:
[Kustomize](https://kubernetes.io/docs/tasks/manage-kubernetes-objects/kustomization/)
- Terraform: [state refactoring](https://developer.hashicorp.com/terraform/language/state/refactor),
[`terraform_remote_state` security warning](https://developer.hashicorp.com/terraform/language/state/remote-state-data),
[write-only arguments](https://developer.hashicorp.com/terraform/language/manage-sensitive-data/write-only)
- Vault:
[JWT/OIDC authentication](https://developer.hashicorp.com/vault/docs/auth/jwt)
## Repository-only change
이 구조와 gate 설계는 2026-07-26 현재 Git에서만 작성·검증했습니다. 실제
cluster migration이나 sync는 이 리팩터링 리뷰 범위에 포함되지 않습니다.