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
+17
View File
@@ -0,0 +1,17 @@
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[*.{yaml,yml,json,tf,tofu,hcl}]
indent_style = space
indent_size = 2
[*.md]
trim_trailing_whitespace = false
[Makefile]
indent_style = tab
+15
View File
@@ -0,0 +1,15 @@
* text=auto eol=lf
*.sh text eol=lf
*.tf text eol=lf
*.tofu text eol=lf
*.yaml text eol=lf
*.yml text eol=lf
*.gif binary
*.ico binary
*.jpeg binary
*.jpg binary
*.pdf binary
*.png binary
*.webp binary
+2 -2
View File
@@ -52,7 +52,7 @@ jobs:
echo "image_digest must be an immutable sha256 digest" >&2
exit 1
fi
manifest="clusters/dev-k3s/overlays/workloads/${SERVICE}/kustomization.yaml"
manifest="gitops/clusters/dev-k3s/overlays/workloads/${SERVICE}/kustomization.yaml"
temporary="$(mktemp "${manifest}.XXXXXX")"
awk -v image_name="$image_name" -v image_digest="$IMAGE_DIGEST" '
$1 == "-" && $2 == "name:" { target = ($3 == image_name) }
@@ -85,7 +85,7 @@ jobs:
git config user.name "gitops-bot"
git config user.email "gitops-bot@hyeonworks.local"
git switch -c "$branch"
git add "clusters/dev-k3s/overlays/workloads/${SERVICE}/kustomization.yaml"
git add "gitops/clusters/dev-k3s/overlays/workloads/${SERVICE}/kustomization.yaml"
git commit -m "chore(gitops): promote ${SERVICE} dev digest"
git push --set-upstream origin "$branch"
echo "PROMOTION_BRANCH=$branch" >>"$GITHUB_ENV"
+1 -1
View File
@@ -20,4 +20,4 @@ jobs:
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Validate
run: ./hack/validate.sh
run: make check
+51 -7
View File
@@ -1,22 +1,66 @@
# Terraform working directories and local state
# OS and editors
.DS_Store
.idea/
.vscode/
*.swp
*.swo
*.tmp
# Local environment and credentials
.env
.env.*
!.env.example
*.key
*.pem
*.p12
*.pfx
*.jks
credentials.json
id_dsa
id_ecdsa
id_ed25519
id_rsa
# Kubernetes local access
kubeconfig
kubeconfig.*
*.kubeconfig
# Terraform / OpenTofu working directories and local state
**/.terraform/*
**/.terraform-state/*
**/.terragrunt-cache/*
*.tfstate
*.tfstate.*
*.tfplan
*.tfvars
!*.tfvars.example
plan.out
.terraform.tfstate.lock.info
crash.log
crash.*.log
override.tf
override.tf.json
*_override.tf
*_override.tf.json
*.local.tfvars
*.secret.tfvars
# Bootstrap material and local operator files
.local/*
!.local/.gitkeep
*init*.json
# Tool and editor output
.DS_Store
.idea/
.vscode/
*.swp
*.tmp
# Decrypted or generated secret material
.decrypted/
*.decrypted.yaml
*.dec.yaml
# Build, render, and test output
.build/
.cache/
dist/
rendered/
tmp/
coverage/
*.log
+4 -3
View File
@@ -7,10 +7,11 @@
Vault init JSON, unseal/recovery material or plaintext credentials.
- A Vault API object may be owned by only one Terraform state.
- Keep secret payloads outside Terraform resources and data sources.
- Classify shared capabilities under `platform`, bounded-context backing
services under `systems`, and first-party runtimes under `workloads`.
- Classify shared capabilities under `gitops/platform`, bounded-context
backing services under `gitops/apps/systems`, and first-party runtimes under
`gitops/apps/workloads`.
- Manage child Argo CD Applications through the permission-scoped
ApplicationSets in `platform/control-plane/argocd`; do not add explicit
ApplicationSets in `gitops/platform/control-plane/argocd`; do not add explicit
child Applications or use the `default` AppProject.
- Add new ApplicationSet entries with `autoSync: "false"` and open each gate
only after its documented external prerequisites have been verified.
+11
View File
@@ -2,6 +2,17 @@
## 2026-07-26
- Adopted the `k8s-template` lifecycle layout: bootstrap under
`bootstrap/gitops`, Terraform under `infrastructure`, all Kubernetes desired
state under `gitops`, automation under `scripts`, and ADRs under
`docs/decisions`.
- Made `gitops/clusters/dev-k3s` the root Application source while preserving
permission-scoped ApplicationSets and their staged `autoSync` gates.
- Co-located reusable Terraform modules in `infrastructure/components`,
backend examples with each live root, and Vault ACLs with their owning
Terraform state.
- Added the template catalogs, `_template` directories, examples, security
checks, and common validation ahead of project-specific validation.
- Clarified that this repository is an independent GitOps reference lab, not a
shared production platform or an application source monorepo.
- Reclassified Kubernetes ownership as `platform`, `systems` and `workloads`;
+32
View File
@@ -0,0 +1,32 @@
# 기여 가이드
## 변경 원칙
1. 변경 대상의 소유 디렉터리를 먼저 확인합니다.
2. 재사용 가능한 구현은 catalog 영역에, 환경별 조합과 차이만 live/cluster
진입점에 둡니다.
3. 하나의 리소스는 하나의 도구와 하나의 디렉터리만 소유합니다.
4. 운영 변경에는 영향 범위, 롤백 방법, 검증 결과를 함께 기록합니다.
5. `make check`를 통과한 변경만 리뷰를 요청합니다.
## 이름 규칙
- 폴더와 리소스: 소문자 `kebab-case`
- 환경: `dev`, `staging`, `prod`처럼 조직에서 합의한 고정 어휘
- 클러스터: 환경과 위치를 식별할 수 있는 안정적인 이름
- 임시 이름, 사람 이름, 티켓 번호를 장기 리소스 이름에 사용하지 않음
## Pull request 체크리스트
- [ ] 변경이 올바른 소유권 경계에 위치한다.
- [ ] 비밀, kubeconfig, state, plan 파일이 포함되지 않았다.
- [ ] 공급자, module, chart, image 버전 변경의 영향을 확인했다.
- [ ] `make check` 결과를 확인했다.
- [ ] 운영 영향이 있으면 rollback/runbook을 갱신했다.
- [ ] 구조적 선택이 바뀌면 ADR을 추가하거나 갱신했다.
## 배포
Kubernetes routine 변경은 Git PR로만 반영합니다. Terraform apply는
`TF_ROOT`, backend config, 검토된 saved plan과 `APPROVE_APPLY`가 모두
준비된 경우에만 실행합니다. `destroy` 진입점은 제공하지 않습니다.
+37 -12
View File
@@ -1,20 +1,45 @@
.PHONY: bootstrap validate vault-init terraform-init terraform-plan terraform-apply check-terraform-inputs
SHELL := /bin/bash
.DEFAULT_GOAL := help
.PHONY: help doctor validate check tree bootstrap vault-init terraform-init terraform-plan terraform-apply check-terraform-inputs
KUBE_CONTEXT ?=
TF_ROOT ?=
BACKEND_CONFIG ?=
TF_DIR = iac/terraform/live/dev-k3s/$(TF_ROOT)
TF_DIR = infrastructure/live/dev-k3s/$(TF_ROOT)
PLAN_FILE ?= $(CURDIR)/.local/terraform-plans/dev-k3s-$(TF_ROOT).tfplan
bootstrap:
help: ## 사용 가능한 명령을 표시합니다.
@awk 'BEGIN {FS = ":.*## "; print "Usage: make <target>"} /^[a-zA-Z_-]+:.*## / {printf " %-20s %s\n", $$1, $$2}' $(MAKEFILE_LIST)
doctor: ## 현재 로컬 도구 상태를 확인합니다.
@./scripts/doctor.sh
validate: ## 템플릿 구조와 프로젝트별 GitOps/IaC 계약을 검증합니다.
@./scripts/validate.sh
check: validate ## CI와 동일한 전체 검증을 실행합니다.
tree: ## 생성물과 Git 메타데이터를 제외한 구조를 표시합니다.
@find . \
\( \
-name .build -o \
-name .cache -o \
-name .git -o \
-name .terraform -o \
-name .terragrunt-cache -o \
-name dist -o \
-name rendered -o \
-name tmp \
\) -prune -o \
-print | sort
bootstrap: ## 확인된 kube context에 Argo CD와 root Application을 설치합니다.
@test -n "$(KUBE_CONTEXT)" || (echo "KUBE_CONTEXT is required" >&2; exit 1)
./hack/bootstrap-argocd.sh --context "$(KUBE_CONTEXT)"
./scripts/bootstrap-argocd.sh --context "$(KUBE_CONTEXT)"
validate:
./hack/validate.sh
vault-init:
./hack/vault-init.sh init
vault-init: ## 개발 Vault를 최초 초기화합니다.
./scripts/vault-init.sh init
check-terraform-inputs:
@case "$(TF_ROOT)" in \
@@ -23,17 +48,17 @@ check-terraform-inputs:
esac
@test -f "$(BACKEND_CONFIG)" || (echo "BACKEND_CONFIG must point to a readable backend file" >&2; exit 1)
terraform-init: check-terraform-inputs
terraform-init: check-terraform-inputs ## 선택한 Vault Terraform root를 초기화합니다.
terraform -chdir="$(TF_DIR)" init -input=false -reconfigure -backend-config="$(abspath $(BACKEND_CONFIG))"
terraform-plan: terraform-init
terraform-plan: terraform-init ## 검토 가능한 saved Terraform plan을 만듭니다.
@mkdir -p "$(dir $(PLAN_FILE))"
@umask 077; terraform -chdir="$(TF_DIR)" plan \
-input=false \
-lock-timeout=5m \
-out="$(PLAN_FILE)"
terraform-apply: terraform-init
terraform-apply: terraform-init ## 명시적으로 승인한 saved Terraform plan을 적용합니다.
@test "$(APPROVE_APPLY)" = "dev-k3s/$(TF_ROOT)" || \
(echo "Set APPROVE_APPLY=dev-k3s/$(TF_ROOT) to continue" >&2; exit 1)
@test -f "$(PLAN_FILE)" || \
+36 -23
View File
@@ -56,41 +56,54 @@ Plugin 안에서 실행하지 않습니다.
## 저장소 분류
```text
bootstrap/argocd/ controller 버전, 제한된 AppProject와 단일 root seed
clusters/dev-k3s/
bootstrap/
foundation/ 외부 state/identity 선행 조건 문서
gitops/argocd/ controller 버전, 제한된 AppProject와 단일 root seed
infrastructure/
components/ 재사용 Vault Terraform component
live/dev-k3s/
vault-foundation/ mount/auth와 delegated automation policy
vault-workloads/ workload ACL, auth role와 Transit key
vault-database/ PostgreSQL connection과 dynamic role
gitops/clusters/dev-k3s/
kustomization.yaml Argo CD가 읽는 유일한 cluster root
overlays/
platform/ 공유 서비스의 dev-k3s 최종 구성
systems/ bounded context system의 dev-k3s 최종 구성
systems/ bounded-context system의 dev-k3s 최종 구성
workloads/ first-party workload의 dev-k3s 최종 구성
platform/
gitops/platform/
control-plane/argocd/ AppProject와 ApplicationSet inventory
shared-services/ 여러 system이 사용할 수 있는 cluster capability base
systems/ 특정 bounded context가 소유하는 backing system base
workloads/ source repository가 따로 있는 실행 애플리케이션 base
iac/terraform/
modules/ 재사용 Vault 모듈
live/dev-k3s/ vault-foundation, vault-workloads, vault-database roots
backend/dev-k3s/ secret 없는 remote backend 예시
policies/vault/ Terraform이 읽는 Vault ACL 문서
hack/ bootstrap, validation, dev Vault init entrypoint
docs/ architecture, ADR, migration/operation runbook
shared-services/ 여러 system이 는 cluster capability base
gitops/apps/
systems/ bounded-context backing system base
workloads/ 별도 source repository를 가진 application base
scripts/ bootstrap, validation, dev Vault init entrypoint
docs/ architecture, ADR, guide와 runbook
examples/, tests/ 템플릿 예제와 저장소 수준 검증 영역
```
각 catalog의 `_template`은 새 component를 만들 때 사용하는 복사 원본이며
Argo CD가 직접 reconcile하지 않습니다.
분류는 제품 종류가 아니라 소비자, 소유자, 변경 주기로 결정합니다.
Vault는 공유 capability이므로 `platform/`, Project Auth 전용 PostgreSQL과
Keycloak은 `systems/auth-system/`, 직접 빌드하는 서버는 `workloads/`
속합니다. namespace, host, image digest, Vault role, NetworkPolicy 같은
클러스터별 값은 `clusters/dev-k3s/overlays/`에서 완결합니다.
Vault는 공유 capability이므로 `gitops/platform/`, Project Auth 전용
PostgreSQL과 Keycloak은 `gitops/apps/systems/auth-system/`, 직접 빌드하는
서버는 `gitops/apps/workloads/`속합니다. namespace, host, image digest,
Vault role, NetworkPolicy 같은 클러스터별 값은
`gitops/clusters/dev-k3s/overlays/`에서 완결합니다. Vault ACL은 소비하는
Terraform state의 `policies/`에 함께 둡니다.
자세한 판단 기준은
[repository taxonomy](docs/architecture/repository-taxonomy.md)와
[ADR 0007](docs/adr/0007-repository-ownership-boundaries.md)을 따릅니다.
[ADR 0007](docs/decisions/0007-repository-ownership-boundaries.md)을 따릅니다.
## Argo CD 단계 gate
Root Application은 `platform/control-plane/argocd`의 AppProject와
ApplicationSet을 소유합니다. 각 inventory 항목은 `autoSync`를 quoted
string으로 명시해야 합니다. 새 항목이나 외부 준비 조건이 있는 항목은
Root Application은 `gitops/clusters/dev-k3s`만 source로 사용합니다. 이
cluster root가 `gitops/platform/control-plane/argocd`의 AppProject와
ApplicationSet을 조립하고, 각 ApplicationSet이 cluster overlay를
reconcile합니다. 각 inventory 항목은 `autoSync`를 quoted string으로
명시해야 합니다. 새 항목이나 외부 준비 조건이 있는 항목은
`autoSync: "false"`로 시작하고, 선행 controller, Vault 구성, runtime
secret, database 준비를 확인한 PR에서만 `autoSync: "true"`로 바꿉니다.
@@ -146,4 +159,4 @@ make terraform-plan \
- [Terraform state 이관](docs/runbooks/terraform-state-migration.md)
- [Vault backup/recovery](docs/runbooks/vault-backup-restore.md)
- [Sealed Secrets recovery](docs/runbooks/sealed-secrets-recovery.md)
- [입문 가이드](INTERN_GUIDE.md)
- [입문 가이드](docs/guides/intern-guide.md)
+46
View File
@@ -0,0 +1,46 @@
# Security Policy
## 저장소에 둘 수 없는 항목
- 평문 비밀번호, token, access key, private key
- base64로만 인코딩한 Kubernetes `Secret`
- kubeconfig와 클라우드 provider credential
- Terraform/OpenTofu state, plan, 복호화 산출물
- 실제 비밀이 포함된 `tfvars`, Helm values, `.env`
`.gitignore`는 실수 완화 장치일 뿐 보안 통제가 아닙니다. 비밀이 한 번이라도
커밋되었다면 history 삭제 여부와 관계없이 즉시 폐기하고 회전합니다.
## 허용하는 비밀 관리 방식
프로젝트마다 다음 중 하나를 ADR로 선택하고 CI와 운영 절차를 함께 정의합니다.
- External Secrets 계열 리소스로 외부 secret manager를 참조
- SOPS와 KMS/age를 사용해 암호화한 파일만 저장
- 조직에서 승인한 동등한 GitOps 비밀 관리 방식
SOPS로 암호화한 Kubernetes Secret은 검증 가능한 규칙을 위해
`*.sops.yaml`, `*.sops.yml` 또는 `*.sops.json` 이름을 사용합니다.
암호화 키, 복호화 권한과 secret manager 접근은 workload identity/OIDC와
최소 권한 원칙으로 부여합니다. 장기 cloud access key를 CI secret으로
사용하지 않습니다.
## State와 CI
- remote state는 암호화, locking, versioning을 활성화합니다.
- 환경과 독립 장애 영역은 별도 state로 분리합니다.
- production apply에는 승인, 직렬화와 감사 로그를 적용합니다.
- fork 또는 신뢰하지 않는 PR 코드에 privileged credential을 제공하지 않습니다.
- CI action, provider, module, chart와 image 버전을 검토 가능한 방식으로 고정합니다.
## 노출 사고 대응
1. 노출된 credential과 파생 token을 폐기하고 회전합니다.
2. 영향받은 시스템의 접근 로그와 변경 이력을 확인합니다.
3. 저장소 history와 artifact/cache에서 민감 데이터를 제거합니다.
4. 원인과 영향 범위, 재발 방지 조치를 incident 문서에 기록합니다.
5. 조직의 보안 연락 채널로 보고합니다.
보안 이슈는 공개 issue 대신 canonical Gitea 저장소의 비공개 보안 채널로
보고합니다. 대응 우선순위와 SLA는 운영 환경 도입 전에 별도로 승인합니다.
+11
View File
@@ -0,0 +1,11 @@
# Bootstrap
선언형 인프라와 GitOps가 스스로 동작하기 전에 한 번 또는 매우 드물게 수행하는
최소 초기화만 둡니다.
- `foundation`: remote state, locking, 최초 identity 같은 선행 조건
- `gitops`: 선택한 controller 설치와 cluster root 연결
일반 네트워크, Kubernetes cluster, addon과 application을 이곳에 두지 않습니다.
부트스트랩 절차는 반복 실행 가능하고 감사 가능해야 하며, 장기 수동 운영 경로가
되어서는 안 됩니다.
+21
View File
@@ -0,0 +1,21 @@
# Foundation Bootstrap
다른 IaC state가 의존하는 최소 선행 조건을 관리합니다.
가능한 대상:
- remote state storage와 locking
- state 암호화 key
- CI의 최초 workload identity/OIDC trust
- 조직 공통 account/project 초기 설정
## 계약
- 일반 infrastructure state와 분리합니다.
- 변경 권한과 실행 빈도를 최소화합니다.
- local state를 장기간 유지하지 않습니다.
- output과 후속 `infrastructure/live`가 값을 소비하는 방식을 문서화합니다.
- provider credential이나 실제 backend secret을 커밋하지 않습니다.
조직 공통 foundation이 외부 저장소에 이미 있다면 이 폴더에는 소유 팀, output
contract와 복구 절차만 기록합니다.
+19
View File
@@ -0,0 +1,19 @@
# GitOps Bootstrap
Flux, Argo CD 등 **하나의** GitOps controller를 선택해 설치하고 cluster root에
연결합니다. 선택하지 않은 controller의 병렬 구조를 만들지 않습니다.
포함 범위:
- controller 설치 또는 설치 manifest
- source repository/OCI 연결
- `gitops/clusters/<...>` root reconcile 선언
- controller용 최소 identity와 secret manager 접근 연결
제외 범위:
- ingress, certificate, DNS, storage, observability addon
- application workload
- controller 설치 후 Git으로 관리할 수 있는 일반 Kubernetes 리소스
bootstrap credential과 recovery 절차는 `docs/runbooks`에 문서화합니다.
@@ -8,7 +8,7 @@ spec:
source:
repoURL: https://git.learn.hyeonworks.com/donghyeon.kang/project-gitops
targetRevision: main
path: platform/control-plane/argocd
path: gitops/clusters/dev-k3s
destination:
server: https://kubernetes.default.svc
namespace: argocd
+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 전체를 공유하지 않습니다.
@@ -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에 재사용하지 않습니다.
+12 -11
View File
@@ -19,11 +19,12 @@ CD가 Terraform을 실행하지 않으며 CI가 routine deployment를 위해
## 디렉터리를 고르는 법
- 여러 system이 공유하는 cluster capability: `platform`
- Project Auth bounded context 전용 backing service: `systems/auth-system`
- 별도 source repository에서 빌드하는 서버: `workloads`
- Dev namespace, digest, host, Vault annotation: `clusters/dev-k3s/overlays`
- Vault API 객체: `iac/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입니다. 설치 순서나 중요도로
@@ -61,9 +62,9 @@ gate를 엽니다. Gate가 닫혀도 수동 Sync는 가능하므로 임의로
최종 dev manifest 렌더링:
```bash
kubectl kustomize clusters/dev-k3s/overlays/workloads/auth-server
kubectl kustomize clusters/dev-k3s/overlays/systems/auth-system
kubectl kustomize clusters/dev-k3s/overlays/platform/vault
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
```
전체 정적 검증:
@@ -78,10 +79,10 @@ Backend 없이 Terraform configuration 검증:
for terraform_root in vault-foundation vault-workloads vault-database; do
terraform_data_dir="$(mktemp -d)"
TF_DATA_DIR="$terraform_data_dir" \
terraform -chdir="iac/terraform/live/dev-k3s/${terraform_root}" \
terraform -chdir="infrastructure/live/dev-k3s/${terraform_root}" \
init -backend=false -input=false -lockfile=readonly
TF_DATA_DIR="$terraform_data_dir" \
terraform -chdir="iac/terraform/live/dev-k3s/${terraform_root}" validate
terraform -chdir="infrastructure/live/dev-k3s/${terraform_root}" validate
rm -rf "$terraform_data_dir"
done
```
@@ -109,7 +110,7 @@ Image 승격은 Gitea `Promote Dev Image by Pull Request` workflow에 정확한
4. `docs/architecture/argocd.md`
5. `docs/architecture/secret-trust.md`
6. `docs/architecture/terraform.md`
7. `docs/adr/`
7. `docs/decisions/`
8. 수행하려는 작업의 runbook
2026-07-26 리팩터링은 repository에서만 구현·검증했으며 실제 cluster에
+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
+28
View File
@@ -0,0 +1,28 @@
# Examples
예제는 구조와 조립 방식을 설명하기 위한 설계 참고 자료이며 실제 reconcile
대상이 아닙니다.
- [`minimal`](minimal/README.md): `kubectl kustomize`로 렌더 가능한 단일
환경·단일 클러스터의 canonical 구조
- [`scaled`](scaled/README.md): 여러 계정·환경·리전·클러스터로 확장할 때의
경로와 state 분리 기준
## 활용 순서
1. `minimal`에서 `infrastructure/live`, catalog `base`,
`gitops/clusters`의 관계를 확인합니다.
2. 규모가 커질 가능성이 있으면 `scaled`에서 account/region/environment
segment와 state 분리 기준을 선택합니다.
3. 실제 구현은 예제가 아니라 다음 `_template`을 복사해 시작합니다.
| 영역 | 복사 원본 |
|---|---|
| Infrastructure | `../infrastructure/components/_template`, `../infrastructure/stacks/_template`, `../infrastructure/live/_template` |
| GitOps | `../gitops/clusters/_template`, `../gitops/platform/_template`, `../gitops/apps/_template`, `../gitops/policies/_template`, `../gitops/tenants/_template` |
4. 실제 `live`와 cluster root가 예제와 같은 소유권·조립 경계를 유지하는지
비교하고 `make check`로 검증합니다.
예제 디렉터리를 GitOps controller root로 연결하거나 예제의 이름, namespace,
값을 production 기본값으로 재사용하지 않습니다.
+20
View File
@@ -0,0 +1,20 @@
# Minimal Example
단일 환경·단일 클러스터가 같은 경계를 어떻게 사용하는지 보여 주는 예입니다.
```text
minimal/
├── infrastructure/
│ └── live/dev/cluster/README.md
└── gitops/
├── platform/core/base/
├── apps/hello-config/base/
└── clusters/dev/main/
```
실제 cloud 리소스를 만들지 않으며, GitOps 예제는 Namespace와 ConfigMap만
렌더합니다.
```bash
kubectl kustomize examples/minimal/gitops/clusters/dev/main
```
@@ -0,0 +1,11 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: hello-config
namespace: skeleton-demo
labels:
app.kubernetes.io/name: hello-config
app.kubernetes.io/part-of: skeleton-demo
app.kubernetes.io/managed-by: kustomize
data:
message: "replace this example with a real application definition"
@@ -0,0 +1,5 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- config-map.yaml
@@ -0,0 +1,6 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../../platform/core/base
- ../../../apps/hello-config/base
@@ -0,0 +1,5 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- namespace.yaml
@@ -0,0 +1,7 @@
apiVersion: v1
kind: Namespace
metadata:
name: skeleton-demo
labels:
app.kubernetes.io/part-of: skeleton-demo
app.kubernetes.io/managed-by: kustomize
@@ -0,0 +1,11 @@
# Example Live Root
실제 프로젝트에서는 이 위치가 독립 state를 갖는 실행 가능한 IaC root가 됩니다.
- Scope: local example
- Environment: dev
- Stack: cluster
- State: example에는 없음
이 예제에는 provider 또는 IaC 엔진을 선택하지 않았기 때문에 실행 코드를
포함하지 않습니다.
+48
View File
@@ -0,0 +1,48 @@
# Scaled Layout Example
계정·리전·환경·클러스터가 늘어나도 lifecycle과 entrypoint 계약은 바뀌지
않습니다.
```text
infrastructure/
├── components/
│ └── aws/
│ ├── network/
│ ├── identity/
│ └── eks/
├── stacks/
│ ├── regional-foundation/
│ └── kubernetes-cluster/
└── live/
└── aws/
├── platform-nonprod/
│ └── ap-northeast-2/
│ ├── dev/{network,cluster-a}/
│ └── staging/{network,cluster-a}/
└── platform-prod/
├── ap-northeast-2/prod/{network,cluster-a}/
└── ap-southeast-1/prod/{network,cluster-b}/
gitops/
├── platform/{core,networking,security,observability}/
├── policies/{baseline,production}/
├── tenants/{team-a,team-b}/
├── apps/{api,worker}/
└── clusters/
├── dev/ap-northeast-2/cluster-a/
├── staging/ap-northeast-2/cluster-a/
└── prod/
├── ap-northeast-2/cluster-a/
└── ap-southeast-1/cluster-b/
```
중괄호 표기는 설명을 줄이기 위한 것이며 실제 폴더명으로 사용하지 않습니다.
## 분리 기준
- `regional-foundation` network stack과 cluster는 파괴 영향이 달라 state를
분리합니다.
- production과 non-production은 account, credential과 state를 분리합니다.
- 공통 구현은 catalog에 한 번만 두고 cluster root는 선택과 patch만 가집니다.
- tenant/team별 권한이 다르면 CODEOWNERS와 repository 분리를 검토합니다.
- repository 분리는 폴더 수가 아니라 소유권과 권한 경계가 달라질 때 수행합니다.
+33
View File
@@ -0,0 +1,33 @@
# GitOps Desired State
Kubernetes API 안에서 지속적으로 reconcile할 desired state를 관리합니다.
GitOps controller를 사용하지 않는 초기 단계에도 `kubectl kustomize`로 같은
entrypoint를 렌더할 수 있습니다.
```text
platform ─┐
policies ─┼──▶ clusters/<...> ◀── GitOps controller root
tenants ─┤
apps ─┘
```
- `clusters`: 클러스터별 최종 조립점
- `platform`: cluster-wide addon과 controller
- `policies`: cluster-wide admission과 거버넌스 규칙
- `tenants`: 구체적인 namespace/RBAC/quota/NetworkPolicy
- `apps`: application 배포 정의
Catalog 디렉터리를 controller root로 직접 지정하지 않습니다. cluster entrypoint가
필요한 base/overlay를 선택하고 의존 순서를 명시합니다.
## 기본 규칙
- `base`는 환경을 모르며 재사용 가능한 기본값만 가집니다.
- `overlays`는 차이만 patch하고 전체 manifest를 복사하지 않습니다.
- CRD/controller가 필요한 리소스는 controller 이후에 reconcile합니다.
- resource namespace, ownership label과 버전을 명시합니다.
- raw `Secret` 또는 실제 비밀값을 커밋하지 않습니다.
- 원격 base/chart를 참조할 때 immutable version 또는 digest를 사용합니다.
Flux/Argo CD 고유 리소스와 sync ordering은 선택한 controller를 기록한 ADR에
문서화합니다.
+18
View File
@@ -0,0 +1,18 @@
# Application Deployment Catalog
애플리케이션의 source code가 아니라 Kubernetes 배포 정의를 둡니다. app 팀이
별도 source/deploy 저장소를 소유하면 이곳에는 immutable artifact를 참조하는
GitOps 리소스만 둘 수 있습니다.
```text
apps/
└── example-api/
├── base/
└── overlays/
├── dev/
└── prod/
```
base는 환경을 모르고, overlay에는 replica/resource/config처럼 필요한 차이만
둡니다. image는 mutable tag 대신 조직 정책에 따른 고정 tag 또는 digest를
사용합니다.
+22
View File
@@ -0,0 +1,22 @@
# __REPLACE_ME_APPLICATION_NAME__
## 소유자
Team: __REPLACE_ME_OWNER__
repository와 on-call 정보를 적습니다.
## 배포 계약
- Namespace:
- Image/artifact source:
- Ports/protocol:
- Dependency:
- SLO/alerts:
## 구성
- `base`: 공통 Kubernetes 배포 정의
- `overlays`: 환경별 replica, resource, config 차이
비밀은 ExternalSecret 같은 참조 또는 승인된 암호화 형식으로만 추가합니다.
+4
View File
@@ -0,0 +1,4 @@
# Base
환경을 모르는 application의 공통 manifest를 둡니다. namespace 자체의 소유권이
tenant catalog에 있다면 이곳에서 중복 생성하지 않습니다.
@@ -0,0 +1,4 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources: []
+6
View File
@@ -0,0 +1,6 @@
# Overlays
필요한 환경에만 overlay를 추가합니다. 각 overlay는 `../../base`를 참조하고
환경별 patch만 포함합니다.
비밀값, 임시 debug 설정과 수동 hotfix 결과를 overlay에 커밋하지 않습니다.
@@ -0,0 +1,8 @@
# Project Auth system
Project Auth bounded context가 소유하는 PostgreSQL, Keycloak과 client sync의
환경 중립 base입니다. `dev-k3s` namespace, host, Vault annotation과
NetworkPolicy는
`gitops/clusters/dev-k3s/overlays/systems/auth-system`에서 결합합니다.
Owner: Project Auth system
@@ -0,0 +1,8 @@
# API server
별도 source repository에서 빌드되는 API server의 환경 중립 Kubernetes
base입니다. Image digest, dev configuration, ingress와 pull credential
reference는 `gitops/clusters/dev-k3s/overlays/workloads/api-server`
소유합니다.
Owner: Project API workload
@@ -0,0 +1,7 @@
# Auth server
별도 source repository에서 빌드되는 Auth server의 환경 중립 Kubernetes
base입니다. Image digest, dev configuration, ingress와 Vault annotation은
`gitops/clusters/dev-k3s/overlays/workloads/auth-server`가 소유합니다.
Owner: Project Auth workload
+30
View File
@@ -0,0 +1,30 @@
# Cluster Entrypoints
클러스터별 최종 desired state 진입점입니다. GitOps controller의 root는 이
디렉터리 아래 **하나의 실제 cluster leaf**만 가리킵니다.
소규모:
```text
clusters/
└── dev/
└── main/
└── kustomization.yaml
```
다중 환경·리전:
```text
clusters/
├── dev/ap-northeast-2/dev-a/
├── staging/ap-northeast-2/staging-a/
└── prod/
├── ap-northeast-2/prod-a/
└── ap-southeast-1/prod-b/
```
cluster leaf에는 catalog 구현을 복사하지 않고 선택 목록과 cluster 고유 patch만
둡니다. 계정 또는 조직 경계가 필요하면 경로 segment를 추가할 수 있지만 자동화가
고정 depth에 의존하지 않도록 합니다.
`_template`을 실제 경로로 복사해 시작합니다.
+31
View File
@@ -0,0 +1,31 @@
# __REPLACE_ME_CLUSTER_NAME__
## 대상
- Environment: __REPLACE_ME_ENVIRONMENT__
- Region: __REPLACE_ME_REGION__
- Cluster: __REPLACE_ME_CLUSTER_NAME__
- Owner: __REPLACE_ME_OWNER__
## 구성
`kustomization.yaml``resources`에 필요한 platform, policy, tenant와 app의
base 또는 overlay를 추가합니다. 예:
```yaml
resources:
- ../../../platform/core/base
- ../../../policies/baseline/base
- ../../../apps/example-api/overlays/prod
```
위 예시는 `clusters/dev/main` 경로를 기준으로 합니다. 실제 상대 경로는 cluster
leaf 깊이에 맞게 조정합니다.
## 규칙
- 이 디렉터리가 해당 클러스터의 유일한 root입니다.
- 공통 manifest를 복사하지 않습니다.
- 클러스터 고유 차이만 local patch로 둡니다.
- dependency/sync 순서와 장애 시 reconcile 중지 절차를 문서화합니다.
- `_template` 자체를 GitOps controller에 연결하지 않습니다.
@@ -0,0 +1,5 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
# Add reusable platform, policy, tenant and app paths after copying this template.
resources: []
@@ -4,13 +4,15 @@
ApplicationSet의 `server`는 in-cluster API
`https://kubernetes.default.svc`를 사용합니다.
이 디렉터리는 base 복사본이 아니라 다음 cluster-specific composition만
소유합니다.
`kustomization.yaml`은 bootstrap root Application이 읽는 유일한 cluster
entrypoint이며 permission-scoped Argo CD control plane을 조립합니다.
ApplicationSet inventory는 아래 cluster-specific overlay를 child
Application source로 선택합니다.
- `platform/vault`: dev Vault namespace, NetworkPolicy와 single-node profile
- `systems/auth-system`: `auth-system-dev` namespace, internal/public host,
- `overlays/platform/vault`: dev Vault namespace, NetworkPolicy와 single-node profile
- `overlays/systems/auth-system`: `auth-system-dev` namespace, internal/public host,
Vault injection path와 system NetworkPolicy
- `workloads/*`: dev namespace, image reference, ingress, pull
- `overlays/workloads/*`: dev namespace, image reference, ingress, pull
SealedSecret과 workload NetworkPolicy
현재 Vault NetworkPolicy의 Kubernetes API CIDR와 node address는
@@ -19,7 +21,8 @@ ApplicationSet의 `server`는 in-cluster API
두 번째 클러스터를 추가할 때는 다음 순서를 사용합니다.
1. 실제 차이가 있는 overlay만 `clusters/<cluster>/overlays` 추가합니다.
1. 실제 차이가 있는 overlay만 `gitops/clusters/<cluster>/overlays`
추가합니다.
2. Argo CD cluster credential을 Git 밖에서 등록합니다.
3. 각 AppProject destination에 정확한 API server/namespace를 추가합니다.
4. 권한별 ApplicationSet inventory에 `autoSync: "false"` element를
@@ -0,0 +1,5 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../platform/control-plane/argocd
@@ -4,7 +4,7 @@ kind: Kustomization
namespace: auth-system-dev
resources:
- ../../../../../systems/auth-system/base
- ../../../../../apps/systems/auth-system/base
- namespace.yaml
- keycloak-ingress.yaml
- public-access.yaml

Some files were not shown because too many files have changed in this diff Show More