7.0 KiB
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)은
직접 배포하지 않습니다. 실제 진입점이 필요한 항목만 조합해서 참조합니다.
의존 방향
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에서 필요한 항목만 참조합니다.
현재 프로젝트의 lab은 아직 GitOps controller가 없으므로
clusters/lab/main/namespaces와 stages/*를 bootstrap script가 순서대로 적용합니다.
clusters/lab/main/all은 reconcile root가 아닌 감사용 aggregate입니다.
작은 구성은 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, NetworkPolicyapps: 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
현재 bootstrap/gitops에는 구현이 없으며 controller 도입 전까지 lab의 staged
bootstrap이 이 역할을 대신합니다.
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를 분리하는 시점은 폴더 수가 아니라 권한, 배포 주기와 소유 팀이 실제로 달라졌을 때입니다.