refactor: 구조 변경

This commit is contained in:
donghyeon-ka
2026-08-28 17:24:26 +09:00
parent a6f6c663e0
commit b8626946b1
192 changed files with 2251 additions and 206 deletions
@@ -0,0 +1,12 @@
# ADR 0001: Internal Gitea is canonical
Status: accepted
The internal repository
`https://git.learn.hyeonworks.com/donghyeon.kang/project-gitops` is the only
writable deployment source.
Argo CD and Gitea Actions use this URL. A GitHub copy may exist only as a
read-only mirror with monitored replication; it must never be an independent
deployment branch. GHCR remains an image registry and does not require GitHub
to host the GitOps source.
@@ -0,0 +1,55 @@
# ADR 0002: Terraform state ownership
Status: accepted
Updated: 2026-07-26
Terraform의 현재 범위는 Vault API 객체입니다. Kubernetes 리소스는 Argo
CD가 소유하며, machine/network provisioning은 provider와 운영 경계가
확정될 때 별도 root로 추가합니다.
`dev-k3s`는 정확히 세 state를 사용합니다.
| 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 |
Resource 또는 Vault API path 하나는 한 state에만 속합니다. State 사이는
이름 contract와 실행 순서만 공유하며 `terraform_remote_state`로 서로의
snapshot을 읽지 않습니다. Backend는 encryption, versioning, access
control, locking을 제공해야 합니다.
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에서 제거합니다.
+25
View File
@@ -0,0 +1,25 @@
# ADR 0003: Vault topology
Status: accepted for dev, production decision pending
동일한 단일 노드 K3s 안의 두 Vault는 failure domain을 분리하지 못하면서
초기화, Transit credential, rotation과 staged apply 절차를 추가했다.
따라서 `dev-k3s`는 단일 self-hosted Vault로 단순화한다.
Dev profile:
- single-node integrated Raft
- Shamir 1-of-1 init/unseal
- TLS 미적용
- 명시적 backup/recovery runbook
이 구성은 production에 사용할 수 없다. production은 다음 중 하나를
선택해야 한다.
- managed Vault
- workload cluster 밖의 독립 HA Vault
- 최소 3-node integrated-Raft + TLS + KMS/HSM auto-unseal + PDB,
anti-affinity와 정기 restore exercise
production Vault가 결정되기 전에는 production manifest와 Terraform root를
만들지 않는다.
@@ -0,0 +1,44 @@
# ADR 0004: Single Argo CD root and stage-gated ApplicationSets
Status: accepted
Updated: 2026-07-26
Argo CD 설치 후 bootstrap 전용 `gitops-control-plane` AppProject와 단일
root Application을 순서대로 수동 seed합니다. Root는
`gitops/clusters/dev-k3s`를 source로 사용합니다. 이 cluster root가
`gitops/platform/control-plane/argocd`의 AppProject와 ApplicationSet을 소유하고,
ApplicationSet이 platform addon/shared service, system, workload
Application을 생성합니다. Bootstrap Project는 canonical repository,
`argocd` namespace와 AppProject/ApplicationSet kind만 허용합니다.
반복 `kubectl apply`와 category별 root wrapper는 사용하지 않습니다.
Routine deployment는 Git merge만으로 시작합니다.
각 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`를 사용하지 않습니다.
+33
View File
@@ -0,0 +1,33 @@
# ADR 0005: Cluster-first repository layout
Status: accepted
Updated: 2026-07-26
현재는 단일 `dev-k3s`와 소수 workload를 다루므로 GitOps configuration
monorepo를 유지합니다. Application source repository와 deployment
configuration repository는 분리합니다. 이 저장소 자체는 독립 reference
lab이며 범용 platform product로 간주하지 않습니다.
- `gitops/platform`, `gitops/apps/systems`, `gitops/apps/workloads`:
ownership별 base; 환경 중립은 목표 contract
- `gitops/clusters/<cluster>`: GitOps controller가 읽는 cluster root
- `gitops/clusters/<cluster>/overlays`: cluster-specific final composition
- `gitops/platform/control-plane/argocd/projects`: Argo 권한 경계
- `gitops/platform/control-plane/argocd/application-sets`: reconciliation inventory
- `infrastructure`: Kubernetes manifest와 분리된 external API IaC
- `bootstrap`: controller가 존재하기 전의 최소 seed
`gitops/clusters`가 배포 가능한 최종 상태를 소유합니다. Argo CD는
catalog 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은 유지하지 않습니다.
+33
View File
@@ -0,0 +1,33 @@
# ADR 0006: Gateway API first, Istio deferred
Status: accepted
현재 단일 노드 K3s와 auth/api 중심 workload에는 service mesh 운영 비용을
정당화할 mTLS identity, L7 authorization, canary traffic policy 또는
multi-team 요구가 없다. 이번 개편에는 Istio를 설치하지 않는다.
선행 작업:
1. Traefik Gateway API provider와 GatewayClass 검증
2. Ingress를 Gateway/HTTPRoute로 이관
3. north-south TLS
4. 내부 호출의 ingress hairpin 제거
5. Vault/PostgreSQL native TLS
6. NetworkPolicy regression test와 observability/SLO
Istio 요구가 실제화되면 sidecar가 아니라 ambient mode로 제한 pilot한다.
초기 범위는 api-server와 auth-server이며 Vault, Vault injector,
PostgreSQL은 제외한다. ztunnel L4부터 시작하고 L7 정책이 필요할 때만
waypoint를 추가한다.
다음 기능 요구 중 두 개 이상과 운영 선행조건이 모두 충족될 때 ADR을
재검토한다.
- ServiceAccount identity 기반 east-west mTLS
- path/JWT 기반 L7 authorization
- canary traffic split/retry/timeout/outlier detection
- 지속적인 서비스·namespace·팀 증가
- application instrumentation만으로 해결하기 어려운 장애 분석
현재 Ingress를 즉시 제거하지 않는다. TLS, DNS, GatewayClass 계약이
확정되기 전 가상의 Gateway 설정을 배포하지 않기 위함이다.
@@ -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를 다음 소유권으로 분류합니다.
- `gitops/platform`: 여러 system이 사용할 수 있고 독립 lifecycle을 가진
cluster capability
- `gitops/apps/systems`: 특정 bounded context가 소유하는 backing services와
domain configuration
- `gitops/apps/workloads`: 별도 source repository와 release digest를 가진
first-party 실행 애플리케이션
- `gitops/clusters/<cluster>/overlays`: 위 base에 namespace, image, host,
secret reference, network boundary를 결합한 최종 구성
Vault는 `gitops/platform/shared-services/vault`에 둡니다. Sealed Secrets와 Vault
Agent Injector는 cluster addon inventory로 관리합니다. PostgreSQL,
Keycloak, realm/client sync는 Project Auth 전용이므로
`gitops/apps/systems/auth-system`으로 이동합니다. `auth-server`
`api-server``gitops/apps/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`를 따른다.
@@ -0,0 +1,45 @@
# ADR 0008: Adopt the Kubernetes template lifecycle layout
Status: accepted
Date: 2026-07-26
## Context
기존 저장소는 `clusters`, `platform`, `systems`, `workloads`, `iac/terraform`,
`policies``hack`을 각각 최상위에 두었습니다. 리소스 소유권은 구분했지만
bootstrap, infrastructure, GitOps desired state의 수명주기 경계가 저장소
최상위에서 일관되게 드러나지 않았습니다.
## Decision
`k8s-template`의 수명주기 구조를 canonical repository layout으로 채택합니다.
- Argo CD 최초 seed는 `bootstrap/gitops/argocd`에 둡니다.
- Vault Terraform component와 실행 root는 각각
`infrastructure/components``infrastructure/live`에 둡니다.
- Kubernetes desired state는 `gitops/platform`, `gitops/apps`,
`gitops/clusters` 아래에만 둡니다.
- 기존 system/workload 소유권 분류는 `gitops/apps/systems`
`gitops/apps/workloads` 하위에서 유지합니다.
- 환경별 Vault ACL은 이를 소비하는 `infrastructure/live` state root의
`policies`에 함께 둡니다. Kubernetes admission 정책용
`gitops/policies`와 혼합하지 않습니다.
- `gitops/clusters/dev-k3s`를 Argo CD bootstrap root의 유일한 source로
사용하고, 이 root가 permission-scoped ApplicationSet control plane을
조립합니다.
- 공통 `_template`, 예제, 구조/보안 검증을 유지하고 프로젝트별 검증을 그
위에 추가합니다.
## Consequences
- 최상위 경로만으로 bootstrap, infrastructure, GitOps lifecycle을 구분할
수 있습니다.
- 기존 AppProject, ApplicationSet, `autoSync` gate와 세 Vault state의
소유권은 유지됩니다.
- backend 예시는 각 live root에, Vault ACL은 소비 state에 가까이 위치합니다.
- Argo CD source path와 Terraform module source가 변경되므로 이미 연결된
live 시스템의 이관은 별도 diff, orphan/prune 검토와 승인 없이 실행하지
않습니다.
- 이 ADR의 구현은 Git 작업 트리에서만 수행하며 live cluster, Vault,
Terraform backend를 변경하지 않습니다.
+26
View File
@@ -0,0 +1,26 @@
# Architecture Decision Records
프로젝트의 장기 구조에 영향을 주는 선택은 ADR로 남깁니다.
파일명은 `NNNN-kebab-case-title.md`를 사용하고 다음 형식을 따릅니다.
```markdown
# NNNN. 제목
- 상태: 제안 | 승인 | 폐기 | 대체
- 날짜: YYYY-MM-DD
- 결정자: 팀 또는 역할
## 배경
## 결정
## 결과
## 대안
```
기존 결정을 바꿀 때 문서를 지우지 말고 새 ADR에서 이전 ADR을 대체했다고
표시합니다.
현재 프로젝트 결정은 이 디렉터리의 `0001`부터 순서대로 관리합니다.