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
+8 -8
View File
@@ -4,16 +4,16 @@
Controller 설치 후 다음 두 bootstrap object를 순서대로 수동 seed합니다.
1. `bootstrap/argocd/control-plane-project.yaml`
2. `bootstrap/argocd/root-application.yaml`
1. `bootstrap/gitops/argocd/control-plane-project.yaml`
2. `bootstrap/gitops/argocd/root-application.yaml`
`gitops-control-plane` AppProject는 canonical Gitea repository와 in-cluster
`argocd` namespace, AppProject/ApplicationSet kind만 허용합니다. 단일 root
Application은 이 Project를 사용하고 다음 control-plane 구성을 source로
사용합니다.
Application은 이 Project를 사용하고 `gitops/clusters/dev-k3s` source로
사용합니다. Cluster root는 다음 control-plane 구성을 참조합니다.
```text
platform/control-plane/argocd
gitops/platform/control-plane/argocd
├── projects
│ ├── platform-addons.yaml
│ ├── platform-services.yaml
@@ -26,7 +26,7 @@ platform/control-plane/argocd
└── workloads.yaml
```
Root는 AppProject와 ApplicationSet까지만 직접 소유합니다. 각
Cluster root는 AppProject와 ApplicationSet까지만 직접 소유합니다. 각
ApplicationSet의 list inventory가 실제 child Application을 생성합니다.
Routine 변경에 category별 root나 직접 `kubectl apply`를 추가하지 않습니다.
@@ -134,8 +134,8 @@ generator가 가장 쉽게 검토됩니다. 존재하지 않는 production이나
cluster를 위해 Matrix abstraction을 미리 만들지 않습니다.
두 번째 실제 cluster가 생겨 `cluster`, `server`와 cluster별 gate를 여러
component에서 반복하게 될 때 `clusters/<cluster>/config.yaml`을 Git files
generator로 읽고 component inventory와 Matrix generator로 결합합니다.
component에서 반복하게 될 때 `gitops/clusters/<cluster>/config.yaml`을 Git
files generator로 읽고 component inventory와 Matrix generator로 결합합니다.
그때도 AppProject는 ApplicationSet template에 고정하고, cluster별
`autoSync`는 quoted string과 승인 gate로 유지합니다.
+8 -6
View File
@@ -25,14 +25,16 @@ registry이며 desired state source가 아닙니다.
| Layer | 역할 |
|---|---|
| `platform/control-plane/argocd` | AppProject와 ApplicationSet control plane |
| `platform/shared-services/*/base` | 환경 중립을 목표로 하는 공유 cluster service base |
| `systems/*/base` | 환경 중립을 목표로 하는 bounded-context backing system base |
| `workloads/*/base` | first-party 애플리케이션 base |
| `clusters/dev-k3s/overlays/*` | dev namespace, host, digest, Vault role/path, NetworkPolicy를 합친 최종 구성 |
| `gitops/clusters/dev-k3s` | bootstrap root가 읽는 유일한 cluster entrypoint |
| `gitops/platform/control-plane/argocd` | AppProject와 ApplicationSet control plane |
| `gitops/platform/shared-services/*/base` | 환경 중립을 목표로 하는 공유 cluster service base |
| `gitops/apps/systems/*/base` | 환경 중립을 목표로 하는 bounded-context backing system base |
| `gitops/apps/workloads/*/base` | first-party 애플리케이션 base |
| `gitops/clusters/dev-k3s/overlays/*` | dev namespace, host, digest, Vault role/path, NetworkPolicy를 합친 최종 구성 |
현재 concrete ownership은 Vault가 platform shared service,
PostgreSQL/Keycloak이 `systems/auth-system`, 두 서버가 workload입니다.
PostgreSQL/Keycloak이 `gitops/apps/systems/auth-system`, 두 서버가
`gitops/apps/workloads`의 workload입니다.
Argo CD Application은 base가 아니라 최종 cluster overlay만 source로
사용합니다.
+150
View File
@@ -0,0 +1,150 @@
# Repository Structure
## 설계 목표
이 구조는 특정 제품의 파일 배치보다 변경 주기와 소유권을 우선합니다.
- 한 리소스에는 한 명확한 소유자가 있다.
- 재사용 구현과 실제 배포 진입점을 분리한다.
- 소규모 구성은 선택 영역을 생략할 수 있다.
- 규모가 커져도 기존 경계를 바꾸지 않고 같은 종류의 leaf를 추가한다.
- 사람이 실행하는 명령과 CI 검증이 같은 진입점을 사용한다.
## 소유권 매트릭스
| 대상 | 소유 경로 | 직접 실행 여부 | 변경 주기 |
|---|---|---:|---|
| state backend, 초기 identity | `bootstrap/foundation` | 예 | 매우 낮음 |
| 네트워크, IAM, DNS, 클러스터 | `infrastructure/live` | 예 | 낮음 |
| 재사용 IaC 단위 | `infrastructure/components` | 아니요 | 중간 |
| 재사용 IaC 조합 | `infrastructure/stacks` | 아니요 | 중간 |
| GitOps 컨트롤러와 root 연결 | `bootstrap/gitops` | 예 | 낮음 |
| 클러스터 desired state | `gitops/clusters` | reconcile 진입점 | 지속적 |
| cluster-wide addon | `gitops/platform` | 아니요 | 중간 |
| 애플리케이션 배포 정의 | `gitops/apps` | 아니요 | 높음 |
| 정책과 tenant 정의 | `gitops/policies`, `gitops/tenants` | 아니요 | 중간 |
Catalog 영역(`components`, `stacks`, `platform`, `policies`, `tenants`, `apps`)은
직접 배포하지 않습니다. 실제 진입점이 필요한 항목만 조합해서 참조합니다.
## 의존 방향
```text
bootstrap/foundation
│ output
infrastructure/components ◀── infrastructure/stacks
▲ ▲
└──────── infrastructure/live ─┘
│ cluster endpoint/identity
bootstrap/gitops
│ root reference
platform ─┐
policies ─┼────────▶ gitops/clusters
tenants ─┤
apps ─┘
```
역방향 의존은 만들지 않습니다. 예를 들어 reusable component가 특정
`live/prod` 값을 읽거나, app base가 특정 cluster overlay를 참조하면 안 됩니다.
## Infrastructure 경계
### `components`
네트워크, identity, registry, Kubernetes cluster처럼 작고 응집된 재사용
단위입니다. Terraform/OpenTofu를 선택했다면 일반적으로 backend가 없는 child
module에 해당합니다.
### `stacks`
여러 component를 반복해서 같은 방식으로 조합할 때만 사용합니다. 작은 프로젝트는
이 계층 없이 `live`가 component를 직접 호출할 수 있습니다. stack이 다른 stack을
깊게 중첩하기보다는 live root에서 평평하게 조합하는 방식을 권장합니다.
### `live`
실제로 plan/apply하는 root입니다. leaf 하나는 다음을 만족해야 합니다.
- 독립된 state와 locking
- 명시적인 provider와 backend 설정
- 고정된 component/module/chart 버전
- 비밀이 아닌 환경 입력만 저장소에 커밋
- 출력값과 downstream contract 문서화
작은 구성은 `live/dev/cluster`로 충분합니다. 계정과 리전이 늘어나면
`live/<provider>/<account>/<region>/<environment>/<stack>`처럼 경로를 확장합니다.
자동화는 경로의 고정 깊이에 의존하지 말고 실행 가능한 root 파일을 기준으로
대상을 찾도록 작성합니다.
서로 다른 환경의 root가 상대 경로로 다른 환경 구현을 import하면 안 됩니다.
공유가 필요하면 versioned component나 명시적인 remote output/data contract를
사용합니다.
## GitOps 경계
### `clusters`
클러스터가 reconcile하는 유일한 진입점입니다. 공통 리소스를 복사하지 않고
platform, policy, tenant, app catalog에서 필요한 항목만 참조합니다.
작은 구성은 `clusters/dev/main`, 다중 리전 구성은
`clusters/<environment>/<region>/<cluster>` 형태를 사용할 수 있습니다. 여기에도
고정된 경로 깊이를 강제하지 않습니다.
### `platform`
cluster-wide controller와 addon을 둡니다. 예시는 다음과 같습니다.
- ingress/gateway, external DNS, certificate
- storage class/CSI, autoscaling
- metrics, logs, traces, alerting
- secret operator와 delivery controller
각 component는 `base`와 필요한 `overlays`를 같은 디렉터리 안에 응집시킵니다.
환경 차이는 전체 파일 복사 대신 Kustomize patch 또는 별도 values로 표현합니다.
### `policies`, `tenants`, `apps`
- `policies`: cluster-wide admission 규칙, 거버넌스와 예외
- `tenants`: 구체적인 namespace, RBAC, quota, limit range, NetworkPolicy
- `apps`: application source code가 아닌 배포 정의
애플리케이션 팀이 별도 저장소를 소유하면 `apps`에는 그 저장소/OCI artifact를
참조하는 GitOps 리소스만 둘 수 있습니다.
Cloud DNS zone/delegation과 cloud IAM role은 `infrastructure`가 소유합니다.
External DNS controller, 동적 record 요청과 Kubernetes ServiceAccount binding은
`gitops/platform`이 소유합니다. 두 계층 사이에는 zone ID, role ARN 같은
명시적인 output contract만 전달합니다.
## Bootstrap 경계
Bootstrap은 선언형 관리가 스스로 시작될 수 없는 최소 범위만 담당합니다.
- `foundation`: state backend, 최초 CI identity와 같은 선행 조건
- `gitops`: Flux 또는 Argo CD 중 선택한 컨트롤러 설치와 root reference
ingress, cert-manager, observability 같은 addon은 bootstrap이 아니라 GitOps가
소유합니다. bootstrap 이후의 변경을 계속 수동 명령으로 누적하지 않습니다.
## 규모 확장 기준
| 단계 | 추가하는 것 | 그대로 유지하는 것 |
|---|---|---|
| 소형 | 단일 live root, 단일 cluster root, 최소 platform | lifecycle/ownership 경계 |
| 중형 | reusable stack, staging/prod, 정책, 관측성 | component와 entrypoint 분리 |
| 대형 | 계정·리전별 state, 다중 cluster, tenants, CODEOWNERS | 한 리소스 한 소유자 |
| 조직 분리 | lifecycle/team별 repository 분리 가능 | 각 repository 내부의 동일한 계약 |
repository를 분리하는 시점은 폴더 수가 아니라 권한, 배포 주기와 소유 팀이
실제로 달라졌을 때입니다.
## 설계 참고 자료
- [Kubernetes: Kustomize를 이용한 선언형 객체 관리](https://kubernetes.io/docs/tasks/manage-kubernetes-objects/kustomization/)
- [Flux: GitOps repository 구조](https://fluxcd.io/flux/guides/repository-structure/)
- [OpenTofu: reusable module](https://opentofu.org/docs/language/modules/)
- [OpenTofu: 평평한 module composition](https://opentofu.org/docs/language/modules/develop/composition/)
+33 -31
View File
@@ -8,11 +8,11 @@
| 분류 | 판단 질문 | 현재 예 |
|---|---|---|
| `platform` | GitOps control plane이거나, 둘 이상의 system이 독립 lifecycle로 소비할 cluster capability인가? | Argo inventory, Vault shared service |
| `systems` | 하나의 bounded context가 함께 소유하는 backing system인가? | Project Auth의 PostgreSQL, Keycloak, realm/client sync |
| `workloads` | 별도 source repository에서 빌드하는 first-party 실행 단위인가? | `auth-server`, `api-server` |
| `clusters` | 특정 클러스터의 최종 composition 값인가? | namespace, host, digest, Vault role, NetworkPolicy |
| `iac` | Kubernetes가 아닌 외부 API 객체를 선언하는가? | Vault mounts, policies, auth roles, database roles |
| `gitops/platform` | GitOps control plane이거나, 둘 이상의 system이 독립 lifecycle로 소비할 cluster capability인가? | Argo inventory, Vault shared service |
| `gitops/apps/systems` | 하나의 bounded context가 함께 소유하는 backing system인가? | Project Auth의 PostgreSQL, Keycloak, realm/client sync |
| `gitops/apps/workloads` | 별도 source repository에서 빌드하는 first-party 실행 단위인가? | `auth-server`, `api-server` |
| `gitops/clusters` | 특정 클러스터의 최종 composition 값인가? | namespace, host, digest, Vault role, NetworkPolicy |
| `infrastructure` | Kubernetes가 아닌 외부 API 객체를 선언하는가? | Vault mounts, policies, auth roles, database roles |
| `bootstrap` | GitOps controller가 존재하기 전에 필요한 최소 seed인가? | Argo CD 설치 버전, 제한된 control-plane AppProject와 root Application |
다음 세 질문을 순서대로 사용합니다.
@@ -23,9 +23,10 @@
제품 이름만으로 분류하지 않습니다. 예를 들어 Keycloak이 여러 system의
공용 identity service가 되고 별도 owner와 release cadence를 갖게 되면
실행 서비스는 `platform/`으로 이동할 수 있습니다. 그래도 Project Auth
realm/client 구성은 `systems/auth-system/`에 남습니다. 현재 Keycloak과
PostgreSQL은 Project Auth 전용이므로 모두 system 소유입니다.
실행 서비스는 `gitops/platform/`으로 이동할 수 있습니다. 그래도 Project
Auth realm/client 구성은 `gitops/apps/systems/auth-system/`에 남습니다.
현재 Keycloak과 PostgreSQL은 Project Auth 전용이므로 모두 system
소유입니다.
## Path contract
@@ -33,16 +34,16 @@ PostgreSQL은 Project Auth 전용이므로 모두 system 소유입니다.
contract입니다.
```text
platform/shared-services/<name>/base
systems/<system>/base
workloads/<workload>/base
gitops/platform/shared-services/<name>/base
gitops/apps/systems/<system>/base
gitops/apps/workloads/<workload>/base
clusters/<cluster>/overlays/platform/<name>
clusters/<cluster>/overlays/systems/<system>
clusters/<cluster>/overlays/workloads/<workload>
gitops/clusters/<cluster>/overlays/platform/<name>
gitops/clusters/<cluster>/overlays/systems/<system>
gitops/clusters/<cluster>/overlays/workloads/<workload>
platform/control-plane/argocd/projects
platform/control-plane/argocd/application-sets
gitops/platform/control-plane/argocd/projects
gitops/platform/control-plane/argocd/application-sets
```
Base에는 재사용 가능한 workload 구조, Service, ServiceAccount와 기본
@@ -68,8 +69,9 @@ Base 내부의 PostgreSQL·Keycloak 참조는 namespace를 포함하지 않은
Service DNS를 사용하므로 overlay namespace에 재사용할 수 있습니다. 다만
아직 다음 dev/single-node 가정은 남아 있습니다.
- `systems/auth-system/base`의 Keycloak 실행 command가 `start-dev`입니다.
- `platform/shared-services/vault/base/files/vault/vault.hcl`
- `gitops/apps/systems/auth-system/base`의 Keycloak 실행 command가
`start-dev`입니다.
- `gitops/platform/shared-services/vault/base/files/vault/vault.hcl`
`tls_disable = 1`과 고정된 single-node `node_id`를 사용합니다.
이는 숨겨진 환경 중립성이 아니라 명시적인 리팩터링 부채입니다. 두 번째
@@ -88,14 +90,14 @@ base -> dev-k3s overlay -> ApplicationSet inventory -> generated Application
- Cluster addon: Kubernetes API를 확장하거나 admission/control-plane
기능을 제공하는 외부 chart. 현재 Sealed Secrets와 Vault Agent Injector가
해당합니다. Inventory는
`platform/control-plane/argocd/application-sets/platform-addons.yaml`
`gitops/platform/control-plane/argocd/application-sets/platform-addons.yaml`
둡니다.
- Shared service: 일반 workload처럼 namespace에서 실행되지만 여러 system이
사용할 수 있는 capability. 현재 Vault가 해당합니다.
외부 Helm chart를 복사해 base처럼 유지하지 않습니다. chart version과
values는 Argo inventory에서 pin합니다. 저장소가 직접 소유하는 shared
service manifest만 `platform/shared-services/`에 둡니다.
service manifest만 `gitops/platform/shared-services/`에 둡니다.
`foundation`은 소유권 분류가 아닙니다. Bootstrap 때 먼저 필요하다는 뜻은
분리된 ApplicationSet category, `autoSync` gate와 runbook 순서로
@@ -104,16 +106,16 @@ service manifest만 `platform/shared-services/`에 둡니다.
## System와 workload의 경계
`systems/auth-system`은 인증 bounded context가 함께 책임지는 데이터와
identity backing services입니다.
`gitops/apps/systems/auth-system`은 인증 bounded context가 함께 책임지는
데이터와 identity backing services입니다.
- PostgreSQL StatefulSet와 초기 database contract
- Keycloak server와 Project Auth realm
- Keycloak client synchronization
`workloads/auth-server``workloads/api-server` 각각 별도 source
repository와 release digest가 있는 애플리케이션입니다. Workload가
auth-system을 사용하더라도 두 lifecycle을 합치지 않습니다.
`gitops/apps/workloads/auth-server``gitops/apps/workloads/api-server`
각각 별도 source repository와 release digest가 있는 애플리케이션입니다.
Workload가 auth-system을 사용하더라도 두 lifecycle을 합치지 않습니다.
Dev namespace도 소유권을 드러냅니다.
@@ -145,12 +147,12 @@ state가 아닙니다.
| 변경 | 위치 |
|---|---|
| 또 다른 공용 admission controller | `platform/control-plane/argocd/application-sets/platform-addons.yaml` |
| 공용 object storage service base | `platform/shared-services/object-storage/base` |
| Project Auth 전용 Redis | `systems/auth-system/base` |
| 새 first-party worker | `workloads/<worker>/base` |
| dev worker digest/secret annotation | `clusters/dev-k3s/overlays/workloads/<worker>` |
| Vault workload policy/role | `vault-workloads` Terraform state와 `policies/vault/` |
| 또 다른 공용 admission controller | `gitops/platform/control-plane/argocd/application-sets/platform-addons.yaml` |
| 공용 object storage service base | `gitops/platform/shared-services/object-storage/base` |
| Project Auth 전용 Redis | `gitops/apps/systems/auth-system/base` |
| 새 first-party worker | `gitops/apps/workloads/<worker>/base` |
| dev worker digest/secret annotation | `gitops/clusters/dev-k3s/overlays/workloads/<worker>` |
| Vault workload policy/role | `infrastructure/live/dev-k3s/vault-workloads`와 그 아래 `policies/` |
| Vault auth backend | `vault-foundation` Terraform state |
분류가 애매하면 설치 순서가 아니라 owner와 소비자 경계를 ADR에 먼저
+4 -4
View File
@@ -77,10 +77,10 @@ destroy/backup 책임이 정해졌을 때만 다음처럼 별도 machine root를
추가합니다.
```text
iac/terraform/live/<cluster>/machine
iac/terraform/live/<cluster>/vault-foundation
iac/terraform/live/<cluster>/vault-workloads
iac/terraform/live/<cluster>/vault-database
infrastructure/live/<cluster>/machine
infrastructure/live/<cluster>/vault-foundation
infrastructure/live/<cluster>/vault-workloads
infrastructure/live/<cluster>/vault-database
```
Machine root output을 읽기 위해 Vault state 전체를 공유하지 않습니다.
@@ -0,0 +1,8 @@
# Legacy policies
These policies were used by permanent AppRole credentials and could modify
mounts, auth backends and their own policies. They are retained only to make
the state and credential migration auditable.
No current Terraform root references files in this directory. Revoke the
legacy AppRoles after the JWT/Kubernetes-authenticated workflow is verified.
@@ -0,0 +1,59 @@
path "transit/*" {
capabilities = ["create", "read", "update", "delete", "list"]
}
path "kv/data/dev/workload/*" {
capabilities = ["create", "read", "update", "delete", "list"]
}
path "kv/metadata/dev/workload/*" {
capabilities = ["read", "delete", "list"]
}
path "sys/internal/ui/mounts/*" {
capabilities = ["read"]
}
path "sys/mounts/*" {
capabilities = ["create", "read", "update", "delete", "list", "sudo"]
}
path "sys/auth/*" {
capabilities = ["create", "read", "update", "delete", "list", "sudo"]
}
path "sys/policies/acl/*" {
capabilities = ["create", "read", "update", "delete", "list"]
}
path "auth/approle/*" {
capabilities = ["create", "read", "update", "delete", "list"]
}
path "auth/token/create" {
capabilities = ["update"]
}
path "auth/token/create-orphan" {
capabilities = ["update"]
}
path "auth/token/revoke" {
capabilities = ["update"]
}
path "auth/token/revoke-accessor" {
capabilities = ["update"]
}
path "auth/token/lookup" {
capabilities = ["update"]
}
path "auth/token/lookup-accessor" {
capabilities = ["update"]
}
path "auth/token/lookup-self" {
capabilities = ["read"]
}
@@ -0,0 +1,71 @@
path "kv/data/dev/platform/postgres/superuser" {
capabilities = ["create", "read", "update", "delete", "list"]
}
path "kv/metadata/dev/platform/postgres/superuser" {
capabilities = ["read", "delete", "list"]
}
path "kv/data/dev/platform/postgres/auth-server" {
capabilities = ["create", "read", "update", "delete", "list"]
}
path "kv/metadata/dev/platform/postgres/auth-server" {
capabilities = ["read", "delete", "list"]
}
path "kv/data/dev/platform/postgres/keycloak" {
capabilities = ["create", "read", "update", "delete", "list"]
}
path "kv/metadata/dev/platform/postgres/keycloak" {
capabilities = ["read", "delete", "list"]
}
path "kv/data/dev/platform/keycloak/bootstrap-admin" {
capabilities = ["create", "read", "update", "delete", "list"]
}
path "kv/metadata/dev/platform/keycloak/bootstrap-admin" {
capabilities = ["read", "delete", "list"]
}
path "kv/data/dev/platform/keycloak/client-auth-server" {
capabilities = ["create", "read", "update", "delete", "list"]
}
path "kv/metadata/dev/platform/keycloak/client-auth-server" {
capabilities = ["read", "delete", "list"]
}
path "auth/kubernetes/*" {
capabilities = ["create", "read", "update", "delete", "list"]
}
path "auth/approle/*" {
capabilities = ["create", "read", "update", "delete", "list"]
}
path "sys/auth/*" {
capabilities = ["create", "read", "update", "delete", "list", "sudo"]
}
path "sys/mounts/*" {
capabilities = ["create", "read", "update", "delete", "list", "sudo"]
}
path "sys/policies/acl/*" {
capabilities = ["create", "read", "update", "delete", "list"]
}
path "database/config/*" {
capabilities = ["create", "read", "update", "delete", "list"]
}
path "database/roles/*" {
capabilities = ["create", "read", "update", "delete", "list"]
}
path "transit/*" {
capabilities = ["create", "read", "update", "delete", "list"]
}
@@ -6,7 +6,8 @@ Updated: 2026-07-26
Argo CD 설치 후 bootstrap 전용 `gitops-control-plane` AppProject와 단일
root Application을 순서대로 수동 seed합니다. Root는
`platform/control-plane/argocd`의 AppProject와 ApplicationSet을 소유하고,
`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만 허용합니다.
@@ -9,16 +9,17 @@ 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
- `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
`clusters`가 배포 가능한 최종 상태를 소유합니다. Argo CD는 top-level
base를 직접 source로 사용하지 않습니다. `foundation`은 directory
`gitops/clusters`가 배포 가능한 최종 상태를 소유합니다. Argo CD는
catalog base를 직접 source로 사용하지 않습니다. `foundation`은 directory
taxonomy가 아니라 bootstrap ordering/stage이고, 구체적인 ownership 분류는
ADR 0007을 따릅니다.
@@ -21,20 +21,20 @@ Auth를 예제로 한 독립 GitOps reference lab입니다. 따라서 존재하
최상위 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를 결합한 최종 구성
- `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는 `platform/shared-services/vault`에 둡니다. Sealed Secrets와 Vault
Vault는 `gitops/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`에 유지합니다.
`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를
@@ -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`부터 순서대로 관리합니다.
+146
View File
@@ -0,0 +1,146 @@
# Getting Started
이 문서는 이 저장소가 채택한 공통 템플릿 구조와 새 catalog 항목 생성 규칙을
설명합니다. Project GitOps의 실제 입문 순서와 운영 gate는
[`intern-guide.md`](intern-guide.md)와 runbook을 우선합니다.
## 예제를 먼저 확인하기
실제 파일을 만들기 전에 두 예제를 설계 참고 자료로 사용합니다.
1. `examples/minimal`에서 단일 환경의 canonical 폴더와 조립 방식을 확인합니다.
2. 다음 명령으로 platform과 app이 cluster root에서 합쳐지는 결과를 확인합니다.
```bash
kubectl kustomize examples/minimal/gitops/clusters/dev/main
```
3. 다중 계정·리전·클러스터가 필요하면 `examples/scaled/README.md`에서 경로와
state 분리 기준을 선택합니다.
4. 실제 파일은 `examples`에서 복사하지 않고 각 책임 폴더의 `_template`에서
생성합니다.
```text
examples ──▶ 구조 선택 ──▶ _template 복사 ──▶ live/clusters 구현
```
## 1. 프로젝트 선택 기록
구현을 추가하기 전에 다음 항목을 결정하고 `docs/decisions`에 ADR을 작성합니다.
- cloud/on-prem provider와 account/project 구조
- Terraform, OpenTofu, Pulumi 등 IaC 엔진
- Kustomize 중심 또는 Helm 사용 범위
- Flux 또는 Argo CD 등 GitOps 컨트롤러
- External Secrets 또는 SOPS 등 비밀 관리 방식
- admission policy와 observability 운영 범위
선택하지 않은 도구의 빈 폴더를 모두 만들 필요는 없습니다.
Terraform/OpenTofu를 선택했다면 IaC 파일을 추가하기 전에 다음 선택 파일을
만들고 한 값만 활성화합니다.
```bash
cp infrastructure/.iac-engine.example infrastructure/.iac-engine
```
`.iac-engine`에는 주석을 제외하고 `tofu` 또는 `terraform` 한 줄만 남깁니다.
선택한 도구와 version을 CI에도 설치·고정하고 project-specific
`init -backend=false`/`validate` 검사를 추가합니다.
## 2. 프로젝트 메타데이터 설정
1. `README.md`의 제목과 프로젝트 범위를 바꿉니다.
2. `.github/CODEOWNERS.example`을 실제 소유자로 수정한 뒤 `CODEOWNERS`로
이름을 바꿉니다.
3. `SECURITY.md`에 조직의 보안 연락처와 SLA를 추가합니다.
4. 선택한 도구 버전을 프로젝트의 버전 관리 방식으로 고정합니다.
5. branch protection과 required check를 설정합니다.
## 3. Foundation bootstrap
`bootstrap/foundation` 아래에 remote state, locking, 초기 CI identity 등
다른 인프라가 의존하는 최소 구성을 작성합니다.
Foundation은 일반 infrastructure state와 분리하고 변경 권한을 좁게 유지합니다.
이미 조직 공통 foundation이 있다면 이 폴더에는 외부 의존 계약과 초기화 방법만
문서화해도 됩니다.
## 4. Infrastructure 작성
작은 프로젝트는 component와 live root만으로 시작합니다.
```bash
cp -R infrastructure/components/_template infrastructure/components/kubernetes-cluster
mkdir -p infrastructure/live/dev
cp -R infrastructure/live/_template infrastructure/live/dev/cluster
```
동일한 조합이 여러 환경에서 반복될 때만 stack을 추가합니다.
```bash
cp -R infrastructure/stacks/_template infrastructure/stacks/cluster
```
`live` root마다 backend/state를 분리하고, provider credential은 파일에 저장하지
않습니다.
## 5. Desired state 조립
필요한 catalog 템플릿을 복사합니다.
```bash
cp -R gitops/platform/_template gitops/platform/core
cp -R gitops/apps/_template gitops/apps/example-api
mkdir -p gitops/clusters/dev
cp -R gitops/clusters/_template gitops/clusters/dev/main
```
component의 `base`에 공통값을 두고, 환경 차이가 있을 때만 overlay를 추가합니다.
마지막으로 cluster `kustomization.yaml`이 사용할 component를 참조하게 합니다.
복사된 README의 `__REPLACE_ME_*__` 값을 모두 실제 메타데이터로 바꿉니다.
controller에 연결하기 전에 실제 cluster root를 로컬에서 렌더해 확인합니다.
## 6. GitOps bootstrap
desired-state root가 준비되고 클러스터가 생성되면 `bootstrap/gitops`에서 GitOps
컨트롤러 하나를 선택해 설치합니다. 이 단계에는 다음만 포함합니다.
- controller 설치 또는 설치 선언
- repository/OCI source 연결
- 검증된 `gitops/clusters/<...>` root reconcile 연결
- controller가 secret manager에 접근하는 최소 identity
일반 platform addon과 application은 이 단계에 넣지 않습니다.
## 7. 검증
```bash
make doctor
make check
kubectl kustomize examples/minimal/gitops/clusters/dev/main
kubectl kustomize gitops/clusters/dev/main
```
프로젝트에서 실제 IaC, Helm, policy 파일을 추가하면 필요한 validator를
`scripts/validate.sh`에 명시적으로 추가하고 CI에서도 같은 `make check`를
호출합니다. 도구가 없을 때 조용히 성공하도록 만들지 않습니다.
첫 환경은 다음 조건을 모두 만족하면 완료된 것으로 봅니다.
- 실제 `live` root의 대상, state, owner와 실행 절차가 작성되어 있다.
- 실제 cluster root가 필요한 catalog base/overlay를 참조하고 비어 있지 않다.
- cluster root 렌더 결과와 IaC plan이 리뷰 가능하다.
- GitOps bootstrap root가 `_template`이 아닌 실제 cluster root를 가리킨다.
- 비밀 관리, rollback과 담당자 연락 경로가 문서화되어 있다.
## 8. 운영 준비
- production apply 승인 및 concurrency lock
- backup/restore와 disaster recovery runbook
- cluster와 addon upgrade 정책
- secret rotation과 접근 감사
- alert routing과 담당자
- 비용, 용량, SLO 기준
운영 준비가 끝나기 전에는 예제 값을 production에 재사용하지 않습니다.
+117
View File
@@ -0,0 +1,117 @@
# Intern guide
## 이 저장소의 역할
이 저장소는 Project Auth를 예제로 한 독립 GitOps reference lab입니다.
애플리케이션 소스나 범용 production platform이 아닙니다. 현재 지원하는
환경은 `dev-k3s` 하나입니다.
서로 다른 세 reconciliation 경계를 먼저 구분합니다.
1. Gitea `main`이 승인된 desired-state revision을 저장합니다.
2. Argo CD가 그 revision의 Kubernetes 리소스를 지속적으로 맞춥니다.
3. Terraform이 승인된 실행 환경에서 Vault API 객체를 관리합니다.
GHCR은 빌드된 image를 보관할 뿐 desired-state source가 아닙니다. Argo
CD가 Terraform을 실행하지 않으며 CI가 routine deployment를 위해
`kubectl apply`를 호출하지 않습니다. Secret 값도 Git이나 Terraform을
통과하지 않습니다.
## 디렉터리를 고르는 법
- 여러 system이 공유하는 cluster capability: `gitops/platform`
- Project Auth bounded context 전용 backing service:
`gitops/apps/systems/auth-system`
- 별도 source repository에서 빌드하는 서버: `gitops/apps/workloads`
- Dev namespace, digest, host, Vault annotation: `gitops/clusters/dev-k3s/overlays`
- Vault API 객체: `infrastructure/components``infrastructure/live`
현재 Vault는 platform shared service, PostgreSQL과 Keycloak은 auth-system,
`auth-server``api-server`는 workload입니다. 설치 순서나 중요도로
`foundation`을 만들지 않습니다. 자세한 기준은
`docs/architecture/repository-taxonomy.md`에 있습니다.
## 안전한 변경 흐름
1. `refactor/...`, `feat/...`, `fix/...` branch에서 변경합니다.
2. `make validate`를 실행합니다.
3. Rendered manifest 또는 Terraform plan을 검토합니다.
4. 내부 Gitea에 PR을 생성합니다.
5. 승인 후 `main`에 merge합니다.
6. Kubernetes 변경은 열린 `autoSync` gate에서 Argo CD가 반영합니다.
7. Terraform 변경은 해당 state identity로 별도 승인 후 실행합니다.
새 ApplicationSet element는 기본적으로 `autoSync: "false"`로 추가합니다.
선행 controller, Vault 구성, secret과 database 준비를 확인한 별도 PR에서
gate를 엽니다. Gate가 닫혀도 수동 Sync는 가능하므로 임의로 누르지
않습니다.
금지 사항:
- `.terraform`, state, plan, tfvars, Vault init JSON, token commit
- 동일 Vault path/resource를 두 state에서 관리
- Secret payload를 Terraform resource/data source로 관리
- Image promotion workflow의 `main` 직접 push
- Routine CI 또는 사람의 직접 `kubectl apply`
- Production skeleton이나 이름뿐인 production Application 추가
- Hook을 사용하는 Application에 `ApplyOutOfSyncOnly=true` 적용
- `autoSync: "true"` 전환 PR에서 누적 live diff를 확인하지 않음
## 자주 쓰는 읽기 전용 명령
최종 dev manifest 렌더링:
```bash
kubectl kustomize gitops/clusters/dev-k3s/overlays/workloads/auth-server
kubectl kustomize gitops/clusters/dev-k3s/overlays/systems/auth-system
kubectl kustomize gitops/clusters/dev-k3s/overlays/platform/vault
```
전체 정적 검증:
```bash
make validate
```
Backend 없이 Terraform configuration 검증:
```bash
for terraform_root in vault-foundation vault-workloads vault-database; do
terraform_data_dir="$(mktemp -d)"
TF_DATA_DIR="$terraform_data_dir" \
terraform -chdir="infrastructure/live/dev-k3s/${terraform_root}" \
init -backend=false -input=false -lockfile=readonly
TF_DATA_DIR="$terraform_data_dir" \
terraform -chdir="infrastructure/live/dev-k3s/${terraform_root}" validate
rm -rf "$terraform_data_dir"
done
```
일반적으로는 같은 검사를 포함한 `make validate`를 사용합니다. 위 예는
provider data를 repository의 `.terraform`에 남기지 않습니다.
실제 plan은 승인된 backend와 identity를 준비한 뒤 수행합니다.
```bash
make terraform-plan \
TF_ROOT=vault-workloads \
BACKEND_CONFIG=.local/terraform-backend/dev-k3s/vault-workloads.s3.hcl
```
Image 승격은 Gitea `Promote Dev Image by Pull Request` workflow에 정확한
`sha256:` digest를 전달합니다. Workflow는 전용 branch와 PR을 만들며
`main`에 직접 쓰지 않습니다.
## 읽는 순서
1. `README.md`
2. `docs/architecture/repository-taxonomy.md`
3. `docs/architecture/deployment.md`
4. `docs/architecture/argocd.md`
5. `docs/architecture/secret-trust.md`
6. `docs/architecture/terraform.md`
7. `docs/decisions/`
8. 수행하려는 작업의 runbook
2026-07-26 리팩터링은 repository에서만 구현·검증했으며 실제 cluster에
적용하지 않았습니다. Live migration을 연습 과제로 실행하지 않습니다.
+16
View File
@@ -0,0 +1,16 @@
# Runbooks
운영자가 긴급 상황에서도 그대로 실행할 수 있는 절차를 둡니다. 프로젝트를
운영하기 전에 최소한 다음 runbook을 준비합니다.
- foundation/state 접근 복구
- 실패한 plan/apply 복구와 state lock 처리
- GitOps controller 복구와 reconciliation 중지/재개
- cluster 및 핵심 addon upgrade/rollback
- secret rotation과 credential 노출 대응
- backup restore와 disaster recovery
- 인증서, DNS, ingress 장애 대응
- 관측성 또는 alert pipeline 장애 대응
각 문서는 `목적`, `사전 조건`, `영향`, `절차`, `검증`, `롤백`,
`에스컬레이션` 섹션을 포함해야 합니다.
+15
View File
@@ -0,0 +1,15 @@
# Runbook 제목
## 목적
## 사전 조건
## 영향
## 절차
## 검증
## 롤백
## 에스컬레이션
+5 -5
View File
@@ -24,11 +24,11 @@ commit하지 않습니다.
```bash
mkdir -p .local/terraform-backend/dev-k3s
cp iac/terraform/backend/dev-k3s/vault-foundation.s3.hcl.example \
cp infrastructure/live/dev-k3s/vault-foundation/backend.s3.hcl.example \
.local/terraform-backend/dev-k3s/vault-foundation.s3.hcl
cp iac/terraform/backend/dev-k3s/vault-workloads.s3.hcl.example \
cp infrastructure/live/dev-k3s/vault-workloads/backend.s3.hcl.example \
.local/terraform-backend/dev-k3s/vault-workloads.s3.hcl
cp iac/terraform/backend/dev-k3s/vault-database.s3.hcl.example \
cp infrastructure/live/dev-k3s/vault-database/backend.s3.hcl.example \
.local/terraform-backend/dev-k3s/vault-database.s3.hcl
```
@@ -78,7 +78,7 @@ kubectl -n vault port-forward deployment/vault 8200:8200
```bash
export VAULT_ADDR=http://127.0.0.1:8200
./hack/vault-init.sh init
./scripts/vault-init.sh init
```
`.local/vault/dev-k3s-init.json`을 즉시 encrypted custody에 복사합니다.
@@ -297,7 +297,7 @@ unseal custody의 담당자와 승인된 Vault generated-root recovery 절차를
확인할 수 없으면 root를 폐기하지 않습니다.
```bash
./hack/vault-init.sh revoke-root
./scripts/vault-init.sh revoke-root
VAULT_TOKEN="$VAULT_WORKLOADS_TOKEN" vault token revoke -self
VAULT_TOKEN="$VAULT_DATABASE_TOKEN" vault token revoke -self