diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..1b88682 --- /dev/null +++ b/.editorconfig @@ -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 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..79c1b70 --- /dev/null +++ b/.gitattributes @@ -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 diff --git a/.gitea/workflows/promote-image.yaml b/.gitea/workflows/promote-image.yaml index c9cfa7d..7d7bd4e 100644 --- a/.gitea/workflows/promote-image.yaml +++ b/.gitea/workflows/promote-image.yaml @@ -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" diff --git a/.gitea/workflows/validate.yaml b/.gitea/workflows/validate.yaml index 89deb85..5f31a3c 100644 --- a/.gitea/workflows/validate.yaml +++ b/.gitea/workflows/validate.yaml @@ -20,4 +20,4 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Validate - run: ./hack/validate.sh + run: make check diff --git a/.gitignore b/.gitignore index 733971d..ba097a2 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/AGENTS.md b/AGENTS.md index 7e65be9..27acc26 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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. diff --git a/CHANGELOG.md b/CHANGELOG.md index 0394dbf..468def3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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`; diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..a51e440 --- /dev/null +++ b/CONTRIBUTING.md @@ -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` 진입점은 제공하지 않습니다. diff --git a/Makefile b/Makefile index 5200e3b..4f1be03 100644 --- a/Makefile +++ b/Makefile @@ -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 "} /^[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)" || \ diff --git a/README.md b/README.md index 0d9c110..55708c3 100644 --- a/README.md +++ b/README.md @@ -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 최종 구성 - workloads/ first-party workload의 dev-k3s 최종 구성 -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 + platform/ 공유 서비스의 dev-k3s 최종 구성 + systems/ bounded-context system의 dev-k3s 최종 구성 + workloads/ first-party workload의 dev-k3s 최종 구성 +gitops/platform/ + control-plane/argocd/ AppProject와 ApplicationSet inventory + 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) diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..59fd733 --- /dev/null +++ b/SECURITY.md @@ -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는 운영 환경 도입 전에 별도로 승인합니다. diff --git a/bootstrap/README.md b/bootstrap/README.md new file mode 100644 index 0000000..b3fdf92 --- /dev/null +++ b/bootstrap/README.md @@ -0,0 +1,11 @@ +# Bootstrap + +선언형 인프라와 GitOps가 스스로 동작하기 전에 한 번 또는 매우 드물게 수행하는 +최소 초기화만 둡니다. + +- `foundation`: remote state, locking, 최초 identity 같은 선행 조건 +- `gitops`: 선택한 controller 설치와 cluster root 연결 + +일반 네트워크, Kubernetes cluster, addon과 application을 이곳에 두지 않습니다. +부트스트랩 절차는 반복 실행 가능하고 감사 가능해야 하며, 장기 수동 운영 경로가 +되어서는 안 됩니다. diff --git a/bootstrap/foundation/README.md b/bootstrap/foundation/README.md new file mode 100644 index 0000000..4165379 --- /dev/null +++ b/bootstrap/foundation/README.md @@ -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와 복구 절차만 기록합니다. diff --git a/bootstrap/gitops/README.md b/bootstrap/gitops/README.md new file mode 100644 index 0000000..1105073 --- /dev/null +++ b/bootstrap/gitops/README.md @@ -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`에 문서화합니다. diff --git a/bootstrap/argocd/control-plane-project.yaml b/bootstrap/gitops/argocd/control-plane-project.yaml similarity index 100% rename from bootstrap/argocd/control-plane-project.yaml rename to bootstrap/gitops/argocd/control-plane-project.yaml diff --git a/bootstrap/argocd/kustomization.yaml b/bootstrap/gitops/argocd/kustomization.yaml similarity index 100% rename from bootstrap/argocd/kustomization.yaml rename to bootstrap/gitops/argocd/kustomization.yaml diff --git a/bootstrap/argocd/root-application.yaml b/bootstrap/gitops/argocd/root-application.yaml similarity index 93% rename from bootstrap/argocd/root-application.yaml rename to bootstrap/gitops/argocd/root-application.yaml index 572d8e6..8981d6a 100644 --- a/bootstrap/argocd/root-application.yaml +++ b/bootstrap/gitops/argocd/root-application.yaml @@ -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 diff --git a/bootstrap/argocd/version.env b/bootstrap/gitops/argocd/version.env similarity index 100% rename from bootstrap/argocd/version.env rename to bootstrap/gitops/argocd/version.env diff --git a/docs/architecture/argocd.md b/docs/architecture/argocd.md index cceecb8..776b4a2 100644 --- a/docs/architecture/argocd.md +++ b/docs/architecture/argocd.md @@ -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//config.yaml`을 Git files -generator로 읽고 component inventory와 Matrix generator로 결합합니다. +component에서 반복하게 될 때 `gitops/clusters//config.yaml`을 Git +files generator로 읽고 component inventory와 Matrix generator로 결합합니다. 그때도 AppProject는 ApplicationSet template에 고정하고, cluster별 `autoSync`는 quoted string과 승인 gate로 유지합니다. diff --git a/docs/architecture/deployment.md b/docs/architecture/deployment.md index 01bd717..319f31b 100644 --- a/docs/architecture/deployment.md +++ b/docs/architecture/deployment.md @@ -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로 사용합니다. diff --git a/docs/architecture/repository-structure.md b/docs/architecture/repository-structure.md new file mode 100644 index 0000000..2a3ccdb --- /dev/null +++ b/docs/architecture/repository-structure.md @@ -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/////`처럼 경로를 확장합니다. +자동화는 경로의 고정 깊이에 의존하지 말고 실행 가능한 root 파일을 기준으로 +대상을 찾도록 작성합니다. + +서로 다른 환경의 root가 상대 경로로 다른 환경 구현을 import하면 안 됩니다. +공유가 필요하면 versioned component나 명시적인 remote output/data contract를 +사용합니다. + +## GitOps 경계 + +### `clusters` + +클러스터가 reconcile하는 유일한 진입점입니다. 공통 리소스를 복사하지 않고 +platform, policy, tenant, app catalog에서 필요한 항목만 참조합니다. + +작은 구성은 `clusters/dev/main`, 다중 리전 구성은 +`clusters///` 형태를 사용할 수 있습니다. 여기에도 +고정된 경로 깊이를 강제하지 않습니다. + +### `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/) diff --git a/docs/architecture/repository-taxonomy.md b/docs/architecture/repository-taxonomy.md index 6e0c722..2e71b4a 100644 --- a/docs/architecture/repository-taxonomy.md +++ b/docs/architecture/repository-taxonomy.md @@ -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//base -systems//base -workloads//base +gitops/platform/shared-services//base +gitops/apps/systems//base +gitops/apps/workloads//base -clusters//overlays/platform/ -clusters//overlays/systems/ -clusters//overlays/workloads/ +gitops/clusters//overlays/platform/ +gitops/clusters//overlays/systems/ +gitops/clusters//overlays/workloads/ -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//base` | -| dev worker digest/secret annotation | `clusters/dev-k3s/overlays/workloads/` | -| 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//base` | +| dev worker digest/secret annotation | `gitops/clusters/dev-k3s/overlays/workloads/` | +| Vault workload policy/role | `infrastructure/live/dev-k3s/vault-workloads`와 그 아래 `policies/` | | Vault auth backend | `vault-foundation` Terraform state | 분류가 애매하면 설치 순서가 아니라 owner와 소비자 경계를 ADR에 먼저 diff --git a/docs/architecture/terraform.md b/docs/architecture/terraform.md index 67bac19..33c3e25 100644 --- a/docs/architecture/terraform.md +++ b/docs/architecture/terraform.md @@ -77,10 +77,10 @@ destroy/backup 책임이 정해졌을 때만 다음처럼 별도 machine root를 추가합니다. ```text -iac/terraform/live//machine -iac/terraform/live//vault-foundation -iac/terraform/live//vault-workloads -iac/terraform/live//vault-database +infrastructure/live//machine +infrastructure/live//vault-foundation +infrastructure/live//vault-workloads +infrastructure/live//vault-database ``` Machine root output을 읽기 위해 Vault state 전체를 공유하지 않습니다. diff --git a/policies/legacy/dev/README.md b/docs/archive/policies/legacy/dev/README.md similarity index 100% rename from policies/legacy/dev/README.md rename to docs/archive/policies/legacy/dev/README.md diff --git a/policies/legacy/dev/vault-transit-automation-dev.hcl b/docs/archive/policies/legacy/dev/vault-transit-automation-dev.hcl similarity index 100% rename from policies/legacy/dev/vault-transit-automation-dev.hcl rename to docs/archive/policies/legacy/dev/vault-transit-automation-dev.hcl diff --git a/policies/legacy/dev/workload-automation-dev.hcl b/docs/archive/policies/legacy/dev/workload-automation-dev.hcl similarity index 100% rename from policies/legacy/dev/workload-automation-dev.hcl rename to docs/archive/policies/legacy/dev/workload-automation-dev.hcl diff --git a/docs/adr/0001-canonical-repository.md b/docs/decisions/0001-canonical-repository.md similarity index 100% rename from docs/adr/0001-canonical-repository.md rename to docs/decisions/0001-canonical-repository.md diff --git a/docs/adr/0002-terraform-ownership.md b/docs/decisions/0002-terraform-ownership.md similarity index 100% rename from docs/adr/0002-terraform-ownership.md rename to docs/decisions/0002-terraform-ownership.md diff --git a/docs/adr/0003-vault-topology.md b/docs/decisions/0003-vault-topology.md similarity index 100% rename from docs/adr/0003-vault-topology.md rename to docs/decisions/0003-vault-topology.md diff --git a/docs/adr/0004-argo-bootstrap-stages.md b/docs/decisions/0004-argo-bootstrap-stages.md similarity index 92% rename from docs/adr/0004-argo-bootstrap-stages.md rename to docs/decisions/0004-argo-bootstrap-stages.md index a5abc5f..53ff946 100644 --- a/docs/adr/0004-argo-bootstrap-stages.md +++ b/docs/decisions/0004-argo-bootstrap-stages.md @@ -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만 허용합니다. diff --git a/docs/adr/0005-repository-layout.md b/docs/decisions/0005-repository-layout.md similarity index 62% rename from docs/adr/0005-repository-layout.md rename to docs/decisions/0005-repository-layout.md index fc70414..9227e32 100644 --- a/docs/adr/0005-repository-layout.md +++ b/docs/decisions/0005-repository-layout.md @@ -9,16 +9,17 @@ monorepo를 유지합니다. Application source repository와 deployment configuration repository는 분리합니다. 이 저장소 자체는 독립 reference lab이며 범용 platform product로 간주하지 않습니다. -- `platform/`, `systems/`, `workloads/`: ownership별 base; 환경 중립은 - 목표 contract -- `clusters//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/`: GitOps controller가 읽는 cluster root +- `gitops/clusters//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을 따릅니다. diff --git a/docs/adr/0006-gateway-and-istio.md b/docs/decisions/0006-gateway-and-istio.md similarity index 100% rename from docs/adr/0006-gateway-and-istio.md rename to docs/decisions/0006-gateway-and-istio.md diff --git a/docs/adr/0007-repository-ownership-boundaries.md b/docs/decisions/0007-repository-ownership-boundaries.md similarity index 78% rename from docs/adr/0007-repository-ownership-boundaries.md rename to docs/decisions/0007-repository-ownership-boundaries.md index 77e8c8f..d482064 100644 --- a/docs/adr/0007-repository-ownership-boundaries.md +++ b/docs/decisions/0007-repository-ownership-boundaries.md @@ -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//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//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를 diff --git a/docs/decisions/0008-adopt-k8s-template-lifecycle-layout.md b/docs/decisions/0008-adopt-k8s-template-lifecycle-layout.md new file mode 100644 index 0000000..5c42a4a --- /dev/null +++ b/docs/decisions/0008-adopt-k8s-template-lifecycle-layout.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를 변경하지 않습니다. diff --git a/docs/decisions/README.md b/docs/decisions/README.md new file mode 100644 index 0000000..f65bcd2 --- /dev/null +++ b/docs/decisions/README.md @@ -0,0 +1,26 @@ +# Architecture Decision Records + +프로젝트의 장기 구조에 영향을 주는 선택은 ADR로 남깁니다. + +파일명은 `NNNN-kebab-case-title.md`를 사용하고 다음 형식을 따릅니다. + +```markdown +# NNNN. 제목 + +- 상태: 제안 | 승인 | 폐기 | 대체 +- 날짜: YYYY-MM-DD +- 결정자: 팀 또는 역할 + +## 배경 + +## 결정 + +## 결과 + +## 대안 +``` + +기존 결정을 바꿀 때 문서를 지우지 말고 새 ADR에서 이전 ADR을 대체했다고 +표시합니다. + +현재 프로젝트 결정은 이 디렉터리의 `0001`부터 순서대로 관리합니다. diff --git a/docs/guides/getting-started.md b/docs/guides/getting-started.md new file mode 100644 index 0000000..d7761a1 --- /dev/null +++ b/docs/guides/getting-started.md @@ -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에 재사용하지 않습니다. diff --git a/INTERN_GUIDE.md b/docs/guides/intern-guide.md similarity index 83% rename from INTERN_GUIDE.md rename to docs/guides/intern-guide.md index 43747f1..7acff4d 100644 --- a/INTERN_GUIDE.md +++ b/docs/guides/intern-guide.md @@ -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에 diff --git a/docs/runbooks/README.md b/docs/runbooks/README.md new file mode 100644 index 0000000..14b2934 --- /dev/null +++ b/docs/runbooks/README.md @@ -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 장애 대응 + +각 문서는 `목적`, `사전 조건`, `영향`, `절차`, `검증`, `롤백`, +`에스컬레이션` 섹션을 포함해야 합니다. diff --git a/docs/runbooks/_template.md b/docs/runbooks/_template.md new file mode 100644 index 0000000..3610262 --- /dev/null +++ b/docs/runbooks/_template.md @@ -0,0 +1,15 @@ +# Runbook 제목 + +## 목적 + +## 사전 조건 + +## 영향 + +## 절차 + +## 검증 + +## 롤백 + +## 에스컬레이션 diff --git a/docs/runbooks/dev-bootstrap.md b/docs/runbooks/dev-bootstrap.md index 831ba17..becd467 100644 --- a/docs/runbooks/dev-bootstrap.md +++ b/docs/runbooks/dev-bootstrap.md @@ -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 diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..59a353e --- /dev/null +++ b/examples/README.md @@ -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 기본값으로 재사용하지 않습니다. diff --git a/examples/minimal/README.md b/examples/minimal/README.md new file mode 100644 index 0000000..3be62da --- /dev/null +++ b/examples/minimal/README.md @@ -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 +``` diff --git a/examples/minimal/gitops/apps/hello-config/base/config-map.yaml b/examples/minimal/gitops/apps/hello-config/base/config-map.yaml new file mode 100644 index 0000000..1225508 --- /dev/null +++ b/examples/minimal/gitops/apps/hello-config/base/config-map.yaml @@ -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" diff --git a/examples/minimal/gitops/apps/hello-config/base/kustomization.yaml b/examples/minimal/gitops/apps/hello-config/base/kustomization.yaml new file mode 100644 index 0000000..a4da9fa --- /dev/null +++ b/examples/minimal/gitops/apps/hello-config/base/kustomization.yaml @@ -0,0 +1,5 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - config-map.yaml diff --git a/examples/minimal/gitops/clusters/dev/main/kustomization.yaml b/examples/minimal/gitops/clusters/dev/main/kustomization.yaml new file mode 100644 index 0000000..5323efb --- /dev/null +++ b/examples/minimal/gitops/clusters/dev/main/kustomization.yaml @@ -0,0 +1,6 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - ../../../platform/core/base + - ../../../apps/hello-config/base diff --git a/examples/minimal/gitops/platform/core/base/kustomization.yaml b/examples/minimal/gitops/platform/core/base/kustomization.yaml new file mode 100644 index 0000000..dca4a51 --- /dev/null +++ b/examples/minimal/gitops/platform/core/base/kustomization.yaml @@ -0,0 +1,5 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - namespace.yaml diff --git a/examples/minimal/gitops/platform/core/base/namespace.yaml b/examples/minimal/gitops/platform/core/base/namespace.yaml new file mode 100644 index 0000000..f66849c --- /dev/null +++ b/examples/minimal/gitops/platform/core/base/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 diff --git a/examples/minimal/infrastructure/live/dev/cluster/README.md b/examples/minimal/infrastructure/live/dev/cluster/README.md new file mode 100644 index 0000000..4981294 --- /dev/null +++ b/examples/minimal/infrastructure/live/dev/cluster/README.md @@ -0,0 +1,11 @@ +# Example Live Root + +실제 프로젝트에서는 이 위치가 독립 state를 갖는 실행 가능한 IaC root가 됩니다. + +- Scope: local example +- Environment: dev +- Stack: cluster +- State: example에는 없음 + +이 예제에는 provider 또는 IaC 엔진을 선택하지 않았기 때문에 실행 코드를 +포함하지 않습니다. diff --git a/examples/scaled/README.md b/examples/scaled/README.md new file mode 100644 index 0000000..6190db6 --- /dev/null +++ b/examples/scaled/README.md @@ -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 분리는 폴더 수가 아니라 소유권과 권한 경계가 달라질 때 수행합니다. diff --git a/gitops/README.md b/gitops/README.md new file mode 100644 index 0000000..9e41d5f --- /dev/null +++ b/gitops/README.md @@ -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에 +문서화합니다. diff --git a/gitops/apps/README.md b/gitops/apps/README.md new file mode 100644 index 0000000..955aa01 --- /dev/null +++ b/gitops/apps/README.md @@ -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를 +사용합니다. diff --git a/gitops/apps/_template/README.md b/gitops/apps/_template/README.md new file mode 100644 index 0000000..83e889e --- /dev/null +++ b/gitops/apps/_template/README.md @@ -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 같은 참조 또는 승인된 암호화 형식으로만 추가합니다. diff --git a/gitops/apps/_template/base/README.md b/gitops/apps/_template/base/README.md new file mode 100644 index 0000000..8bcecaa --- /dev/null +++ b/gitops/apps/_template/base/README.md @@ -0,0 +1,4 @@ +# Base + +환경을 모르는 application의 공통 manifest를 둡니다. namespace 자체의 소유권이 +tenant catalog에 있다면 이곳에서 중복 생성하지 않습니다. diff --git a/gitops/apps/_template/base/kustomization.yaml b/gitops/apps/_template/base/kustomization.yaml new file mode 100644 index 0000000..e609fb2 --- /dev/null +++ b/gitops/apps/_template/base/kustomization.yaml @@ -0,0 +1,4 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: [] diff --git a/gitops/apps/_template/overlays/README.md b/gitops/apps/_template/overlays/README.md new file mode 100644 index 0000000..6c3d7fa --- /dev/null +++ b/gitops/apps/_template/overlays/README.md @@ -0,0 +1,6 @@ +# Overlays + +필요한 환경에만 overlay를 추가합니다. 각 overlay는 `../../base`를 참조하고 +환경별 patch만 포함합니다. + +비밀값, 임시 debug 설정과 수동 hotfix 결과를 overlay에 커밋하지 않습니다. diff --git a/gitops/apps/systems/auth-system/README.md b/gitops/apps/systems/auth-system/README.md new file mode 100644 index 0000000..8388761 --- /dev/null +++ b/gitops/apps/systems/auth-system/README.md @@ -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 diff --git a/systems/auth-system/base/files/keycloak/project-auth-realm.json b/gitops/apps/systems/auth-system/base/files/keycloak/project-auth-realm.json similarity index 100% rename from systems/auth-system/base/files/keycloak/project-auth-realm.json rename to gitops/apps/systems/auth-system/base/files/keycloak/project-auth-realm.json diff --git a/systems/auth-system/base/files/postgres/01-init-project-auth-databases.sh b/gitops/apps/systems/auth-system/base/files/postgres/01-init-project-auth-databases.sh similarity index 100% rename from systems/auth-system/base/files/postgres/01-init-project-auth-databases.sh rename to gitops/apps/systems/auth-system/base/files/postgres/01-init-project-auth-databases.sh diff --git a/systems/auth-system/base/keycloak-client-sync-job.yaml b/gitops/apps/systems/auth-system/base/keycloak-client-sync-job.yaml similarity index 100% rename from systems/auth-system/base/keycloak-client-sync-job.yaml rename to gitops/apps/systems/auth-system/base/keycloak-client-sync-job.yaml diff --git a/systems/auth-system/base/keycloak-client-sync-serviceaccount.yaml b/gitops/apps/systems/auth-system/base/keycloak-client-sync-serviceaccount.yaml similarity index 100% rename from systems/auth-system/base/keycloak-client-sync-serviceaccount.yaml rename to gitops/apps/systems/auth-system/base/keycloak-client-sync-serviceaccount.yaml diff --git a/systems/auth-system/base/keycloak-deployment.yaml b/gitops/apps/systems/auth-system/base/keycloak-deployment.yaml similarity index 100% rename from systems/auth-system/base/keycloak-deployment.yaml rename to gitops/apps/systems/auth-system/base/keycloak-deployment.yaml diff --git a/systems/auth-system/base/keycloak-service.yaml b/gitops/apps/systems/auth-system/base/keycloak-service.yaml similarity index 100% rename from systems/auth-system/base/keycloak-service.yaml rename to gitops/apps/systems/auth-system/base/keycloak-service.yaml diff --git a/systems/auth-system/base/keycloak-serviceaccount.yaml b/gitops/apps/systems/auth-system/base/keycloak-serviceaccount.yaml similarity index 100% rename from systems/auth-system/base/keycloak-serviceaccount.yaml rename to gitops/apps/systems/auth-system/base/keycloak-serviceaccount.yaml diff --git a/systems/auth-system/base/kustomization.yaml b/gitops/apps/systems/auth-system/base/kustomization.yaml similarity index 100% rename from systems/auth-system/base/kustomization.yaml rename to gitops/apps/systems/auth-system/base/kustomization.yaml diff --git a/systems/auth-system/base/postgres-service.yaml b/gitops/apps/systems/auth-system/base/postgres-service.yaml similarity index 100% rename from systems/auth-system/base/postgres-service.yaml rename to gitops/apps/systems/auth-system/base/postgres-service.yaml diff --git a/systems/auth-system/base/postgres-serviceaccount.yaml b/gitops/apps/systems/auth-system/base/postgres-serviceaccount.yaml similarity index 100% rename from systems/auth-system/base/postgres-serviceaccount.yaml rename to gitops/apps/systems/auth-system/base/postgres-serviceaccount.yaml diff --git a/systems/auth-system/base/postgres-statefulset.yaml b/gitops/apps/systems/auth-system/base/postgres-statefulset.yaml similarity index 100% rename from systems/auth-system/base/postgres-statefulset.yaml rename to gitops/apps/systems/auth-system/base/postgres-statefulset.yaml diff --git a/gitops/apps/workloads/api-server/README.md b/gitops/apps/workloads/api-server/README.md new file mode 100644 index 0000000..a0d81da --- /dev/null +++ b/gitops/apps/workloads/api-server/README.md @@ -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 diff --git a/workloads/api-server/base/deployment.yaml b/gitops/apps/workloads/api-server/base/deployment.yaml similarity index 100% rename from workloads/api-server/base/deployment.yaml rename to gitops/apps/workloads/api-server/base/deployment.yaml diff --git a/workloads/api-server/base/kustomization.yaml b/gitops/apps/workloads/api-server/base/kustomization.yaml similarity index 100% rename from workloads/api-server/base/kustomization.yaml rename to gitops/apps/workloads/api-server/base/kustomization.yaml diff --git a/workloads/api-server/base/service.yaml b/gitops/apps/workloads/api-server/base/service.yaml similarity index 100% rename from workloads/api-server/base/service.yaml rename to gitops/apps/workloads/api-server/base/service.yaml diff --git a/workloads/api-server/base/serviceaccount.yaml b/gitops/apps/workloads/api-server/base/serviceaccount.yaml similarity index 100% rename from workloads/api-server/base/serviceaccount.yaml rename to gitops/apps/workloads/api-server/base/serviceaccount.yaml diff --git a/gitops/apps/workloads/auth-server/README.md b/gitops/apps/workloads/auth-server/README.md new file mode 100644 index 0000000..a4bcaa1 --- /dev/null +++ b/gitops/apps/workloads/auth-server/README.md @@ -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 diff --git a/workloads/auth-server/base/auth-db-migration-serviceaccount.yaml b/gitops/apps/workloads/auth-server/base/auth-db-migration-serviceaccount.yaml similarity index 100% rename from workloads/auth-server/base/auth-db-migration-serviceaccount.yaml rename to gitops/apps/workloads/auth-server/base/auth-db-migration-serviceaccount.yaml diff --git a/workloads/auth-server/base/db-migration-job.yaml b/gitops/apps/workloads/auth-server/base/db-migration-job.yaml similarity index 100% rename from workloads/auth-server/base/db-migration-job.yaml rename to gitops/apps/workloads/auth-server/base/db-migration-job.yaml diff --git a/workloads/auth-server/base/deployment.yaml b/gitops/apps/workloads/auth-server/base/deployment.yaml similarity index 100% rename from workloads/auth-server/base/deployment.yaml rename to gitops/apps/workloads/auth-server/base/deployment.yaml diff --git a/workloads/auth-server/base/kustomization.yaml b/gitops/apps/workloads/auth-server/base/kustomization.yaml similarity index 100% rename from workloads/auth-server/base/kustomization.yaml rename to gitops/apps/workloads/auth-server/base/kustomization.yaml diff --git a/workloads/auth-server/base/service.yaml b/gitops/apps/workloads/auth-server/base/service.yaml similarity index 100% rename from workloads/auth-server/base/service.yaml rename to gitops/apps/workloads/auth-server/base/service.yaml diff --git a/workloads/auth-server/base/serviceaccount.yaml b/gitops/apps/workloads/auth-server/base/serviceaccount.yaml similarity index 100% rename from workloads/auth-server/base/serviceaccount.yaml rename to gitops/apps/workloads/auth-server/base/serviceaccount.yaml diff --git a/gitops/clusters/README.md b/gitops/clusters/README.md new file mode 100644 index 0000000..ec05283 --- /dev/null +++ b/gitops/clusters/README.md @@ -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`을 실제 경로로 복사해 시작합니다. diff --git a/gitops/clusters/_template/README.md b/gitops/clusters/_template/README.md new file mode 100644 index 0000000..88b7c59 --- /dev/null +++ b/gitops/clusters/_template/README.md @@ -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에 연결하지 않습니다. diff --git a/gitops/clusters/_template/kustomization.yaml b/gitops/clusters/_template/kustomization.yaml new file mode 100644 index 0000000..d7587e5 --- /dev/null +++ b/gitops/clusters/_template/kustomization.yaml @@ -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: [] diff --git a/clusters/dev-k3s/README.md b/gitops/clusters/dev-k3s/README.md similarity index 65% rename from clusters/dev-k3s/README.md rename to gitops/clusters/dev-k3s/README.md index 0d5fc88..fe4bcbe 100644 --- a/clusters/dev-k3s/README.md +++ b/gitops/clusters/dev-k3s/README.md @@ -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//overlays`에 추가합니다. +1. 실제 차이가 있는 overlay만 `gitops/clusters//overlays`에 + 추가합니다. 2. Argo CD cluster credential을 Git 밖에서 등록합니다. 3. 각 AppProject destination에 정확한 API server/namespace를 추가합니다. 4. 권한별 ApplicationSet inventory에 `autoSync: "false"` element를 diff --git a/gitops/clusters/dev-k3s/kustomization.yaml b/gitops/clusters/dev-k3s/kustomization.yaml new file mode 100644 index 0000000..25bd875 --- /dev/null +++ b/gitops/clusters/dev-k3s/kustomization.yaml @@ -0,0 +1,5 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - ../../platform/control-plane/argocd diff --git a/clusters/dev-k3s/overlays/platform/vault/kustomization.yaml b/gitops/clusters/dev-k3s/overlays/platform/vault/kustomization.yaml similarity index 100% rename from clusters/dev-k3s/overlays/platform/vault/kustomization.yaml rename to gitops/clusters/dev-k3s/overlays/platform/vault/kustomization.yaml diff --git a/clusters/dev-k3s/overlays/platform/vault/namespace.yaml b/gitops/clusters/dev-k3s/overlays/platform/vault/namespace.yaml similarity index 100% rename from clusters/dev-k3s/overlays/platform/vault/namespace.yaml rename to gitops/clusters/dev-k3s/overlays/platform/vault/namespace.yaml diff --git a/clusters/dev-k3s/overlays/platform/vault/networkpolicy.yaml b/gitops/clusters/dev-k3s/overlays/platform/vault/networkpolicy.yaml similarity index 100% rename from clusters/dev-k3s/overlays/platform/vault/networkpolicy.yaml rename to gitops/clusters/dev-k3s/overlays/platform/vault/networkpolicy.yaml diff --git a/clusters/dev-k3s/overlays/systems/auth-system/config.env b/gitops/clusters/dev-k3s/overlays/systems/auth-system/config.env similarity index 100% rename from clusters/dev-k3s/overlays/systems/auth-system/config.env rename to gitops/clusters/dev-k3s/overlays/systems/auth-system/config.env diff --git a/clusters/dev-k3s/overlays/systems/auth-system/keycloak-client-sync.vault-patch.yaml b/gitops/clusters/dev-k3s/overlays/systems/auth-system/keycloak-client-sync.vault-patch.yaml similarity index 100% rename from clusters/dev-k3s/overlays/systems/auth-system/keycloak-client-sync.vault-patch.yaml rename to gitops/clusters/dev-k3s/overlays/systems/auth-system/keycloak-client-sync.vault-patch.yaml diff --git a/clusters/dev-k3s/overlays/systems/auth-system/keycloak-ingress.yaml b/gitops/clusters/dev-k3s/overlays/systems/auth-system/keycloak-ingress.yaml similarity index 100% rename from clusters/dev-k3s/overlays/systems/auth-system/keycloak-ingress.yaml rename to gitops/clusters/dev-k3s/overlays/systems/auth-system/keycloak-ingress.yaml diff --git a/clusters/dev-k3s/overlays/systems/auth-system/keycloak.public-url-patch.yaml b/gitops/clusters/dev-k3s/overlays/systems/auth-system/keycloak.public-url-patch.yaml similarity index 100% rename from clusters/dev-k3s/overlays/systems/auth-system/keycloak.public-url-patch.yaml rename to gitops/clusters/dev-k3s/overlays/systems/auth-system/keycloak.public-url-patch.yaml diff --git a/clusters/dev-k3s/overlays/systems/auth-system/keycloak.vault-patch.yaml b/gitops/clusters/dev-k3s/overlays/systems/auth-system/keycloak.vault-patch.yaml similarity index 100% rename from clusters/dev-k3s/overlays/systems/auth-system/keycloak.vault-patch.yaml rename to gitops/clusters/dev-k3s/overlays/systems/auth-system/keycloak.vault-patch.yaml diff --git a/clusters/dev-k3s/overlays/systems/auth-system/kustomization.yaml b/gitops/clusters/dev-k3s/overlays/systems/auth-system/kustomization.yaml similarity index 91% rename from clusters/dev-k3s/overlays/systems/auth-system/kustomization.yaml rename to gitops/clusters/dev-k3s/overlays/systems/auth-system/kustomization.yaml index 72dec2d..ed7a1cd 100644 --- a/clusters/dev-k3s/overlays/systems/auth-system/kustomization.yaml +++ b/gitops/clusters/dev-k3s/overlays/systems/auth-system/kustomization.yaml @@ -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 diff --git a/clusters/dev-k3s/overlays/systems/auth-system/namespace.yaml b/gitops/clusters/dev-k3s/overlays/systems/auth-system/namespace.yaml similarity index 100% rename from clusters/dev-k3s/overlays/systems/auth-system/namespace.yaml rename to gitops/clusters/dev-k3s/overlays/systems/auth-system/namespace.yaml diff --git a/clusters/dev-k3s/overlays/systems/auth-system/networkpolicy.yaml b/gitops/clusters/dev-k3s/overlays/systems/auth-system/networkpolicy.yaml similarity index 100% rename from clusters/dev-k3s/overlays/systems/auth-system/networkpolicy.yaml rename to gitops/clusters/dev-k3s/overlays/systems/auth-system/networkpolicy.yaml diff --git a/clusters/dev-k3s/overlays/systems/auth-system/postgres.vault-patch.yaml b/gitops/clusters/dev-k3s/overlays/systems/auth-system/postgres.vault-patch.yaml similarity index 100% rename from clusters/dev-k3s/overlays/systems/auth-system/postgres.vault-patch.yaml rename to gitops/clusters/dev-k3s/overlays/systems/auth-system/postgres.vault-patch.yaml diff --git a/clusters/dev-k3s/overlays/systems/auth-system/public-access.yaml b/gitops/clusters/dev-k3s/overlays/systems/auth-system/public-access.yaml similarity index 100% rename from clusters/dev-k3s/overlays/systems/auth-system/public-access.yaml rename to gitops/clusters/dev-k3s/overlays/systems/auth-system/public-access.yaml diff --git a/clusters/dev-k3s/overlays/workloads/api-server/config.env b/gitops/clusters/dev-k3s/overlays/workloads/api-server/config.env similarity index 100% rename from clusters/dev-k3s/overlays/workloads/api-server/config.env rename to gitops/clusters/dev-k3s/overlays/workloads/api-server/config.env diff --git a/clusters/dev-k3s/overlays/workloads/api-server/ghcr-regcred.sealedsecret.yaml b/gitops/clusters/dev-k3s/overlays/workloads/api-server/ghcr-regcred.sealedsecret.yaml similarity index 100% rename from clusters/dev-k3s/overlays/workloads/api-server/ghcr-regcred.sealedsecret.yaml rename to gitops/clusters/dev-k3s/overlays/workloads/api-server/ghcr-regcred.sealedsecret.yaml diff --git a/clusters/dev-k3s/overlays/workloads/api-server/ingress.yaml b/gitops/clusters/dev-k3s/overlays/workloads/api-server/ingress.yaml similarity index 100% rename from clusters/dev-k3s/overlays/workloads/api-server/ingress.yaml rename to gitops/clusters/dev-k3s/overlays/workloads/api-server/ingress.yaml diff --git a/clusters/dev-k3s/overlays/workloads/api-server/kustomization.yaml b/gitops/clusters/dev-k3s/overlays/workloads/api-server/kustomization.yaml similarity index 90% rename from clusters/dev-k3s/overlays/workloads/api-server/kustomization.yaml rename to gitops/clusters/dev-k3s/overlays/workloads/api-server/kustomization.yaml index 70728e3..05c10d3 100644 --- a/clusters/dev-k3s/overlays/workloads/api-server/kustomization.yaml +++ b/gitops/clusters/dev-k3s/overlays/workloads/api-server/kustomization.yaml @@ -4,7 +4,7 @@ kind: Kustomization namespace: api-dev resources: - - ../../../../../workloads/api-server/base + - ../../../../../apps/workloads/api-server/base - namespace.yaml - ingress.yaml - public-access.yaml diff --git a/clusters/dev-k3s/overlays/workloads/api-server/namespace.yaml b/gitops/clusters/dev-k3s/overlays/workloads/api-server/namespace.yaml similarity index 100% rename from clusters/dev-k3s/overlays/workloads/api-server/namespace.yaml rename to gitops/clusters/dev-k3s/overlays/workloads/api-server/namespace.yaml diff --git a/clusters/dev-k3s/overlays/workloads/api-server/networkpolicy.yaml b/gitops/clusters/dev-k3s/overlays/workloads/api-server/networkpolicy.yaml similarity index 100% rename from clusters/dev-k3s/overlays/workloads/api-server/networkpolicy.yaml rename to gitops/clusters/dev-k3s/overlays/workloads/api-server/networkpolicy.yaml diff --git a/clusters/dev-k3s/overlays/workloads/api-server/public-access.yaml b/gitops/clusters/dev-k3s/overlays/workloads/api-server/public-access.yaml similarity index 100% rename from clusters/dev-k3s/overlays/workloads/api-server/public-access.yaml rename to gitops/clusters/dev-k3s/overlays/workloads/api-server/public-access.yaml diff --git a/clusters/dev-k3s/overlays/workloads/auth-server/config.env b/gitops/clusters/dev-k3s/overlays/workloads/auth-server/config.env similarity index 100% rename from clusters/dev-k3s/overlays/workloads/auth-server/config.env rename to gitops/clusters/dev-k3s/overlays/workloads/auth-server/config.env diff --git a/clusters/dev-k3s/overlays/workloads/auth-server/db-migration-job.vault-patch.yaml b/gitops/clusters/dev-k3s/overlays/workloads/auth-server/db-migration-job.vault-patch.yaml similarity index 100% rename from clusters/dev-k3s/overlays/workloads/auth-server/db-migration-job.vault-patch.yaml rename to gitops/clusters/dev-k3s/overlays/workloads/auth-server/db-migration-job.vault-patch.yaml diff --git a/clusters/dev-k3s/overlays/workloads/auth-server/deployment.vault-patch.yaml b/gitops/clusters/dev-k3s/overlays/workloads/auth-server/deployment.vault-patch.yaml similarity index 100% rename from clusters/dev-k3s/overlays/workloads/auth-server/deployment.vault-patch.yaml rename to gitops/clusters/dev-k3s/overlays/workloads/auth-server/deployment.vault-patch.yaml diff --git a/clusters/dev-k3s/overlays/workloads/auth-server/ghcr-regcred.sealedsecret.yaml b/gitops/clusters/dev-k3s/overlays/workloads/auth-server/ghcr-regcred.sealedsecret.yaml similarity index 100% rename from clusters/dev-k3s/overlays/workloads/auth-server/ghcr-regcred.sealedsecret.yaml rename to gitops/clusters/dev-k3s/overlays/workloads/auth-server/ghcr-regcred.sealedsecret.yaml diff --git a/clusters/dev-k3s/overlays/workloads/auth-server/ingress.yaml b/gitops/clusters/dev-k3s/overlays/workloads/auth-server/ingress.yaml similarity index 100% rename from clusters/dev-k3s/overlays/workloads/auth-server/ingress.yaml rename to gitops/clusters/dev-k3s/overlays/workloads/auth-server/ingress.yaml diff --git a/clusters/dev-k3s/overlays/workloads/auth-server/kustomization.yaml b/gitops/clusters/dev-k3s/overlays/workloads/auth-server/kustomization.yaml similarity index 92% rename from clusters/dev-k3s/overlays/workloads/auth-server/kustomization.yaml rename to gitops/clusters/dev-k3s/overlays/workloads/auth-server/kustomization.yaml index 7dd7527..2d7b89f 100644 --- a/clusters/dev-k3s/overlays/workloads/auth-server/kustomization.yaml +++ b/gitops/clusters/dev-k3s/overlays/workloads/auth-server/kustomization.yaml @@ -4,7 +4,7 @@ kind: Kustomization namespace: auth-dev resources: - - ../../../../../workloads/auth-server/base + - ../../../../../apps/workloads/auth-server/base - namespace.yaml - ingress.yaml - public-access.yaml diff --git a/clusters/dev-k3s/overlays/workloads/auth-server/namespace.yaml b/gitops/clusters/dev-k3s/overlays/workloads/auth-server/namespace.yaml similarity index 100% rename from clusters/dev-k3s/overlays/workloads/auth-server/namespace.yaml rename to gitops/clusters/dev-k3s/overlays/workloads/auth-server/namespace.yaml diff --git a/clusters/dev-k3s/overlays/workloads/auth-server/networkpolicy.yaml b/gitops/clusters/dev-k3s/overlays/workloads/auth-server/networkpolicy.yaml similarity index 100% rename from clusters/dev-k3s/overlays/workloads/auth-server/networkpolicy.yaml rename to gitops/clusters/dev-k3s/overlays/workloads/auth-server/networkpolicy.yaml diff --git a/clusters/dev-k3s/overlays/workloads/auth-server/public-access.yaml b/gitops/clusters/dev-k3s/overlays/workloads/auth-server/public-access.yaml similarity index 100% rename from clusters/dev-k3s/overlays/workloads/auth-server/public-access.yaml rename to gitops/clusters/dev-k3s/overlays/workloads/auth-server/public-access.yaml diff --git a/gitops/platform/README.md b/gitops/platform/README.md new file mode 100644 index 0000000..f44dd19 --- /dev/null +++ b/gitops/platform/README.md @@ -0,0 +1,23 @@ +# Platform Catalog + +클러스터 전체에서 사용하는 addon과 controller의 재사용 구성을 둡니다. + +예: + +```text +platform/ +├── ingress/ +├── certificates/ +├── external-dns/ +├── external-secrets/ +├── storage/ +├── autoscaling/ +└── observability/ +``` + +실제로 사용하는 항목만 만듭니다. 각 component는 자신의 CRD, controller, +configuration과 values/patch를 응집해서 관리합니다. component 간 숨은 의존을 +만들지 말고 cluster root 또는 선택한 GitOps controller의 ordering 기능으로 +순서를 표현합니다. + +새 component는 `_template`을 복사해 시작합니다. diff --git a/gitops/platform/_template/README.md b/gitops/platform/_template/README.md new file mode 100644 index 0000000..fffdb28 --- /dev/null +++ b/gitops/platform/_template/README.md @@ -0,0 +1,21 @@ +# __REPLACE_ME_PLATFORM_COMPONENT_NAME__ + +## 책임 + +이 component가 소유하는 CRD, controller와 configuration을 적습니다. + +## 의존성 + +선행 component, namespace, identity와 최소 Kubernetes version을 적습니다. + +## 구성 + +- `base`: 모든 대상에서 공유하는 기본값 +- `overlays`: 환경/규모별 차이가 실제로 있을 때만 추가 + +Helm을 사용하면 chart source/version, values와 release 리소스를 이 component +안에 함께 둡니다. 원격 version은 고정합니다. + +## 운영 + +upgrade 순서, health check, rollback과 uninstall 영향을 runbook으로 연결합니다. diff --git a/gitops/platform/_template/base/README.md b/gitops/platform/_template/base/README.md new file mode 100644 index 0000000..961d33d --- /dev/null +++ b/gitops/platform/_template/base/README.md @@ -0,0 +1,7 @@ +# Base + +환경과 클러스터를 모르는 재사용 가능한 기본 manifest만 둡니다. + +- 실제 domain, account ID, credential을 하드코딩하지 않습니다. +- resource request/limit와 security context의 안전한 기본값을 둡니다. +- 환경 차이는 `overlays`에서 patch합니다. diff --git a/gitops/platform/_template/base/kustomization.yaml b/gitops/platform/_template/base/kustomization.yaml new file mode 100644 index 0000000..e609fb2 --- /dev/null +++ b/gitops/platform/_template/base/kustomization.yaml @@ -0,0 +1,4 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: [] diff --git a/gitops/platform/_template/overlays/README.md b/gitops/platform/_template/overlays/README.md new file mode 100644 index 0000000..dce243e --- /dev/null +++ b/gitops/platform/_template/overlays/README.md @@ -0,0 +1,7 @@ +# Overlays + +공통 base와 다른 값이 있을 때만 `/` overlay를 만듭니다. +overlay는 base를 참조하고 patch만 포함해야 합니다. + +`dev`, `prod` 전체 manifest 복사보다 replica, resource, retention처럼 실제로 +달라지는 항목만 표현합니다. diff --git a/gitops/platform/control-plane/argocd/README.md b/gitops/platform/control-plane/argocd/README.md new file mode 100644 index 0000000..f695a05 --- /dev/null +++ b/gitops/platform/control-plane/argocd/README.md @@ -0,0 +1,8 @@ +# Argo CD control plane + +Permission-scoped AppProject와 list-generated ApplicationSet inventory를 +소유합니다. Bootstrap root는 이 경로를 직접 source로 사용하지 않고 +`gitops/clusters/dev-k3s`를 통해 조립합니다. + +새 inventory 항목은 `autoSync: "false"`로 시작하며 project, repository와 +revision trust boundary를 template하지 않습니다. diff --git a/platform/control-plane/argocd/application-sets/kustomization.yaml b/gitops/platform/control-plane/argocd/application-sets/kustomization.yaml similarity index 100% rename from platform/control-plane/argocd/application-sets/kustomization.yaml rename to gitops/platform/control-plane/argocd/application-sets/kustomization.yaml diff --git a/platform/control-plane/argocd/application-sets/platform-addons.yaml b/gitops/platform/control-plane/argocd/application-sets/platform-addons.yaml similarity index 100% rename from platform/control-plane/argocd/application-sets/platform-addons.yaml rename to gitops/platform/control-plane/argocd/application-sets/platform-addons.yaml diff --git a/platform/control-plane/argocd/application-sets/platform-services.yaml b/gitops/platform/control-plane/argocd/application-sets/platform-services.yaml similarity index 96% rename from platform/control-plane/argocd/application-sets/platform-services.yaml rename to gitops/platform/control-plane/argocd/application-sets/platform-services.yaml index 90b70e6..8a7ad24 100644 --- a/platform/control-plane/argocd/application-sets/platform-services.yaml +++ b/gitops/platform/control-plane/argocd/application-sets/platform-services.yaml @@ -17,7 +17,7 @@ spec: cluster: dev-k3s server: https://kubernetes.default.svc namespace: vault - path: clusters/dev-k3s/overlays/platform/vault + path: gitops/clusters/dev-k3s/overlays/platform/vault autoSync: "true" template: metadata: diff --git a/platform/control-plane/argocd/application-sets/systems.yaml b/gitops/platform/control-plane/argocd/application-sets/systems.yaml similarity index 96% rename from platform/control-plane/argocd/application-sets/systems.yaml rename to gitops/platform/control-plane/argocd/application-sets/systems.yaml index 0ea75de..da60ded 100644 --- a/platform/control-plane/argocd/application-sets/systems.yaml +++ b/gitops/platform/control-plane/argocd/application-sets/systems.yaml @@ -17,7 +17,7 @@ spec: cluster: dev-k3s server: https://kubernetes.default.svc namespace: auth-system-dev - path: clusters/dev-k3s/overlays/systems/auth-system + path: gitops/clusters/dev-k3s/overlays/systems/auth-system autoSync: "false" template: metadata: diff --git a/platform/control-plane/argocd/application-sets/workloads.yaml b/gitops/platform/control-plane/argocd/application-sets/workloads.yaml similarity index 92% rename from platform/control-plane/argocd/application-sets/workloads.yaml rename to gitops/platform/control-plane/argocd/application-sets/workloads.yaml index 2edd9a2..a130ec9 100644 --- a/platform/control-plane/argocd/application-sets/workloads.yaml +++ b/gitops/platform/control-plane/argocd/application-sets/workloads.yaml @@ -17,13 +17,13 @@ spec: cluster: dev-k3s server: https://kubernetes.default.svc namespace: auth-dev - path: clusters/dev-k3s/overlays/workloads/auth-server + path: gitops/clusters/dev-k3s/overlays/workloads/auth-server autoSync: "false" - component: api-server cluster: dev-k3s server: https://kubernetes.default.svc namespace: api-dev - path: clusters/dev-k3s/overlays/workloads/api-server + path: gitops/clusters/dev-k3s/overlays/workloads/api-server autoSync: "false" template: metadata: diff --git a/platform/control-plane/argocd/kustomization.yaml b/gitops/platform/control-plane/argocd/kustomization.yaml similarity index 100% rename from platform/control-plane/argocd/kustomization.yaml rename to gitops/platform/control-plane/argocd/kustomization.yaml diff --git a/platform/control-plane/argocd/projects/kustomization.yaml b/gitops/platform/control-plane/argocd/projects/kustomization.yaml similarity index 100% rename from platform/control-plane/argocd/projects/kustomization.yaml rename to gitops/platform/control-plane/argocd/projects/kustomization.yaml diff --git a/platform/control-plane/argocd/projects/platform-addons.yaml b/gitops/platform/control-plane/argocd/projects/platform-addons.yaml similarity index 100% rename from platform/control-plane/argocd/projects/platform-addons.yaml rename to gitops/platform/control-plane/argocd/projects/platform-addons.yaml diff --git a/platform/control-plane/argocd/projects/platform-services.yaml b/gitops/platform/control-plane/argocd/projects/platform-services.yaml similarity index 100% rename from platform/control-plane/argocd/projects/platform-services.yaml rename to gitops/platform/control-plane/argocd/projects/platform-services.yaml diff --git a/platform/control-plane/argocd/projects/systems.yaml b/gitops/platform/control-plane/argocd/projects/systems.yaml similarity index 100% rename from platform/control-plane/argocd/projects/systems.yaml rename to gitops/platform/control-plane/argocd/projects/systems.yaml diff --git a/platform/control-plane/argocd/projects/workloads.yaml b/gitops/platform/control-plane/argocd/projects/workloads.yaml similarity index 100% rename from platform/control-plane/argocd/projects/workloads.yaml rename to gitops/platform/control-plane/argocd/projects/workloads.yaml diff --git a/gitops/platform/shared-services/vault/README.md b/gitops/platform/shared-services/vault/README.md new file mode 100644 index 0000000..71f78fa --- /dev/null +++ b/gitops/platform/shared-services/vault/README.md @@ -0,0 +1,8 @@ +# Vault shared service + +여러 system이 소비하는 dev Vault service의 Kubernetes base입니다. Namespace, +single-node profile과 NetworkPolicy는 +`gitops/clusters/dev-k3s/overlays/platform/vault`에서 결합합니다. + +Vault API object는 이 경로가 아니라 `infrastructure/live/dev-k3s`의 세 +Terraform state가 소유합니다. diff --git a/platform/shared-services/vault/base/files/vault/vault.hcl b/gitops/platform/shared-services/vault/base/files/vault/vault.hcl similarity index 100% rename from platform/shared-services/vault/base/files/vault/vault.hcl rename to gitops/platform/shared-services/vault/base/files/vault/vault.hcl diff --git a/platform/shared-services/vault/base/kustomization.yaml b/gitops/platform/shared-services/vault/base/kustomization.yaml similarity index 100% rename from platform/shared-services/vault/base/kustomization.yaml rename to gitops/platform/shared-services/vault/base/kustomization.yaml diff --git a/platform/shared-services/vault/base/vault-auth-delegator.clusterrolebinding.yaml b/gitops/platform/shared-services/vault/base/vault-auth-delegator.clusterrolebinding.yaml similarity index 100% rename from platform/shared-services/vault/base/vault-auth-delegator.clusterrolebinding.yaml rename to gitops/platform/shared-services/vault/base/vault-auth-delegator.clusterrolebinding.yaml diff --git a/platform/shared-services/vault/base/vault-deployment.yaml b/gitops/platform/shared-services/vault/base/vault-deployment.yaml similarity index 100% rename from platform/shared-services/vault/base/vault-deployment.yaml rename to gitops/platform/shared-services/vault/base/vault-deployment.yaml diff --git a/platform/shared-services/vault/base/vault-pvc.yaml b/gitops/platform/shared-services/vault/base/vault-pvc.yaml similarity index 100% rename from platform/shared-services/vault/base/vault-pvc.yaml rename to gitops/platform/shared-services/vault/base/vault-pvc.yaml diff --git a/platform/shared-services/vault/base/vault-service.yaml b/gitops/platform/shared-services/vault/base/vault-service.yaml similarity index 100% rename from platform/shared-services/vault/base/vault-service.yaml rename to gitops/platform/shared-services/vault/base/vault-service.yaml diff --git a/platform/shared-services/vault/base/vault-serviceaccount.yaml b/gitops/platform/shared-services/vault/base/vault-serviceaccount.yaml similarity index 100% rename from platform/shared-services/vault/base/vault-serviceaccount.yaml rename to gitops/platform/shared-services/vault/base/vault-serviceaccount.yaml diff --git a/gitops/policies/README.md b/gitops/policies/README.md new file mode 100644 index 0000000..6cf111d --- /dev/null +++ b/gitops/policies/README.md @@ -0,0 +1,18 @@ +# Policy Catalog + +cluster-wide admission 규칙과 거버넌스 정책을 둡니다. Kyverno, Gatekeeper 등 +정책 엔진은 하나를 선택하고 ADR로 기록합니다. 구체적인 Namespace, +Role/RoleBinding, ResourceQuota와 NetworkPolicy 인스턴스는 `tenants`가 +소유하며 이곳에서 중복 생성하지 않습니다. + +권장 분류: + +- baseline workload security +- allowed registries와 image 검증 +- resource request/limit +- namespace/RBAC 생성 규칙 검증 +- network isolation 적용 여부 검증 +- 정책 예외와 만료 조건 + +처음에는 audit 모드와 테스트로 영향 범위를 확인한 뒤 enforcement를 적용합니다. +예외에는 owner, 사유와 만료일을 반드시 기록합니다. diff --git a/gitops/policies/_template/README.md b/gitops/policies/_template/README.md new file mode 100644 index 0000000..27f7bd7 --- /dev/null +++ b/gitops/policies/_template/README.md @@ -0,0 +1,17 @@ +# __REPLACE_ME_POLICY_SET_NAME__ + +## 목적과 범위 + +보호하는 대상, 위협과 제외 범위를 적습니다. + +## 적용 단계 + +audit 결과, enforcement 전환 조건과 rollback을 적습니다. + +## 예외 + +예외 schema, 승인자와 만료 정책을 적습니다. + +## 테스트 + +허용/거부 fixture와 선택한 policy engine의 test 명령을 추가합니다. diff --git a/gitops/policies/_template/base/kustomization.yaml b/gitops/policies/_template/base/kustomization.yaml new file mode 100644 index 0000000..e609fb2 --- /dev/null +++ b/gitops/policies/_template/base/kustomization.yaml @@ -0,0 +1,4 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: [] diff --git a/gitops/tenants/README.md b/gitops/tenants/README.md new file mode 100644 index 0000000..9e1d4c6 --- /dev/null +++ b/gitops/tenants/README.md @@ -0,0 +1,16 @@ +# Tenant Catalog + +여러 팀 또는 workload 경계를 운영할 때 사용하는 선택 영역입니다. + +가능한 리소스: + +- Namespace와 ownership label +- Role/RoleBinding +- ResourceQuota와 LimitRange +- 기본 NetworkPolicy +- secret manager/service account 연결 + +작은 단일 팀 구성에서는 이 계층을 생략하고 platform 또는 app 소유권에 맞게 +namespace를 관리할 수 있습니다. tenant와 app이 같은 namespace를 중복 생성하지 +않도록 한쪽만 소유합니다. `policies`는 이런 namespace-scoped 리소스 자체가 +아니라 조직 규칙을 검증하는 admission 정책만 소유합니다. diff --git a/gitops/tenants/_template/README.md b/gitops/tenants/_template/README.md new file mode 100644 index 0000000..d204772 --- /dev/null +++ b/gitops/tenants/_template/README.md @@ -0,0 +1,19 @@ +# __REPLACE_ME_TENANT_NAME__ + +## 소유자와 범위 + +Team: __REPLACE_ME_OWNER__ + +namespace, cluster 범위와 연락처를 적습니다. + +## 권한 + +최소 RBAC와 workload identity contract를 적습니다. + +## Guardrail + +quota, limit, network와 승인된 policy exception ID/문서 참조를 적습니다. + +## 온보딩/오프보딩 + +생성, 권한 회수, 데이터 보존과 namespace 삭제 절차를 적습니다. diff --git a/gitops/tenants/_template/base/kustomization.yaml b/gitops/tenants/_template/base/kustomization.yaml new file mode 100644 index 0000000..e609fb2 --- /dev/null +++ b/gitops/tenants/_template/base/kustomization.yaml @@ -0,0 +1,4 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: [] diff --git a/infrastructure/.iac-engine b/infrastructure/.iac-engine new file mode 100644 index 0000000..5ac7c57 --- /dev/null +++ b/infrastructure/.iac-engine @@ -0,0 +1 @@ +terraform diff --git a/infrastructure/.iac-engine.example b/infrastructure/.iac-engine.example new file mode 100644 index 0000000..e5295e7 --- /dev/null +++ b/infrastructure/.iac-engine.example @@ -0,0 +1,4 @@ +# Copy this file to .iac-engine after choosing one engine. +# Leave exactly one uncommented value in the copied file: +# tofu +# terraform diff --git a/infrastructure/README.md b/infrastructure/README.md new file mode 100644 index 0000000..6ec7860 --- /dev/null +++ b/infrastructure/README.md @@ -0,0 +1,33 @@ +# Infrastructure + +클라우드 또는 온프레미스의 네트워크, identity, DNS, registry와 Kubernetes +cluster 같은 기반 리소스를 코드로 관리합니다. + +```text +components reusable primitive + │ + ├──────────────┐ + ▼ ▼ + stacks live + │ ▲ + └──────────────┘ +``` + +- `components`: 작고 재사용 가능한 구현 단위 +- `stacks`: 반복되는 component 조합(선택) +- `live`: 실제 environment root와 state 경계 +- `tests`: component와 contract 검증 + +IaC 엔진은 프로젝트에서 하나를 선택합니다. Terraform/OpenTofu 호환이 필요하면 +같은 `.tf` 구성을 공유하고 실행 명령만 프로젝트 표준으로 통일합니다. 서로 다른 +엔진용으로 동일한 인프라 트리를 복제하지 않습니다. + +IaC 파일을 추가할 때 `.iac-engine.example`을 `.iac-engine`으로 복사하고 +`tofu` 또는 `terraform` 중 하나만 기록합니다. 이 선택 파일은 커밋합니다. +선택한 실행 파일과 version을 로컬/CI에 고정하고, 프로젝트별 `init -backend=false` +및 semantic validate 단계도 검증 스크립트에 추가합니다. 기본 스켈레톤의 자동 +검사는 provider를 선택하지 않았기 때문에 IaC format까지만 수행합니다. + +Kubernetes API 안의 platform/app 리소스는 원칙적으로 `gitops`가 소유합니다. +클러스터 생성 시 반드시 필요한 최소 bootstrap 출력만 명시적인 contract로 +전달합니다. diff --git a/infrastructure/components/README.md b/infrastructure/components/README.md new file mode 100644 index 0000000..ae66219 --- /dev/null +++ b/infrastructure/components/README.md @@ -0,0 +1,24 @@ +# Infrastructure Components + +작고 응집된 재사용 단위를 둡니다. + +예: + +```text +components/ +├── aws/ +│ ├── network/ +│ ├── identity/ +│ └── eks/ +├── gcp/ +│ ├── network/ +│ └── gke/ +└── shared/ + └── naming/ +``` + +실제로 사용하는 provider 경로만 만듭니다. component에는 environment backend, +실제 credential과 환경 고유 값을 두지 않습니다. 입력, 출력, version constraint, +권한 요구 사항과 사용 예를 component README에 기록합니다. + +새 component는 `_template`을 복사해 시작합니다. diff --git a/infrastructure/components/_template/README.md b/infrastructure/components/_template/README.md new file mode 100644 index 0000000..f32f28b --- /dev/null +++ b/infrastructure/components/_template/README.md @@ -0,0 +1,29 @@ +# __REPLACE_ME_COMPONENT_NAME__ + +## 책임 + +이 component가 생성하고 소유하는 리소스를 적습니다. + +## 입력 + +필수/선택 입력과 민감 정보 여부를 적습니다. + +## 출력 + +다른 component 또는 live root에 제공하는 안정적인 contract를 적습니다. + +## 요구 권한 + +plan/apply에 필요한 최소 provider 권한을 적습니다. + +## 사용 예 + +실제 credential, account ID와 운영 값을 포함하지 않는 호출 예를 적습니다. + +## 구현 체크리스트 + +- [ ] backend를 선언하지 않는다. +- [ ] provider/version constraint를 명시한다. +- [ ] 입력 validation과 민감 output 표시를 추가한다. +- [ ] 환경 이름을 내부에 하드코딩하지 않는다. +- [ ] README와 테스트를 함께 갱신한다. diff --git a/infrastructure/components/vault-kubernetes-roles/README.md b/infrastructure/components/vault-kubernetes-roles/README.md new file mode 100644 index 0000000..f3d1256 --- /dev/null +++ b/infrastructure/components/vault-kubernetes-roles/README.md @@ -0,0 +1,16 @@ +# Vault Kubernetes roles + +## 책임 + +Vault Kubernetes auth role을 입력 map에서 생성하는 backend 없는 재사용 +Terraform component입니다. + +## 입력과 출력 + +- 입력: auth backend 경로와 role별 audience, ServiceAccount, namespace, + policy, TTL +- 출력: 없음 +- 민감 payload: 없음 + +호출하는 live root가 provider와 state를 소유합니다. 이 component는 환경 +이름이나 credential을 내부에 저장하지 않습니다. diff --git a/iac/terraform/modules/vault-kubernetes-roles/main.tf b/infrastructure/components/vault-kubernetes-roles/main.tf similarity index 100% rename from iac/terraform/modules/vault-kubernetes-roles/main.tf rename to infrastructure/components/vault-kubernetes-roles/main.tf diff --git a/iac/terraform/modules/vault-kubernetes-roles/variables.tf b/infrastructure/components/vault-kubernetes-roles/variables.tf similarity index 100% rename from iac/terraform/modules/vault-kubernetes-roles/variables.tf rename to infrastructure/components/vault-kubernetes-roles/variables.tf diff --git a/iac/terraform/modules/vault-kubernetes-roles/versions.tf b/infrastructure/components/vault-kubernetes-roles/versions.tf similarity index 100% rename from iac/terraform/modules/vault-kubernetes-roles/versions.tf rename to infrastructure/components/vault-kubernetes-roles/versions.tf diff --git a/infrastructure/components/vault-policy-set/README.md b/infrastructure/components/vault-policy-set/README.md new file mode 100644 index 0000000..1e96553 --- /dev/null +++ b/infrastructure/components/vault-policy-set/README.md @@ -0,0 +1,15 @@ +# Vault policy set + +## 책임 + +이름과 HCL 문서 map을 Vault ACL policy로 만드는 backend 없는 재사용 +Terraform component입니다. + +## 입력과 출력 + +- 입력: policy 이름별 HCL 문서 +- 출력: 요청 이름별 생성된 Vault policy 이름 +- 민감 payload: 없음 + +Policy 문서는 이를 소비하는 live root가 소유하며, 이 component는 API 객체 +생성만 캡슐화합니다. diff --git a/iac/terraform/modules/vault-policy-set/main.tf b/infrastructure/components/vault-policy-set/main.tf similarity index 100% rename from iac/terraform/modules/vault-policy-set/main.tf rename to infrastructure/components/vault-policy-set/main.tf diff --git a/iac/terraform/modules/vault-policy-set/variables.tf b/infrastructure/components/vault-policy-set/variables.tf similarity index 100% rename from iac/terraform/modules/vault-policy-set/variables.tf rename to infrastructure/components/vault-policy-set/variables.tf diff --git a/iac/terraform/modules/vault-policy-set/versions.tf b/infrastructure/components/vault-policy-set/versions.tf similarity index 100% rename from iac/terraform/modules/vault-policy-set/versions.tf rename to infrastructure/components/vault-policy-set/versions.tf diff --git a/infrastructure/live/README.md b/infrastructure/live/README.md new file mode 100644 index 0000000..25bd3f6 --- /dev/null +++ b/infrastructure/live/README.md @@ -0,0 +1,38 @@ +# Live Infrastructure + +실제로 plan/apply하는 root를 둡니다. leaf 디렉터리 하나가 독립적인 state, +locking, 권한과 실패 범위입니다. + +소규모 예: + +```text +live/ +└── dev/ + ├── network/ + └── cluster/ +``` + +확장 예: + +```text +live/ +└── aws/ + └── platform-prod/ + └── ap-northeast-2/ + └── prod/ + ├── network/ + ├── shared-services/ + └── cluster-a/ +``` + +경로 깊이보다 leaf의 실행 계약이 중요합니다. 자동화는 특정 depth를 가정하지 +말고 IaC root marker를 기준으로 대상을 찾습니다. + +## 규칙 + +- 각 root는 remote backend와 locking을 사용합니다. +- production과 non-production state/credential을 분리합니다. +- 민감하지 않은 입력만 커밋하며 secret 입력은 runtime에 주입합니다. +- 다른 환경 경로를 상대 import하지 않습니다. +- provider, module과 component version을 고정합니다. +- output consumer와 파괴 영향 범위를 README에 기록합니다. diff --git a/infrastructure/live/_template/README.md b/infrastructure/live/_template/README.md new file mode 100644 index 0000000..66d0424 --- /dev/null +++ b/infrastructure/live/_template/README.md @@ -0,0 +1,34 @@ +# __REPLACE_ME_LIVE_ROOT_NAME__ + +## 대상 + +- Provider/account/project: __REPLACE_ME_SCOPE__ +- Region: __REPLACE_ME_REGION_OR_GLOBAL__ +- Environment: __REPLACE_ME_ENVIRONMENT__ +- Stack: __REPLACE_ME_STACK__ +- Owner: __REPLACE_ME_OWNER__ + +## State + +- Backend: __REPLACE_ME_BACKEND__ +- Locking: +- Encryption: +- Recovery runbook: + +## 의존성 + +선행 state/output과 사용하는 component/stack version을 적습니다. + +## Output Contract + +downstream bootstrap 또는 system에 전달하는 값을 적습니다. 민감 output은 +명시적으로 표시하고 로그에 출력하지 않습니다. + +## 실행 + +프로젝트가 선택한 IaC 엔진의 init/plan/apply 절차를 적습니다. production은 +검토된 plan, 승인과 concurrency lock 없이 적용하지 않습니다. + +## 롤백/복구 + +되돌릴 수 있는 변경과 state 복구 절차 링크를 적습니다. diff --git a/iac/terraform/live/dev-k3s/vault-database/.terraform.lock.hcl b/infrastructure/live/dev-k3s/vault-database/.terraform.lock.hcl similarity index 100% rename from iac/terraform/live/dev-k3s/vault-database/.terraform.lock.hcl rename to infrastructure/live/dev-k3s/vault-database/.terraform.lock.hcl diff --git a/infrastructure/live/dev-k3s/vault-database/README.md b/infrastructure/live/dev-k3s/vault-database/README.md new file mode 100644 index 0000000..9df7f21 --- /dev/null +++ b/infrastructure/live/dev-k3s/vault-database/README.md @@ -0,0 +1,14 @@ +# dev-k3s Vault database + +## 대상과 State + +- Environment/cluster: `dev-k3s` +- Provider: HashiCorp Vault +- State: `vault-database` +- Backend example: `backend.s3.hcl.example` +- Owner: delegated database automation + +Project Auth PostgreSQL connection과 migration dynamic role만 소유합니다. +PostgreSQL이 준비되고 연결 입력을 안전하게 주입할 수 있을 때 별도 승인으로 +실행합니다. Database password는 ephemeral/write-only 입력이며 state나 +repository에 저장하지 않습니다. diff --git a/iac/terraform/backend/dev-k3s/vault-database.s3.hcl.example b/infrastructure/live/dev-k3s/vault-database/backend.s3.hcl.example similarity index 100% rename from iac/terraform/backend/dev-k3s/vault-database.s3.hcl.example rename to infrastructure/live/dev-k3s/vault-database/backend.s3.hcl.example diff --git a/iac/terraform/live/dev-k3s/vault-database/main.tf b/infrastructure/live/dev-k3s/vault-database/main.tf similarity index 100% rename from iac/terraform/live/dev-k3s/vault-database/main.tf rename to infrastructure/live/dev-k3s/vault-database/main.tf diff --git a/iac/terraform/live/dev-k3s/vault-database/variables.tf b/infrastructure/live/dev-k3s/vault-database/variables.tf similarity index 100% rename from iac/terraform/live/dev-k3s/vault-database/variables.tf rename to infrastructure/live/dev-k3s/vault-database/variables.tf diff --git a/iac/terraform/live/dev-k3s/vault-foundation/.terraform.lock.hcl b/infrastructure/live/dev-k3s/vault-foundation/.terraform.lock.hcl similarity index 100% rename from iac/terraform/live/dev-k3s/vault-foundation/.terraform.lock.hcl rename to infrastructure/live/dev-k3s/vault-foundation/.terraform.lock.hcl diff --git a/infrastructure/live/dev-k3s/vault-foundation/README.md b/infrastructure/live/dev-k3s/vault-foundation/README.md new file mode 100644 index 0000000..c861779 --- /dev/null +++ b/infrastructure/live/dev-k3s/vault-foundation/README.md @@ -0,0 +1,19 @@ +# dev-k3s Vault foundation + +## 대상과 State + +- Environment/cluster: `dev-k3s` +- Provider: HashiCorp Vault +- State: `vault-foundation` +- Backend example: `backend.s3.hcl.example` +- Owner: bootstrap/security administrator + +Vault mount, Kubernetes auth config, delegated automation policy와 선택적 CI JWT +role을 소유합니다. Routine automation 대상이 아니며 downstream state가 자기 +실행 권한을 직접 만들지 않도록 합니다. + +`policies/`는 이 state가 소유하는 정확한 delegated automation ACL입니다. +실행과 복구 절차는 +[`docs/runbooks/dev-bootstrap.md`](../../../../docs/runbooks/dev-bootstrap.md)와 +[`docs/runbooks/terraform-state-migration.md`](../../../../docs/runbooks/terraform-state-migration.md)를 +따릅니다. diff --git a/iac/terraform/backend/dev-k3s/vault-foundation.s3.hcl.example b/infrastructure/live/dev-k3s/vault-foundation/backend.s3.hcl.example similarity index 100% rename from iac/terraform/backend/dev-k3s/vault-foundation.s3.hcl.example rename to infrastructure/live/dev-k3s/vault-foundation/backend.s3.hcl.example diff --git a/iac/terraform/live/dev-k3s/vault-foundation/main.tf b/infrastructure/live/dev-k3s/vault-foundation/main.tf similarity index 98% rename from iac/terraform/live/dev-k3s/vault-foundation/main.tf rename to infrastructure/live/dev-k3s/vault-foundation/main.tf index cefa01f..c1f9615 100644 --- a/iac/terraform/live/dev-k3s/vault-foundation/main.tf +++ b/infrastructure/live/dev-k3s/vault-foundation/main.tf @@ -65,7 +65,7 @@ removed { } locals { - platform_policy_dir = "${path.module}/../../../../../policies/vault/dev-k3s/platform" + platform_policy_dir = "${path.module}/policies" database_automation_policy_name = "vault-database-automation-dev" database_mount_path = "database" diff --git a/policies/vault/dev-k3s/platform/vault-database-automation-dev.hcl b/infrastructure/live/dev-k3s/vault-foundation/policies/vault-database-automation-dev.hcl similarity index 100% rename from policies/vault/dev-k3s/platform/vault-database-automation-dev.hcl rename to infrastructure/live/dev-k3s/vault-foundation/policies/vault-database-automation-dev.hcl diff --git a/policies/vault/dev-k3s/platform/vault-workloads-automation-dev.hcl b/infrastructure/live/dev-k3s/vault-foundation/policies/vault-workloads-automation-dev.hcl similarity index 100% rename from policies/vault/dev-k3s/platform/vault-workloads-automation-dev.hcl rename to infrastructure/live/dev-k3s/vault-foundation/policies/vault-workloads-automation-dev.hcl diff --git a/iac/terraform/live/dev-k3s/vault-foundation/variables.tf b/infrastructure/live/dev-k3s/vault-foundation/variables.tf similarity index 100% rename from iac/terraform/live/dev-k3s/vault-foundation/variables.tf rename to infrastructure/live/dev-k3s/vault-foundation/variables.tf diff --git a/iac/terraform/live/dev-k3s/vault-workloads/.terraform.lock.hcl b/infrastructure/live/dev-k3s/vault-workloads/.terraform.lock.hcl similarity index 100% rename from iac/terraform/live/dev-k3s/vault-workloads/.terraform.lock.hcl rename to infrastructure/live/dev-k3s/vault-workloads/.terraform.lock.hcl diff --git a/infrastructure/live/dev-k3s/vault-workloads/README.md b/infrastructure/live/dev-k3s/vault-workloads/README.md new file mode 100644 index 0000000..d4ff8eb --- /dev/null +++ b/infrastructure/live/dev-k3s/vault-workloads/README.md @@ -0,0 +1,16 @@ +# dev-k3s Vault workloads + +## 대상과 State + +- Environment/cluster: `dev-k3s` +- Provider: HashiCorp Vault +- State: `vault-workloads` +- Backend example: `backend.s3.hcl.example` +- Owner: delegated workload automation + +정확한 workload ACL, Kubernetes auth role와 Project Auth Transit key를 +소유합니다. `policies/`의 ACL은 wildcard 없이 workload가 실제 사용하는 +경로만 허용합니다. + +Foundation이 만든 short-lived identity로 실행하며, 이 state는 자기 실행 +policy/login role을 생성하지 않습니다. diff --git a/iac/terraform/backend/dev-k3s/vault-workloads.s3.hcl.example b/infrastructure/live/dev-k3s/vault-workloads/backend.s3.hcl.example similarity index 100% rename from iac/terraform/backend/dev-k3s/vault-workloads.s3.hcl.example rename to infrastructure/live/dev-k3s/vault-workloads/backend.s3.hcl.example diff --git a/iac/terraform/live/dev-k3s/vault-workloads/main.tf b/infrastructure/live/dev-k3s/vault-workloads/main.tf similarity index 94% rename from iac/terraform/live/dev-k3s/vault-workloads/main.tf rename to infrastructure/live/dev-k3s/vault-workloads/main.tf index 879de8f..fc33010 100644 --- a/iac/terraform/live/dev-k3s/vault-workloads/main.tf +++ b/infrastructure/live/dev-k3s/vault-workloads/main.tf @@ -18,7 +18,7 @@ provider "vault" { } locals { - workload_policy_dir = "${path.module}/../../../../../policies/vault/dev-k3s/workloads" + workload_policy_dir = "${path.module}/policies" jwt_transit_key_name = "project-auth-jwt" kubernetes_auth_path = "kubernetes" @@ -35,13 +35,13 @@ locals { } module "workload_policies" { - source = "../../../modules/vault-policy-set" + source = "../../../components/vault-policy-set" policies = local.workload_policies } module "workload_roles" { - source = "../../../modules/vault-kubernetes-roles" + source = "../../../components/vault-kubernetes-roles" backend = local.kubernetes_auth_path roles = { diff --git a/policies/vault/dev-k3s/workloads/auth-db-migration-dev.hcl b/infrastructure/live/dev-k3s/vault-workloads/policies/auth-db-migration-dev.hcl similarity index 100% rename from policies/vault/dev-k3s/workloads/auth-db-migration-dev.hcl rename to infrastructure/live/dev-k3s/vault-workloads/policies/auth-db-migration-dev.hcl diff --git a/policies/vault/dev-k3s/workloads/auth-server-dev.hcl b/infrastructure/live/dev-k3s/vault-workloads/policies/auth-server-dev.hcl similarity index 100% rename from policies/vault/dev-k3s/workloads/auth-server-dev.hcl rename to infrastructure/live/dev-k3s/vault-workloads/policies/auth-server-dev.hcl diff --git a/policies/vault/dev-k3s/workloads/keycloak-client-sync-dev.hcl b/infrastructure/live/dev-k3s/vault-workloads/policies/keycloak-client-sync-dev.hcl similarity index 100% rename from policies/vault/dev-k3s/workloads/keycloak-client-sync-dev.hcl rename to infrastructure/live/dev-k3s/vault-workloads/policies/keycloak-client-sync-dev.hcl diff --git a/policies/vault/dev-k3s/workloads/keycloak-dev.hcl b/infrastructure/live/dev-k3s/vault-workloads/policies/keycloak-dev.hcl similarity index 100% rename from policies/vault/dev-k3s/workloads/keycloak-dev.hcl rename to infrastructure/live/dev-k3s/vault-workloads/policies/keycloak-dev.hcl diff --git a/policies/vault/dev-k3s/workloads/postgres-dev.hcl b/infrastructure/live/dev-k3s/vault-workloads/policies/postgres-dev.hcl similarity index 100% rename from policies/vault/dev-k3s/workloads/postgres-dev.hcl rename to infrastructure/live/dev-k3s/vault-workloads/policies/postgres-dev.hcl diff --git a/iac/terraform/live/dev-k3s/vault-workloads/variables.tf b/infrastructure/live/dev-k3s/vault-workloads/variables.tf similarity index 100% rename from iac/terraform/live/dev-k3s/vault-workloads/variables.tf rename to infrastructure/live/dev-k3s/vault-workloads/variables.tf diff --git a/infrastructure/stacks/README.md b/infrastructure/stacks/README.md new file mode 100644 index 0000000..5edda95 --- /dev/null +++ b/infrastructure/stacks/README.md @@ -0,0 +1,13 @@ +# Infrastructure Stacks + +여러 component 조합이 두 개 이상의 live root에서 반복될 때 사용하는 선택 계층입니다. + +예: + +- `regional-foundation`: network + shared identity + DNS +- `cluster`: Kubernetes cluster + node pools + workload identity +- `edge`: CDN + load balancer + certificate + +작은 프로젝트에서는 `live`가 component를 직접 호출하고 이 계층을 생략합니다. +stack은 실행 가능한 environment root가 아니므로 backend와 실제 credential을 +두지 않습니다. stack 중첩을 깊게 만들기보다 live root에서 평평하게 조합합니다. diff --git a/infrastructure/stacks/_template/README.md b/infrastructure/stacks/_template/README.md new file mode 100644 index 0000000..ea17743 --- /dev/null +++ b/infrastructure/stacks/_template/README.md @@ -0,0 +1,19 @@ +# __REPLACE_ME_STACK_NAME__ + +## 목적 + +반복해서 함께 배포하는 component 조합을 설명합니다. + +## 포함 Component + +각 component의 version과 책임을 적습니다. + +## 입력과 출력 + +live root에 노출하는 최소 interface를 적습니다. + +## 제약 + +- backend와 environment credential을 선언하지 않습니다. +- 특정 live 경로를 역참조하지 않습니다. +- component가 한 번만 필요하면 stack 계층을 만들지 않습니다. diff --git a/infrastructure/tests/README.md b/infrastructure/tests/README.md new file mode 100644 index 0000000..7084935 --- /dev/null +++ b/infrastructure/tests/README.md @@ -0,0 +1,13 @@ +# Infrastructure Tests + +IaC를 선택한 뒤 다음 검증을 필요에 따라 추가합니다. + +- formatter와 syntax/validate +- component input/output contract test +- policy/static analysis +- ephemeral account/project integration test +- upgrade와 state migration test + +실제 cloud 통합 테스트는 일반 PR 검증과 분리하고, 짧은 수명의 identity와 +격리된 account/project를 사용합니다. 테스트가 production state를 읽거나 +변경해서는 안 됩니다. diff --git a/renovate.json b/renovate.json index 4b938cf..d955cfd 100644 --- a/renovate.json +++ b/renovate.json @@ -14,7 +14,7 @@ { "customType": "regex", "managerFilePatterns": [ - "/^platform/control-plane/argocd/application-sets/platform-addons\\.yaml$/" + "/^gitops/platform/control-plane/argocd/application-sets/platform-addons\\.yaml$/" ], "matchStrings": [ "# renovate: datasource=(?\\S+) depName=(?\\S+) registryUrl=(?\\S+)\\s+revision: (?\\S+)" @@ -24,7 +24,7 @@ "customType": "regex", "datasourceTemplate": "docker", "managerFilePatterns": [ - "/^platform/control-plane/argocd/application-sets/platform-addons\\.yaml$/" + "/^gitops/platform/control-plane/argocd/application-sets/platform-addons\\.yaml$/" ], "matchStrings": [ "# renovate: datasource=docker depName=(?\\S+)\\s+repository: \\S+\\s+tag: \"(?[^@\"]+)@(?sha256:[a-f0-9]{64})\"" @@ -48,12 +48,12 @@ { "description": "Require review for cluster controllers and stateful platform components", "matchFileNames": [ - "platform/control-plane/**", - "clusters/dev-k3s/overlays/platform/**", - "clusters/dev-k3s/overlays/systems/**", - "platform/**", - "systems/**", - "iac/**" + "gitops/platform/control-plane/**", + "gitops/clusters/dev-k3s/overlays/platform/**", + "gitops/clusters/dev-k3s/overlays/systems/**", + "gitops/platform/**", + "gitops/apps/systems/**", + "infrastructure/**" ], "automerge": false }, diff --git a/scripts/README.md b/scripts/README.md new file mode 100644 index 0000000..b5fd41b --- /dev/null +++ b/scripts/README.md @@ -0,0 +1,23 @@ +# Scripts + +로컬과 CI가 동일하게 사용하는 얇고 명시적인 자동화만 둡니다. + +- `doctor.sh`: 필수/선택 도구 가용성 확인 +- `validate.sh`: 템플릿 공통 구조/보안/렌더 검증 후 프로젝트 검증 실행 +- `project-validate.sh`: Argo CD, Vault, Terraform의 프로젝트별 계약 검증 +- `bootstrap-argocd.sh`: 확인된 context에 Argo CD bootstrap 수행 +- `vault-init.sh`: 개발 Vault의 init/unseal/revoke-root ceremony + +검증 중 도구를 몰래 다운로드하거나 환경을 변경하지 않습니다. 실제 IaC, +policy, secret 도구를 선택하면 `doctor.sh`의 필수 목록과 `validate.sh`의 +검증을 함께 확장합니다. + +IaC source가 있으면 tracked `infrastructure/.iac-engine` 선택을 요구합니다. +기본 검사는 provider-neutral하게 유지하기 위해 format까지만 수행하므로, +실제 프로젝트는 root별 `init -backend=false`와 semantic validate를 추가해야 +합니다. 내용 기반 secret scanner와 schema/policy validator도 프로젝트 도구로 +고정해 CI에 추가합니다. + +범용 `apply`/`destroy` 스크립트를 추가하지 않습니다. 배포 스크립트가 필요하면 +대상 environment/root를 필수 입력으로 받고 production 승인과 locking 정책을 +반영합니다. diff --git a/hack/bootstrap-argocd.sh b/scripts/bootstrap-argocd.sh similarity index 89% rename from hack/bootstrap-argocd.sh rename to scripts/bootstrap-argocd.sh index cc74069..f9a95ea 100755 --- a/hack/bootstrap-argocd.sh +++ b/scripts/bootstrap-argocd.sh @@ -29,7 +29,7 @@ if [[ "$current_context" != "$expected_context" ]]; then exit 1 fi -source "${REPO_ROOT}/bootstrap/argocd/version.env" +source "${REPO_ROOT}/bootstrap/gitops/argocd/version.env" manifest="$(mktemp)" cleanup() { @@ -54,8 +54,8 @@ kubectl --context "$expected_context" -n argocd \ kubectl --context "$expected_context" -n argocd \ rollout status deployment/argocd-repo-server --timeout=300s kubectl --context "$expected_context" apply \ - -f "${REPO_ROOT}/bootstrap/argocd/control-plane-project.yaml" + -f "${REPO_ROOT}/bootstrap/gitops/argocd/control-plane-project.yaml" kubectl --context "$expected_context" apply \ - -f "${REPO_ROOT}/bootstrap/argocd/root-application.yaml" + -f "${REPO_ROOT}/bootstrap/gitops/argocd/root-application.yaml" echo "Argo CD ${ARGOCD_VERSION}, its control-plane project, and the root Application are installed." diff --git a/scripts/doctor.sh b/scripts/doctor.sh new file mode 100755 index 0000000..47aecd1 --- /dev/null +++ b/scripts/doctor.sh @@ -0,0 +1,174 @@ +#!/usr/bin/env bash + +set -Eeuo pipefail + +repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "${repo_root}" + +required_tools=( + bash + find + git + grep + kubectl + make + sed +) + +optional_tools=( + age + argocd + conftest + flux + gitleaks + helm + kubeconform + shellcheck + sops + terraform + tofu + trivy + yamllint +) + +activated_tools=() +missing=0 + +collect_source_files() { + if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then + git ls-files --cached --others --exclude-standard -z + else + find . \ + \( \ + -type d \ + \( \ + -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 \ + -type f -print0 + fi +} + +source_files=() +while IFS= read -r -d '' file; do + file="${file#./}" + if [[ -f "${file}" ]]; then + source_files+=("${file}") + fi +done < <(collect_source_files) + +has_chart=0 +iac_file="" +for file in "${source_files[@]}"; do + if [[ "${file##*/}" == "Chart.yaml" ]]; then + has_chart=1 + fi + + case "${file}" in + bootstrap/*.tf | bootstrap/*.tf.json | bootstrap/*.tofu | bootstrap/*.tofu.json | \ + infrastructure/*.tf | infrastructure/*.tf.json | infrastructure/*.tofu | \ + infrastructure/*.tofu.json) + iac_file="${file}" + ;; + esac +done + +if ((has_chart == 1)); then + activated_tools+=("helm") +fi + +if [[ -n "${iac_file}" ]]; then + iac_engine="" + if [[ -f infrastructure/.iac-engine && ! -L infrastructure/.iac-engine ]]; then + iac_engine_values=() + while IFS= read -r line; do + case "${line}" in + "" | \#*) + continue + ;; + esac + iac_engine_values+=("${line}") + done < infrastructure/.iac-engine + + if ((${#iac_engine_values[@]} == 1)); then + iac_engine="${iac_engine_values[0]}" + fi + fi + + case "${iac_engine}" in + terraform | tofu) + activated_tools+=("${iac_engine}") + ;; + *) + printf '[missing] infrastructure/.iac-engine must select terraform or tofu because IaC files exist.\n' >&2 + missing=1 + ;; + esac +fi + +is_activated() { + local candidate="$1" + local tool + + for tool in "${activated_tools[@]}"; do + if [[ "${candidate}" == "${tool}" ]]; then + return 0 + fi + done + return 1 +} + +printf 'Required tools\n' +for tool in "${required_tools[@]}"; do + if command -v "${tool}" >/dev/null 2>&1; then + printf ' [ok] %-14s %s\n' "${tool}" "$(command -v "${tool}")" + else + printf ' [missing] %s\n' "${tool}" + missing=1 + fi +done + +if ((${#activated_tools[@]} > 0)); then + printf '\nTools required by activated source files\n' + for tool in "${activated_tools[@]}"; do + if command -v "${tool}" >/dev/null 2>&1; then + printf ' [ok] %-14s %s\n' "${tool}" "$(command -v "${tool}")" + else + printf ' [missing] %s\n' "${tool}" + missing=1 + fi + done +fi + +printf '\nOptional tools (required only after the related feature is enabled)\n' +for tool in "${optional_tools[@]}"; do + if is_activated "${tool}"; then + continue + fi + + if command -v "${tool}" >/dev/null 2>&1; then + printf ' [found] %-14s %s\n' "${tool}" "$(command -v "${tool}")" + else + printf ' [not set] %s\n' "${tool}" + fi +done + +if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then + printf '\nRepository: Git work tree detected.\n' +else + printf '\nRepository: Git is not initialized yet; run git init when this skeleton becomes a repository.\n' +fi + +if ((missing != 0)); then + printf '\nInstall/configure the missing required items before validation.\n' >&2 + exit 1 +fi + +printf '\nDoctor check passed.\n' diff --git a/hack/validate.sh b/scripts/project-validate.sh similarity index 71% rename from hack/validate.sh rename to scripts/project-validate.sh index a95be2f..2db50c1 100755 --- a/hack/validate.sh +++ b/scripts/project-validate.sh @@ -34,24 +34,25 @@ while IFS= read -r script; do echo "Shell entry point is not executable: ${script}" >&2 exit 1 fi -done < <(rg --files hack -g '*.sh') +done < <(rg --files scripts -g '*.sh') jq empty renovate.json -jq empty systems/auth-system/base/files/keycloak/project-auth-realm.json +jq empty gitops/apps/systems/auth-system/base/files/keycloak/project-auth-realm.json overlays=( - bootstrap/argocd - platform/control-plane/argocd - clusters/dev-k3s/overlays/platform/vault - clusters/dev-k3s/overlays/systems/auth-system - clusters/dev-k3s/overlays/workloads/auth-server - clusters/dev-k3s/overlays/workloads/api-server + bootstrap/gitops/argocd + gitops/clusters/dev-k3s + gitops/platform/control-plane/argocd + gitops/clusters/dev-k3s/overlays/platform/vault + gitops/clusters/dev-k3s/overlays/systems/auth-system + gitops/clusters/dev-k3s/overlays/workloads/auth-server + gitops/clusters/dev-k3s/overlays/workloads/api-server ) for overlay in "${overlays[@]}"; do kubectl kustomize "$overlay" >/dev/null done -control_plane_render="$(kubectl kustomize platform/control-plane/argocd)" +control_plane_render="$(kubectl kustomize gitops/platform/control-plane/argocd)" if [[ "$(rg -c '^kind: AppProject$' <<<"$control_plane_render")" -ne 4 || "$(rg -c '^kind: ApplicationSet$' <<<"$control_plane_render")" -ne 4 ]]; then echo "The control plane must render exactly four AppProjects and four ApplicationSets." >&2 @@ -65,24 +66,27 @@ application_manifests="$( . | sort )" -if [[ "$application_manifests" != "./bootstrap/argocd/root-application.yaml" ]]; then +if [[ "$application_manifests" != "./bootstrap/gitops/argocd/root-application.yaml" ]]; then echo "Only the bootstrap root may be an explicit Argo CD Application:" >&2 echo "$application_manifests" >&2 exit 1 fi if ! rg -q '^[[:space:]]+project:[[:space:]]+gitops-control-plane$' \ - bootstrap/argocd/root-application.yaml || - rg -n '^[[:space:]]+project:[[:space:]]+default$' bootstrap platform/control-plane; then + bootstrap/gitops/argocd/root-application.yaml || + ! rg -q '^[[:space:]]+path:[[:space:]]+gitops/clusters/dev-k3s$' \ + bootstrap/gitops/argocd/root-application.yaml || + rg -n '^[[:space:]]+project:[[:space:]]+default$' \ + bootstrap gitops/platform/control-plane; then echo "The root and generated Applications must use explicit least-privilege AppProjects." >&2 exit 1 fi applicationsets=( - platform/control-plane/argocd/application-sets/platform-addons.yaml - platform/control-plane/argocd/application-sets/platform-services.yaml - platform/control-plane/argocd/application-sets/systems.yaml - platform/control-plane/argocd/application-sets/workloads.yaml + gitops/platform/control-plane/argocd/application-sets/platform-addons.yaml + gitops/platform/control-plane/argocd/application-sets/platform-services.yaml + gitops/platform/control-plane/argocd/application-sets/systems.yaml + gitops/platform/control-plane/argocd/application-sets/workloads.yaml ) for applicationset in "${applicationsets[@]}"; do for safety_setting in \ @@ -98,20 +102,20 @@ for applicationset in "${applicationsets[@]}"; do done if ! rg -q '^ - Prune=confirm$' \ - platform/control-plane/argocd/application-sets/platform-addons.yaml; then + gitops/platform/control-plane/argocd/application-sets/platform-addons.yaml; then echo "Platform addons must require approval before pruning chart resources." >&2 exit 1 fi -if rg -n 'project:[[:space:]]+"?\{\{' platform/control-plane/argocd/application-sets; then +if rg -n 'project:[[:space:]]+"?\{\{' gitops/platform/control-plane/argocd/application-sets; then echo "ApplicationSet projects are privilege boundaries and must never be templated." >&2 exit 1 fi git_applicationsets=( - platform/control-plane/argocd/application-sets/platform-services.yaml - platform/control-plane/argocd/application-sets/systems.yaml - platform/control-plane/argocd/application-sets/workloads.yaml + gitops/platform/control-plane/argocd/application-sets/platform-services.yaml + gitops/platform/control-plane/argocd/application-sets/systems.yaml + gitops/platform/control-plane/argocd/application-sets/workloads.yaml ) for applicationset in "${git_applicationsets[@]}"; do if ! rg -q '^[[:space:]]+targetRevision:[[:space:]]+main$' "$applicationset" || @@ -122,12 +126,12 @@ for applicationset in "${git_applicationsets[@]}"; do done enabled_gates="$( - rg -o 'autoSync: "true"' platform/control-plane/argocd/application-sets | + rg -o 'autoSync: "true"' gitops/platform/control-plane/argocd/application-sets | wc -l | tr -d ' ' )" disabled_gates="$( - rg -o 'autoSync: "false"' platform/control-plane/argocd/application-sets | + rg -o 'autoSync: "false"' gitops/platform/control-plane/argocd/application-sets | wc -l | tr -d ' ' )" @@ -172,8 +176,8 @@ fi rendered_images="$( { - kubectl kustomize clusters/dev-k3s/overlays/systems/auth-system - kubectl kustomize clusters/dev-k3s/overlays/platform/vault + kubectl kustomize gitops/clusters/dev-k3s/overlays/systems/auth-system + kubectl kustomize gitops/clusters/dev-k3s/overlays/platform/vault } | rg '^[[:space:]]+image: (hashicorp/vault|postgres|quay\\.io/keycloak)' )" if printf '%s\n' "$rendered_images" | rg -v '@sha256:[a-f0-9]{64}$'; then @@ -182,8 +186,8 @@ if printf '%s\n' "$rendered_images" | rg -v '@sha256:[a-f0-9]{64}$'; then fi workload_kustomizations=( - clusters/dev-k3s/overlays/workloads/auth-server/kustomization.yaml - clusters/dev-k3s/overlays/workloads/api-server/kustomization.yaml + gitops/clusters/dev-k3s/overlays/workloads/auth-server/kustomization.yaml + gitops/clusters/dev-k3s/overlays/workloads/api-server/kustomization.yaml ) for workload_kustomization in "${workload_kustomizations[@]}"; do if rg -q '^[[:space:]]+digest:[[:space:]]+sha256:[a-f0-9]{64}$' \ @@ -197,11 +201,11 @@ for workload_kustomization in "${workload_kustomizations[@]}"; do fi done -terraform fmt -check -recursive iac/terraform +terraform fmt -check -recursive infrastructure terraform_roots=( - iac/terraform/live/dev-k3s/vault-foundation - iac/terraform/live/dev-k3s/vault-workloads - iac/terraform/live/dev-k3s/vault-database + infrastructure/live/dev-k3s/vault-foundation + infrastructure/live/dev-k3s/vault-workloads + infrastructure/live/dev-k3s/vault-database ) for root in "${terraform_roots[@]}"; do data_dir="$(mktemp -d)" @@ -214,24 +218,23 @@ for root in "${terraform_roots[@]}"; do done if rg -n \ - 'github\\.com/DongHyeonka/Project-Auth-GitOps|bitnami-labs\\.github\\.io/sealed-secrets|/home/donghyeon/dev/Project-Auth-GitOps|terraform/vault(-transit)?/(dev|reconcile)|clusters/dev-k3s/manifests|platform/auth-system|platform/security/vault|platform-config|postgres\\.platform\\.svc|keycloak(-public)?\\.platform\\.svc|kv/data/dev/platform' \ + 'github\\.com/DongHyeonka/Project-Auth-GitOps|bitnami-labs\\.github\\.io/sealed-secrets|/home/donghyeon/dev/Project-Auth-GitOps|terraform/vault(-transit)?/(dev|reconcile)|gitops/clusters/dev-k3s/manifests|platform/auth-system|platform/security/vault|platform-config|postgres\\.platform\\.svc|keycloak(-public)?\\.platform\\.svc|kv/data/dev/platform' \ --glob '!docs/archive/**' \ --glob '!docs/runbooks/terraform-state-migration.md' \ - --glob '!policies/legacy/**' \ - --glob '!hack/validate.sh' \ + --glob '!scripts/project-validate.sh' \ .; then echo "Current files contain a legacy URL, path, namespace, or Vault secret path." >&2 exit 1 fi -if rg -n '^path[[:space:]]+"[^"]*[+*]' policies/vault/dev-k3s/workloads; then +if rg -n '^path[[:space:]]+"[^"]*[+*]' infrastructure/live/dev-k3s/vault-workloads/policies; then echo "Workload Vault policies must use exact paths; wildcard paths require a security review." >&2 exit 1 fi automation_policies=( - policies/vault/dev-k3s/platform/vault-workloads-automation-dev.hcl - policies/vault/dev-k3s/platform/vault-database-automation-dev.hcl + infrastructure/live/dev-k3s/vault-foundation/policies/vault-workloads-automation-dev.hcl + infrastructure/live/dev-k3s/vault-foundation/policies/vault-database-automation-dev.hcl ) for policy in "${automation_policies[@]}"; do for self_path in \ @@ -247,13 +250,13 @@ done if rg -n \ '^path[[:space:]]+"[^"]*[+*]|capabilities[[:space:]]*=.*"(sudo|list)"|^path[[:space:]]+"auth/token/(create|roles)' \ - policies/vault/dev-k3s/platform; then + infrastructure/live/dev-k3s/vault-foundation/policies; then echo "Delegated automation policies must not use wildcards, sudo/list, or token issuance paths." >&2 exit 1 fi if rg -n '^path[[:space:]]+"(sys/|auth/|database/config|database/roles)' \ - policies/vault/dev-k3s/workloads; then + infrastructure/live/dev-k3s/vault-workloads/policies; then echo "Runtime workload policies may not configure Vault control-plane objects." >&2 exit 1 fi @@ -263,12 +266,13 @@ if rg -n 'uses:[[:space:]]+[^#[:space:]]+@v[0-9]' .gitea/workflows; then exit 1 fi -if rg -n 'ApplyOutOfSyncOnly=true' bootstrap clusters platform/control-plane; then +if rg -n 'ApplyOutOfSyncOnly=true' \ + bootstrap gitops/clusters gitops/platform/control-plane; then echo "ApplyOutOfSyncOnly is incompatible with hook-based migrations and must not be enabled." >&2 exit 1 fi -if rg -n 'git[[:space:]]+push[^#]*(HEAD:)?main([[:space:]]|$)' .gitea hack; then +if rg -n 'git[[:space:]]+push[^#]*(HEAD:)?main([[:space:]]|$)' .gitea scripts; then echo "Automation must promote changes through a branch and review, not push directly to main." >&2 exit 1 fi diff --git a/scripts/validate.sh b/scripts/validate.sh new file mode 100755 index 0000000..a862338 --- /dev/null +++ b/scripts/validate.sh @@ -0,0 +1,401 @@ +#!/usr/bin/env bash + +set -Eeuo pipefail + +repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "${repo_root}" + +errors=0 + +pass() { + printf '[pass] %s\n' "$1" +} + +fail() { + printf '[fail] %s\n' "$1" >&2 + errors=$((errors + 1)) +} + +required_files=( + README.md + SECURITY.md + infrastructure/.iac-engine.example +) + +required_directories=( + bootstrap/foundation + bootstrap/gitops + infrastructure/components/_template + infrastructure/stacks/_template + infrastructure/live/_template + gitops/clusters/_template + gitops/platform/_template + gitops/policies/_template + gitops/tenants/_template + gitops/apps/_template + docs/architecture + docs/decisions + docs/runbooks + examples/minimal + scripts + tests +) + +stage_errors="${errors}" +for path in "${required_files[@]}"; do + if [[ ! -f "${path}" || -L "${path}" ]]; then + fail "required regular file is missing or has the wrong type: ${path}" + fi +done + +for path in "${required_directories[@]}"; do + if [[ ! -d "${path}" || -L "${path}" ]]; then + fail "required directory is missing or has the wrong type: ${path}" + fi +done + +if ((errors == stage_errors)); then + pass "required repository structure" +fi + +stage_errors="${errors}" +while IFS= read -r directory; do + name="${directory##*/}" + if [[ "${name}" == "_template" ]]; then + continue + fi + + if [[ ! "${name}" =~ ^[a-z0-9][a-z0-9-]*$ ]]; then + fail "directory must use lowercase kebab-case: ${directory}" + fi +done < <( + find bootstrap docs examples gitops infrastructure scripts tests \ + \( \ + -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 \ + -type d -print | + sort +) + +if ((errors == stage_errors)); then + pass "directory naming" +fi + +collect_source_files() { + if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then + git ls-files --cached --others --exclude-standard -z + else + find . \ + \( \ + -type d \ + \( \ + -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 \ + -type f -print0 + fi +} + +source_files=() +while IFS= read -r -d '' file; do + file="${file#./}" + if [[ -f "${file}" ]]; then + source_files+=("${file}") + fi +done < <(collect_source_files) + +is_sensitive_filename() { + local file="$1" + local name="${file##*/}" + + case "${file}" in + */.decrypted/*) + return 0 + ;; + esac + + case "${name}" in + .env | .env.*) + [[ "${name}" == ".env.example" ]] && return 1 + return 0 + ;; + *.dec.yaml | *.decrypted.yaml | *.jks | *.key | *.kubeconfig | *.p12 | *.pem | *.pfx | \ + *.tfplan | *.tfstate | *.tfstate.* | credentials | credentials.* | id_dsa | id_ecdsa | \ + id_ed25519 | id_rsa | kubeconfig | kubeconfig.* | plan.out | service-account.json | \ + service_account.json) + return 0 + ;; + esac + + return 1 +} + +stage_errors="${errors}" +for file in "${source_files[@]}"; do + if is_sensitive_filename "${file}"; then + fail "sensitive/local artifact must not be stored: ${file}" + fi + + if grep -Eq -- '-----BEGIN (DSA |EC |OPENSSH |RSA )?PRIVATE KEY-----' "${file}" 2>/dev/null; then + fail "private key material must not be stored: ${file}" + fi +done + +if ((errors == stage_errors)); then + pass "sensitive/local artifact checks" +fi + +yaml_secret_kind_pattern="^['\"]?kind['\"]?[[:space:]]*:[[:space:]]*['\"]?Secret['\"]?([[:space:]]*(#.*)?)?$" +json_secret_kind_pattern="['\"]kind['\"][[:space:]]*:[[:space:]]*['\"]Secret['\"]" +yaml_sops_metadata_pattern='^sops:[[:space:]]*(#.*)?$' +json_sops_metadata_pattern='^[[:space:]]*"sops"[[:space:]]*:' +yaml_sops_mac_pattern='^[[:space:]]*mac:[[:space:]]*ENC\[AES256_GCM,' +json_sops_mac_pattern='^[[:space:]]*"mac"[[:space:]]*:[[:space:]]*"ENC\[AES256_GCM,' + +stage_errors="${errors}" +for file in "${source_files[@]}"; do + case "${file}" in + bootstrap/*.yaml | bootstrap/*.yml | bootstrap/*.json | \ + gitops/*.yaml | gitops/*.yml | gitops/*.json | \ + examples/*.yaml | examples/*.yml | examples/*.json) + secret_manifest=0 + case "${file}" in + *.json) + grep -Eq "${json_secret_kind_pattern}" "${file}" && secret_manifest=1 + ;; + *) + grep -Eq "${yaml_secret_kind_pattern}" "${file}" && secret_manifest=1 + ;; + esac + + if ((secret_manifest == 1)); then + case "${file}" in + *.sops.yaml | *.sops.yml) + if ! grep -Eq "${yaml_sops_metadata_pattern}" "${file}" || + ! grep -Eq "${yaml_sops_mac_pattern}" "${file}"; then + fail "SOPS Secret is missing encrypted metadata/MAC: ${file}" + fi + ;; + *.sops.json) + if ! grep -Eq "${json_sops_metadata_pattern}" "${file}" || + ! grep -Eq "${json_sops_mac_pattern}" "${file}"; then + fail "SOPS Secret is missing encrypted metadata/MAC: ${file}" + fi + ;; + *) + fail "plain Kubernetes Secret is not allowed; use an external reference or a *.sops.yaml file: ${file}" + ;; + esac + fi + ;; + esac +done + +if ((errors == stage_errors)); then + pass "plain Kubernetes Secret manifests" +fi + +stage_errors="${errors}" +for file in "${source_files[@]}"; do + case "${file}" in + */_template/*) + continue + ;; + esac + + if grep -Eq '__REPLACE_ME_[A-Z0-9_]+__' "${file}"; then + fail "unresolved replacement token: ${file}" + fi +done + +if ((errors == stage_errors)); then + pass "replacement tokens outside _template" +fi + +stage_errors="${errors}" +while IFS= read -r script; do + if ! bash -n "${script}"; then + fail "shell syntax: ${script}" + fi +done < <(find scripts -type f -name '*.sh' | sort) + +if ((errors == stage_errors)); then + pass "shell syntax" +fi + +kustomizations=() +for file in "${source_files[@]}"; do + case "${file}" in + bootstrap/*/kustomization.yaml | bootstrap/*/kustomization.yml | \ + gitops/*/kustomization.yaml | gitops/*/kustomization.yml | \ + examples/*/kustomization.yaml | examples/*/kustomization.yml) + kustomizations+=("${file}") + ;; + esac +done + +if ((${#kustomizations[@]} > 0)); then + stage_errors="${errors}" + if ! command -v kubectl >/dev/null 2>&1; then + fail "kubectl is required to render Kustomize roots" + else + for file in "${kustomizations[@]}"; do + rendered="" + if ! rendered="$(kubectl kustomize "$(dirname "${file}")")"; then + fail "Kustomize render: ${file}" + continue + fi + + case "${file}" in + gitops/clusters/_template/*) + ;; + gitops/clusters/*) + if [[ -z "${rendered}" ]]; then + fail "actual cluster root renders no resources: ${file}" + fi + ;; + esac + done + fi + + if ((errors == stage_errors)); then + pass "Kustomize render (${#kustomizations[@]} roots)" + fi +fi + +charts=() +for file in "${source_files[@]}"; do + if [[ "${file##*/}" == "Chart.yaml" ]]; then + charts+=("${file}") + fi +done + +if ((${#charts[@]} > 0)); then + stage_errors="${errors}" + if ! command -v helm >/dev/null 2>&1; then + fail "Helm is required because Chart.yaml files exist" + else + for chart in "${charts[@]}"; do + if ! helm lint "$(dirname "${chart}")"; then + fail "Helm lint: ${chart}" + fi + done + fi + + if ((errors == stage_errors)); then + pass "Helm lint (${#charts[@]} charts)" + fi +fi + +iac_files=() +iac_directories=() +has_tofu_syntax=0 +for file in "${source_files[@]}"; do + case "${file}" in + bootstrap/*.tofu | bootstrap/*.tofu.json | infrastructure/*.tofu | infrastructure/*.tofu.json) + iac_files+=("${file}") + has_tofu_syntax=1 + ;; + bootstrap/*.tf | bootstrap/*.tf.json | infrastructure/*.tf | infrastructure/*.tf.json) + iac_files+=("${file}") + ;; + esac +done + +for file in "${iac_files[@]}"; do + directory="${file%/*}" + directory_seen=0 + for existing_directory in "${iac_directories[@]}"; do + if [[ "${directory}" == "${existing_directory}" ]]; then + directory_seen=1 + break + fi + done + + if ((directory_seen == 0)); then + iac_directories+=("${directory}") + fi +done + +if ((${#iac_files[@]} > 0)); then + stage_errors="${errors}" + iac_engine="" + + if [[ ! -f infrastructure/.iac-engine || -L infrastructure/.iac-engine ]]; then + fail "select terraform or tofu in the tracked infrastructure/.iac-engine file" + else + iac_engine_values=() + while IFS= read -r line; do + case "${line}" in + "" | \#*) + continue + ;; + esac + iac_engine_values+=("${line}") + done < infrastructure/.iac-engine + + if ((${#iac_engine_values[@]} != 1)); then + fail "infrastructure/.iac-engine must contain exactly one uncommented value" + else + iac_engine="${iac_engine_values[0]}" + case "${iac_engine}" in + terraform | tofu) + ;; + *) + fail "infrastructure/.iac-engine must contain exactly terraform or tofu" + iac_engine="" + ;; + esac + fi + fi + + if [[ "${iac_engine}" == "terraform" && "${has_tofu_syntax}" == "1" ]]; then + fail "Terraform cannot format .tofu/.tofu.json files; select tofu or use compatible .tf files" + fi + + if [[ -n "${iac_engine}" ]]; then + if ! command -v "${iac_engine}" >/dev/null 2>&1; then + fail "${iac_engine} is selected but is not installed" + elif ((errors == stage_errors)); then + for directory in "${iac_directories[@]}"; do + if ! "${iac_engine}" fmt -check "${directory}"; then + fail "${iac_engine} format check: ${directory}" + fi + done + fi + fi + + if ((errors == stage_errors)); then + pass "${iac_engine} format (${#iac_files[@]} files)" + fi +fi + +stage_errors="${errors}" +if ! ./scripts/project-validate.sh; then + fail "project-specific validation" +fi + +if ((errors == stage_errors)); then + pass "project-specific validation" +fi + +if ((errors > 0)); then + printf '\nValidation failed with %d error(s).\n' "${errors}" >&2 + exit 1 +fi + +printf '\nValidation passed.\n' diff --git a/hack/vault-init.sh b/scripts/vault-init.sh similarity index 100% rename from hack/vault-init.sh rename to scripts/vault-init.sh diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 0000000..6b6531a --- /dev/null +++ b/tests/README.md @@ -0,0 +1,14 @@ +# Repository Tests + +저장소 전체의 배포 결과와 운영 정책을 검증합니다. + +권장 확장 순서: + +1. 모든 Kustomize root/Helm chart 렌더 +2. 렌더된 Kubernetes schema 검증 +3. admission/policy positive·negative fixture +4. kind/k3d 기반 controller 통합 테스트 +5. 실제 provider의 격리된 end-to-end 테스트 + +fixture와 golden output은 실제 credential과 운영 데이터를 포함하지 않습니다. +생성된 render 결과는 source로 커밋하지 않고 CI artifact로 보관합니다.