init: k8s 폴더 구조init
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,7 @@
|
||||
# 복제 후 실제 팀/사용자로 바꾸고 파일명을 CODEOWNERS로 변경하세요.
|
||||
#
|
||||
# * @your-org/platform-team
|
||||
# /bootstrap/ @your-org/platform-admins
|
||||
# /infrastructure/live/**/prod/ @your-org/platform-admins
|
||||
# /gitops/clusters/prod/ @your-org/platform-admins
|
||||
# /gitops/apps/ @your-org/application-owners
|
||||
@@ -0,0 +1,17 @@
|
||||
## 변경 내용
|
||||
|
||||
<!-- 무엇을 왜 변경하는지 적어 주세요. -->
|
||||
|
||||
## 영향 범위
|
||||
|
||||
<!-- 대상 환경, 계정, 리전, 클러스터와 예상 영향을 적어 주세요. -->
|
||||
|
||||
## 검증
|
||||
|
||||
- [ ] `make check`
|
||||
- [ ] 렌더/plan 결과 검토
|
||||
- [ ] 비밀 및 state 파일 미포함
|
||||
|
||||
## 롤백
|
||||
|
||||
<!-- 되돌리는 조건과 절차를 적어 주세요. -->
|
||||
@@ -0,0 +1,35 @@
|
||||
name: Validate
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: validate-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
validate:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 10
|
||||
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install kubectl
|
||||
uses: azure/setup-kubectl@829323503d1be3d00ca8346e5391ca0b07a9ab0d # v5.1.0
|
||||
with:
|
||||
version: v1.36.3
|
||||
|
||||
# Add a SHA-pinned setup step for the selected IaC/Helm/policy tools
|
||||
# before adding files that require them.
|
||||
- name: Run repository checks
|
||||
run: make check
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
# OS and editors
|
||||
.DS_Store
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# 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 / Terragrunt
|
||||
**/.terraform/*
|
||||
**/.terragrunt-cache/*
|
||||
*.tfstate
|
||||
*.tfstate.*
|
||||
*.tfplan
|
||||
plan.out
|
||||
.terraform.tfstate.lock.info
|
||||
crash.log
|
||||
crash.*.log
|
||||
override.tf
|
||||
override.tf.json
|
||||
*_override.tf
|
||||
*_override.tf.json
|
||||
*.local.tfvars
|
||||
*.secret.tfvars
|
||||
|
||||
# Decrypted or generated secret material
|
||||
.decrypted/
|
||||
*.decrypted.yaml
|
||||
*.dec.yaml
|
||||
|
||||
# Build and render output
|
||||
.build/
|
||||
.cache/
|
||||
dist/
|
||||
rendered/
|
||||
tmp/
|
||||
|
||||
# Test output
|
||||
coverage/
|
||||
*.log
|
||||
@@ -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을 추가하거나 갱신했다.
|
||||
|
||||
## 배포
|
||||
|
||||
이 템플릿에는 범용 `apply` 또는 `destroy` 명령이 없습니다. 프로젝트에서 배포
|
||||
자동화를 추가할 때는 대상 환경을 필수 입력으로 받고, 운영 환경에는 승인과
|
||||
동시 실행 잠금을 적용하며, 검토된 plan만 적용하도록 설계합니다.
|
||||
@@ -0,0 +1,29 @@
|
||||
SHELL := /bin/bash
|
||||
.DEFAULT_GOAL := help
|
||||
|
||||
.PHONY: help doctor validate check tree
|
||||
|
||||
help: ## 사용 가능한 명령을 표시합니다.
|
||||
@awk 'BEGIN {FS = ":.*## "; print "Usage: make <target>"} /^[a-zA-Z_-]+:.*## / {printf " %-12s %s\n", $$1, $$2}' $(MAKEFILE_LIST)
|
||||
|
||||
doctor: ## 현재 로컬 도구 상태를 확인합니다.
|
||||
@./scripts/doctor.sh
|
||||
|
||||
validate: ## 구조, shell, Kustomize, Helm lint와 IaC format을 검증합니다.
|
||||
@./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
|
||||
@@ -0,0 +1,190 @@
|
||||
# Kubernetes Infrastructure Skeleton
|
||||
|
||||
Kubernetes 인프라 저장소를 새로 시작할 때 복제해서 사용하는 기본 골격입니다.
|
||||
작은 단일 클러스터부터 여러 계정·리전·클러스터를 운영하는 구성까지 같은
|
||||
수명주기 경계를 유지하도록 설계했습니다.
|
||||
|
||||
이 스켈레톤이 고정하는 것은 **폴더의 책임, 배포 경계, 소유권 규칙**입니다.
|
||||
클라우드 공급자, IaC 엔진(Terraform/OpenTofu/Pulumi 등), GitOps 컨트롤러,
|
||||
비밀 관리 도구와 정책 엔진은 프로젝트 요구에 맞춰 선택합니다.
|
||||
|
||||
Kubernetes manifest 조립의 최소 기본값은 `kubectl`에 내장된 Kustomize입니다.
|
||||
Helm이 필요한 컴포넌트는 해당 컴포넌트 내부에 Chart 또는 values를 함께 둡니다.
|
||||
|
||||
## 수명주기
|
||||
|
||||
```text
|
||||
bootstrap/foundation
|
||||
│
|
||||
▼
|
||||
infrastructure/live
|
||||
│
|
||||
▼
|
||||
bootstrap/gitops
|
||||
│
|
||||
▼
|
||||
gitops/clusters ──▶ platform / policies / tenants / apps
|
||||
```
|
||||
|
||||
- `bootstrap/foundation`: state backend, 초기 identity 등 선행 조건
|
||||
- `infrastructure/live`: 네트워크, IAM, DNS, Kubernetes 클러스터 프로비저닝
|
||||
- `bootstrap/gitops`: 선택한 GitOps 컨트롤러와 루트 연결만 설치
|
||||
- `gitops/clusters`: 플랫폼과 워크로드의 최종 desired state 조립
|
||||
|
||||
동일한 리소스를 두 단계가 동시에 관리하지 않습니다. 예를 들어 GitOps가 관리하는
|
||||
Kubernetes 리소스를 IaC에서도 다시 선언하지 않습니다.
|
||||
|
||||
## 폴더 구조
|
||||
|
||||
```text
|
||||
.
|
||||
├── bootstrap/
|
||||
│ ├── foundation/ # state/identity 등 최초 선행 조건
|
||||
│ └── gitops/ # Flux 또는 Argo CD 최소 부트스트랩
|
||||
├── infrastructure/
|
||||
│ ├── components/ # 작고 재사용 가능한 IaC 단위
|
||||
│ ├── stacks/ # 여러 component의 재사용 조합(선택)
|
||||
│ ├── live/ # 실제 plan/apply 및 state 경계
|
||||
│ └── tests/ # IaC 단위·계약 테스트
|
||||
├── gitops/
|
||||
│ ├── clusters/ # 클러스터별 최종 조립·동기화 진입점
|
||||
│ ├── platform/ # ingress, external-dns controller, storage 등
|
||||
│ ├── policies/ # cluster-wide admission/guardrail 정책
|
||||
│ ├── tenants/ # namespace, RBAC, quota, NetworkPolicy(선택)
|
||||
│ └── apps/ # 애플리케이션 배포 정의
|
||||
├── docs/
|
||||
│ ├── architecture/ # 구조·의존성·설계 문서
|
||||
│ ├── decisions/ # Architecture Decision Records
|
||||
│ ├── guides/ # 사용 절차
|
||||
│ └── runbooks/ # 장애·변경·복구 절차
|
||||
├── examples/
|
||||
│ ├── minimal/ # 렌더 가능한 최소 구성
|
||||
│ └── scaled/ # 확장 구조 예시
|
||||
├── scripts/ # 로컬/CI 공통 검증 자동화
|
||||
└── tests/ # 저장소 수준 렌더·정책·통합 테스트
|
||||
```
|
||||
|
||||
## 템플릿 위치
|
||||
|
||||
별도의 최상위 `template/` 폴더는 없습니다. 템플릿은 실제 결과물이 위치할
|
||||
책임 영역 가까이에 `_template/` 이름으로 배치되어 있습니다.
|
||||
|
||||
| 만들 대상 | 복사 원본 | 용도 |
|
||||
|---|---|---|
|
||||
| IaC component | `infrastructure/components/_template` | network, identity, cluster 등 재사용 단위 |
|
||||
| IaC stack | `infrastructure/stacks/_template` | 여러 component의 반복 조합 |
|
||||
| 실제 IaC root | `infrastructure/live/_template` | environment별 state/plan/apply 경계 |
|
||||
| cluster root | `gitops/clusters/_template` | 클러스터 desired state 최종 조립점 |
|
||||
| platform component | `gitops/platform/_template` | ingress, storage, observability 등 |
|
||||
| application 배포 | `gitops/apps/_template` | application base/overlay |
|
||||
| policy set | `gitops/policies/_template` | cluster-wide admission/guardrail |
|
||||
| tenant | `gitops/tenants/_template` | namespace, RBAC, quota, NetworkPolicy |
|
||||
|
||||
`_template`은 복사 원본이며 배포 대상이 아닙니다. 실제 이름으로 복사한 뒤
|
||||
`__REPLACE_ME_*__` 값을 모두 채우고 리소스를 추가합니다. 템플릿 밖의 미치환
|
||||
값과 아무 리소스도 렌더하지 않는 실제 cluster root는 검증에 실패합니다.
|
||||
|
||||
## 예제를 활용해 설계하는 방법
|
||||
|
||||
`examples/`는 복사 원본이 아니라 설계 참고 자료입니다.
|
||||
|
||||
```text
|
||||
examples/minimal ──▶ 소규모의 canonical 경로와 조립 방식 확인
|
||||
examples/scaled ──▶ 계정·리전·환경·클러스터 확장 경로 결정
|
||||
│
|
||||
▼
|
||||
각 영역의 _template 복사
|
||||
│
|
||||
▼
|
||||
infrastructure/live 및 gitops/clusters에 실제 구성 작성
|
||||
```
|
||||
|
||||
1. `examples/minimal`에서 `live`, catalog `base`, cluster root의 관계를 확인하고
|
||||
Kustomize 결과를 렌더합니다.
|
||||
2. 계정·리전·클러스터가 여러 개인 경우 `examples/scaled`의 계층을 참고해
|
||||
실제 경로 깊이를 정합니다.
|
||||
3. 예제 파일이나 값을 운영 경로로 복사하지 않고, 위 표의 `_template`에서
|
||||
실제 component와 entrypoint를 생성합니다.
|
||||
4. 예제와 실제 구성이 같은 소유권·배포 경계를 따르는지 비교한 뒤 검증합니다.
|
||||
|
||||
```bash
|
||||
kubectl kustomize examples/minimal/gitops/clusters/dev/main
|
||||
make check
|
||||
```
|
||||
|
||||
## 시작하기
|
||||
|
||||
먼저 프로젝트의 공급자, IaC 엔진, GitOps 컨트롤러, 비밀 관리 방식을
|
||||
ADR로 기록합니다. 이후 필요한 템플릿만 복사합니다.
|
||||
|
||||
```bash
|
||||
mkdir -p infrastructure/live/dev
|
||||
cp -R infrastructure/live/_template infrastructure/live/dev/cluster
|
||||
|
||||
mkdir -p gitops/clusters/dev
|
||||
cp -R gitops/clusters/_template gitops/clusters/dev/main
|
||||
|
||||
cp -R gitops/platform/_template gitops/platform/ingress
|
||||
```
|
||||
|
||||
먼저 도구를 확인합니다.
|
||||
|
||||
```bash
|
||||
make doctor
|
||||
```
|
||||
|
||||
복사 직후에는 미치환 token과 빈 cluster root가 있으므로 `make check` 실패가
|
||||
정상입니다. 실제 메타데이터를 채우고 component 리소스와 cluster 참조를 추가한
|
||||
뒤 검증합니다.
|
||||
|
||||
```bash
|
||||
kubectl kustomize gitops/clusters/dev/main
|
||||
make check
|
||||
```
|
||||
|
||||
구체적인 완료 순서는
|
||||
[`docs/guides/getting-started.md`](docs/guides/getting-started.md)를 참고합니다.
|
||||
이 저장소는 안전을 위해 기본 `apply`/`destroy` 명령을 제공하지 않습니다.
|
||||
|
||||
## 규모에 따른 사용법
|
||||
|
||||
### 소규모
|
||||
|
||||
```text
|
||||
infrastructure/live/dev/cluster
|
||||
gitops/clusters/dev/main
|
||||
```
|
||||
|
||||
`live`가 component를 직접 호출하고, `stacks`, `policies`, `tenants`는 필요할
|
||||
때까지 사용하지 않아도 됩니다.
|
||||
|
||||
### 중·대규모
|
||||
|
||||
```text
|
||||
infrastructure/live/<provider>/<account>/<region>/<environment>/<stack>
|
||||
gitops/clusters/<environment>/<region>/<cluster>
|
||||
```
|
||||
|
||||
경로 깊이는 조직 상황에 맞추되 다음 계약은 유지합니다.
|
||||
|
||||
- `infrastructure/live`의 실행 가능한 leaf 하나가 state/plan/apply 경계입니다.
|
||||
- `gitops/clusters`의 leaf 하나가 클러스터 동기화 진입점입니다.
|
||||
- 공통 구현은 `components`, `platform`, `policies`, `tenants`, `apps`에 한 번만
|
||||
둡니다.
|
||||
- 환경·클러스터 경로에는 공통 구현을 복사하지 않고 조합과 차이만 둡니다.
|
||||
|
||||
더 자세한 확장 기준은
|
||||
[`docs/architecture/repository-structure.md`](docs/architecture/repository-structure.md)에
|
||||
정리되어 있습니다.
|
||||
|
||||
## 기본 규칙
|
||||
|
||||
- 디렉터리와 리소스 이름은 소문자 `kebab-case`를 사용합니다.
|
||||
- 평문 Secret, kubeconfig, private key, state와 plan 파일은 커밋하지 않습니다.
|
||||
- `.terraform.lock.hcl`, `Chart.lock`, 암호화된 SOPS 파일은 커밋합니다.
|
||||
- 예제는 `examples/`에만 두며 실제 reconcile 경로에서 참조하지 않습니다.
|
||||
- 배포 변경은 렌더와 검증을 통과한 뒤 리뷰를 거칩니다.
|
||||
- 프로젝트별 도구 버전과 소유자는 복제 직후 명시적으로 고정합니다.
|
||||
|
||||
보안 기준은 [`SECURITY.md`](SECURITY.md), 기여 규칙은
|
||||
[`CONTRIBUTING.md`](CONTRIBUTING.md)를 참고합니다.
|
||||
+46
@@ -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. 조직의 보안 연락 채널로 보고합니다.
|
||||
|
||||
이 템플릿을 실제 조직에서 사용하기 전에 비공개 보안 연락처와 대응 SLA를 이
|
||||
문서에 추가해야 합니다.
|
||||
@@ -0,0 +1,11 @@
|
||||
# Bootstrap
|
||||
|
||||
선언형 인프라와 GitOps가 스스로 동작하기 전에 한 번 또는 매우 드물게 수행하는
|
||||
최소 초기화만 둡니다.
|
||||
|
||||
- `foundation`: remote state, locking, 최초 identity 같은 선행 조건
|
||||
- `gitops`: 선택한 controller 설치와 cluster root 연결
|
||||
|
||||
일반 네트워크, Kubernetes cluster, addon과 application을 이곳에 두지 않습니다.
|
||||
부트스트랩 절차는 반복 실행 가능하고 감사 가능해야 하며, 장기 수동 운영 경로가
|
||||
되어서는 안 됩니다.
|
||||
@@ -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와 복구 절차만 기록합니다.
|
||||
@@ -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`에 문서화합니다.
|
||||
@@ -0,0 +1,150 @@
|
||||
# Repository Structure
|
||||
|
||||
## 설계 목표
|
||||
|
||||
이 구조는 특정 제품의 파일 배치보다 변경 주기와 소유권을 우선합니다.
|
||||
|
||||
- 한 리소스에는 한 명확한 소유자가 있다.
|
||||
- 재사용 구현과 실제 배포 진입점을 분리한다.
|
||||
- 소규모 구성은 선택 영역을 생략할 수 있다.
|
||||
- 규모가 커져도 기존 경계를 바꾸지 않고 같은 종류의 leaf를 추가한다.
|
||||
- 사람이 실행하는 명령과 CI 검증이 같은 진입점을 사용한다.
|
||||
|
||||
## 소유권 매트릭스
|
||||
|
||||
| 대상 | 소유 경로 | 직접 실행 여부 | 변경 주기 |
|
||||
|---|---|---:|---|
|
||||
| state backend, 초기 identity | `bootstrap/foundation` | 예 | 매우 낮음 |
|
||||
| 네트워크, IAM, DNS, 클러스터 | `infrastructure/live` | 예 | 낮음 |
|
||||
| 재사용 IaC 단위 | `infrastructure/components` | 아니요 | 중간 |
|
||||
| 재사용 IaC 조합 | `infrastructure/stacks` | 아니요 | 중간 |
|
||||
| GitOps 컨트롤러와 root 연결 | `bootstrap/gitops` | 예 | 낮음 |
|
||||
| 클러스터 desired state | `gitops/clusters` | reconcile 진입점 | 지속적 |
|
||||
| cluster-wide addon | `gitops/platform` | 아니요 | 중간 |
|
||||
| 애플리케이션 배포 정의 | `gitops/apps` | 아니요 | 높음 |
|
||||
| 정책과 tenant 정의 | `gitops/policies`, `gitops/tenants` | 아니요 | 중간 |
|
||||
|
||||
Catalog 영역(`components`, `stacks`, `platform`, `policies`, `tenants`, `apps`)은
|
||||
직접 배포하지 않습니다. 실제 진입점이 필요한 항목만 조합해서 참조합니다.
|
||||
|
||||
## 의존 방향
|
||||
|
||||
```text
|
||||
bootstrap/foundation
|
||||
│ output
|
||||
▼
|
||||
infrastructure/components ◀── infrastructure/stacks
|
||||
▲ ▲
|
||||
└──────── infrastructure/live ─┘
|
||||
│ cluster endpoint/identity
|
||||
▼
|
||||
bootstrap/gitops
|
||||
│ root reference
|
||||
▼
|
||||
platform ─┐
|
||||
policies ─┼────────▶ gitops/clusters
|
||||
tenants ─┤
|
||||
apps ─┘
|
||||
```
|
||||
|
||||
역방향 의존은 만들지 않습니다. 예를 들어 reusable component가 특정
|
||||
`live/prod` 값을 읽거나, app base가 특정 cluster overlay를 참조하면 안 됩니다.
|
||||
|
||||
## Infrastructure 경계
|
||||
|
||||
### `components`
|
||||
|
||||
네트워크, identity, registry, Kubernetes cluster처럼 작고 응집된 재사용
|
||||
단위입니다. Terraform/OpenTofu를 선택했다면 일반적으로 backend가 없는 child
|
||||
module에 해당합니다.
|
||||
|
||||
### `stacks`
|
||||
|
||||
여러 component를 반복해서 같은 방식으로 조합할 때만 사용합니다. 작은 프로젝트는
|
||||
이 계층 없이 `live`가 component를 직접 호출할 수 있습니다. stack이 다른 stack을
|
||||
깊게 중첩하기보다는 live root에서 평평하게 조합하는 방식을 권장합니다.
|
||||
|
||||
### `live`
|
||||
|
||||
실제로 plan/apply하는 root입니다. leaf 하나는 다음을 만족해야 합니다.
|
||||
|
||||
- 독립된 state와 locking
|
||||
- 명시적인 provider와 backend 설정
|
||||
- 고정된 component/module/chart 버전
|
||||
- 비밀이 아닌 환경 입력만 저장소에 커밋
|
||||
- 출력값과 downstream contract 문서화
|
||||
|
||||
작은 구성은 `live/dev/cluster`로 충분합니다. 계정과 리전이 늘어나면
|
||||
`live/<provider>/<account>/<region>/<environment>/<stack>`처럼 경로를 확장합니다.
|
||||
자동화는 경로의 고정 깊이에 의존하지 말고 실행 가능한 root 파일을 기준으로
|
||||
대상을 찾도록 작성합니다.
|
||||
|
||||
서로 다른 환경의 root가 상대 경로로 다른 환경 구현을 import하면 안 됩니다.
|
||||
공유가 필요하면 versioned component나 명시적인 remote output/data contract를
|
||||
사용합니다.
|
||||
|
||||
## GitOps 경계
|
||||
|
||||
### `clusters`
|
||||
|
||||
클러스터가 reconcile하는 유일한 진입점입니다. 공통 리소스를 복사하지 않고
|
||||
platform, policy, tenant, app catalog에서 필요한 항목만 참조합니다.
|
||||
|
||||
작은 구성은 `clusters/dev/main`, 다중 리전 구성은
|
||||
`clusters/<environment>/<region>/<cluster>` 형태를 사용할 수 있습니다. 여기에도
|
||||
고정된 경로 깊이를 강제하지 않습니다.
|
||||
|
||||
### `platform`
|
||||
|
||||
cluster-wide controller와 addon을 둡니다. 예시는 다음과 같습니다.
|
||||
|
||||
- ingress/gateway, external DNS, certificate
|
||||
- storage class/CSI, autoscaling
|
||||
- metrics, logs, traces, alerting
|
||||
- secret operator와 delivery controller
|
||||
|
||||
각 component는 `base`와 필요한 `overlays`를 같은 디렉터리 안에 응집시킵니다.
|
||||
환경 차이는 전체 파일 복사 대신 Kustomize patch 또는 별도 values로 표현합니다.
|
||||
|
||||
### `policies`, `tenants`, `apps`
|
||||
|
||||
- `policies`: cluster-wide admission 규칙, 거버넌스와 예외
|
||||
- `tenants`: 구체적인 namespace, RBAC, quota, limit range, NetworkPolicy
|
||||
- `apps`: application source code가 아닌 배포 정의
|
||||
|
||||
애플리케이션 팀이 별도 저장소를 소유하면 `apps`에는 그 저장소/OCI artifact를
|
||||
참조하는 GitOps 리소스만 둘 수 있습니다.
|
||||
|
||||
Cloud DNS zone/delegation과 cloud IAM role은 `infrastructure`가 소유합니다.
|
||||
External DNS controller, 동적 record 요청과 Kubernetes ServiceAccount binding은
|
||||
`gitops/platform`이 소유합니다. 두 계층 사이에는 zone ID, role ARN 같은
|
||||
명시적인 output contract만 전달합니다.
|
||||
|
||||
## Bootstrap 경계
|
||||
|
||||
Bootstrap은 선언형 관리가 스스로 시작될 수 없는 최소 범위만 담당합니다.
|
||||
|
||||
- `foundation`: state backend, 최초 CI identity와 같은 선행 조건
|
||||
- `gitops`: Flux 또는 Argo CD 중 선택한 컨트롤러 설치와 root reference
|
||||
|
||||
ingress, cert-manager, observability 같은 addon은 bootstrap이 아니라 GitOps가
|
||||
소유합니다. bootstrap 이후의 변경을 계속 수동 명령으로 누적하지 않습니다.
|
||||
|
||||
## 규모 확장 기준
|
||||
|
||||
| 단계 | 추가하는 것 | 그대로 유지하는 것 |
|
||||
|---|---|---|
|
||||
| 소형 | 단일 live root, 단일 cluster root, 최소 platform | lifecycle/ownership 경계 |
|
||||
| 중형 | reusable stack, staging/prod, 정책, 관측성 | component와 entrypoint 분리 |
|
||||
| 대형 | 계정·리전별 state, 다중 cluster, tenants, CODEOWNERS | 한 리소스 한 소유자 |
|
||||
| 조직 분리 | lifecycle/team별 repository 분리 가능 | 각 repository 내부의 동일한 계약 |
|
||||
|
||||
repository를 분리하는 시점은 폴더 수가 아니라 권한, 배포 주기와 소유 팀이
|
||||
실제로 달라졌을 때입니다.
|
||||
|
||||
## 설계 참고 자료
|
||||
|
||||
- [Kubernetes: Kustomize를 이용한 선언형 객체 관리](https://kubernetes.io/docs/tasks/manage-kubernetes-objects/kustomization/)
|
||||
- [Flux: GitOps repository 구조](https://fluxcd.io/flux/guides/repository-structure/)
|
||||
- [OpenTofu: reusable module](https://opentofu.org/docs/language/modules/)
|
||||
- [OpenTofu: 평평한 module composition](https://opentofu.org/docs/language/modules/develop/composition/)
|
||||
@@ -0,0 +1,37 @@
|
||||
# 0001. 수명주기와 소유권 경계
|
||||
|
||||
- 상태: 승인
|
||||
- 날짜: 2026-07-26
|
||||
- 결정자: repository maintainers
|
||||
|
||||
## 배경
|
||||
|
||||
인프라 저장소는 규모가 커지면서 cloud provisioning, cluster bootstrap,
|
||||
platform addon과 application 배포가 뒤섞이기 쉽습니다. 이 경우 동일 리소스를
|
||||
여러 도구가 관리하거나, 작은 변경이 불필요하게 넓은 권한과 state를 요구합니다.
|
||||
|
||||
## 결정
|
||||
|
||||
저장소를 다음 세 수명주기로 분리합니다.
|
||||
|
||||
1. `bootstrap`: 선언형 관리가 시작되기 위한 최소 선행 조건
|
||||
2. `infrastructure`: cloud/cluster 리소스 provisioning
|
||||
3. `gitops`: Kubernetes desired state의 지속적 reconciliation
|
||||
|
||||
재사용 구현은 catalog 영역에 두고, `infrastructure/live`와
|
||||
`gitops/clusters`만 실제 환경 진입점으로 사용합니다. 하나의 리소스는 하나의
|
||||
수명주기와 하나의 도구만 소유합니다.
|
||||
|
||||
## 결과
|
||||
|
||||
- state, 권한과 배포 실패 범위를 작게 유지할 수 있습니다.
|
||||
- 소규모는 선택 디렉터리를 사용하지 않고도 시작할 수 있습니다.
|
||||
- 계정, 리전과 클러스터가 늘어날 때 같은 leaf를 추가해 확장할 수 있습니다.
|
||||
- 초기에는 디렉터리가 더 많아 보이지만 각 위치의 책임이 명확해집니다.
|
||||
|
||||
## 대안
|
||||
|
||||
- 환경별 전체 복사: 시작은 단순하지만 공통 변경의 drift와 중복이 빠르게 증가합니다.
|
||||
- 도구별 최상위 폴더: 구현 도구는 잘 보이지만 리소스 소유권과 수명주기가 섞입니다.
|
||||
- 모든 리소스를 단일 state로 관리: 작은 데모에는 가능하지만 권한과 장애 범위가
|
||||
지나치게 커집니다.
|
||||
@@ -0,0 +1,24 @@
|
||||
# Architecture Decision Records
|
||||
|
||||
프로젝트의 장기 구조에 영향을 주는 선택은 ADR로 남깁니다.
|
||||
|
||||
파일명은 `NNNN-kebab-case-title.md`를 사용하고 다음 형식을 따릅니다.
|
||||
|
||||
```markdown
|
||||
# NNNN. 제목
|
||||
|
||||
- 상태: 제안 | 승인 | 폐기 | 대체
|
||||
- 날짜: YYYY-MM-DD
|
||||
- 결정자: 팀 또는 역할
|
||||
|
||||
## 배경
|
||||
|
||||
## 결정
|
||||
|
||||
## 결과
|
||||
|
||||
## 대안
|
||||
```
|
||||
|
||||
기존 결정을 바꿀 때 문서를 지우지 말고 새 ADR에서 이전 ADR을 대체했다고
|
||||
표시합니다.
|
||||
@@ -0,0 +1,142 @@
|
||||
# Getting Started
|
||||
|
||||
## 예제를 먼저 확인하기
|
||||
|
||||
실제 파일을 만들기 전에 두 예제를 설계 참고 자료로 사용합니다.
|
||||
|
||||
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에 재사용하지 않습니다.
|
||||
@@ -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 장애 대응
|
||||
|
||||
각 문서는 `목적`, `사전 조건`, `영향`, `절차`, `검증`, `롤백`,
|
||||
`에스컬레이션` 섹션을 포함해야 합니다.
|
||||
@@ -0,0 +1,15 @@
|
||||
# Runbook 제목
|
||||
|
||||
## 목적
|
||||
|
||||
## 사전 조건
|
||||
|
||||
## 영향
|
||||
|
||||
## 절차
|
||||
|
||||
## 검증
|
||||
|
||||
## 롤백
|
||||
|
||||
## 에스컬레이션
|
||||
@@ -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 기본값으로 재사용하지 않습니다.
|
||||
@@ -0,0 +1,20 @@
|
||||
# Minimal Example
|
||||
|
||||
단일 환경·단일 클러스터가 같은 경계를 어떻게 사용하는지 보여 주는 예입니다.
|
||||
|
||||
```text
|
||||
minimal/
|
||||
├── infrastructure/
|
||||
│ └── live/dev/cluster/README.md
|
||||
└── gitops/
|
||||
├── platform/core/base/
|
||||
├── apps/hello-config/base/
|
||||
└── clusters/dev/main/
|
||||
```
|
||||
|
||||
실제 cloud 리소스를 만들지 않으며, GitOps 예제는 Namespace와 ConfigMap만
|
||||
렌더합니다.
|
||||
|
||||
```bash
|
||||
kubectl kustomize examples/minimal/gitops/clusters/dev/main
|
||||
```
|
||||
@@ -0,0 +1,11 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: hello-config
|
||||
namespace: skeleton-demo
|
||||
labels:
|
||||
app.kubernetes.io/name: hello-config
|
||||
app.kubernetes.io/part-of: skeleton-demo
|
||||
app.kubernetes.io/managed-by: kustomize
|
||||
data:
|
||||
message: "replace this example with a real application definition"
|
||||
@@ -0,0 +1,5 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
resources:
|
||||
- config-map.yaml
|
||||
@@ -0,0 +1,6 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
resources:
|
||||
- ../../../platform/core/base
|
||||
- ../../../apps/hello-config/base
|
||||
@@ -0,0 +1,5 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
resources:
|
||||
- namespace.yaml
|
||||
@@ -0,0 +1,7 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: skeleton-demo
|
||||
labels:
|
||||
app.kubernetes.io/part-of: skeleton-demo
|
||||
app.kubernetes.io/managed-by: kustomize
|
||||
@@ -0,0 +1,11 @@
|
||||
# Example Live Root
|
||||
|
||||
실제 프로젝트에서는 이 위치가 독립 state를 갖는 실행 가능한 IaC root가 됩니다.
|
||||
|
||||
- Scope: local example
|
||||
- Environment: dev
|
||||
- Stack: cluster
|
||||
- State: example에는 없음
|
||||
|
||||
이 예제에는 provider 또는 IaC 엔진을 선택하지 않았기 때문에 실행 코드를
|
||||
포함하지 않습니다.
|
||||
@@ -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 분리는 폴더 수가 아니라 소유권과 권한 경계가 달라질 때 수행합니다.
|
||||
@@ -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에
|
||||
문서화합니다.
|
||||
@@ -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를
|
||||
사용합니다.
|
||||
@@ -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 같은 참조 또는 승인된 암호화 형식으로만 추가합니다.
|
||||
@@ -0,0 +1,4 @@
|
||||
# Base
|
||||
|
||||
환경을 모르는 application의 공통 manifest를 둡니다. namespace 자체의 소유권이
|
||||
tenant catalog에 있다면 이곳에서 중복 생성하지 않습니다.
|
||||
@@ -0,0 +1,4 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
resources: []
|
||||
@@ -0,0 +1,6 @@
|
||||
# Overlays
|
||||
|
||||
필요한 환경에만 overlay를 추가합니다. 각 overlay는 `../../base`를 참조하고
|
||||
환경별 patch만 포함합니다.
|
||||
|
||||
비밀값, 임시 debug 설정과 수동 hotfix 결과를 overlay에 커밋하지 않습니다.
|
||||
@@ -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`을 실제 경로로 복사해 시작합니다.
|
||||
@@ -0,0 +1,31 @@
|
||||
# __REPLACE_ME_CLUSTER_NAME__
|
||||
|
||||
## 대상
|
||||
|
||||
- Environment: __REPLACE_ME_ENVIRONMENT__
|
||||
- Region: __REPLACE_ME_REGION__
|
||||
- Cluster: __REPLACE_ME_CLUSTER_NAME__
|
||||
- Owner: __REPLACE_ME_OWNER__
|
||||
|
||||
## 구성
|
||||
|
||||
`kustomization.yaml`의 `resources`에 필요한 platform, policy, tenant와 app의
|
||||
base 또는 overlay를 추가합니다. 예:
|
||||
|
||||
```yaml
|
||||
resources:
|
||||
- ../../../platform/core/base
|
||||
- ../../../policies/baseline/base
|
||||
- ../../../apps/example-api/overlays/prod
|
||||
```
|
||||
|
||||
위 예시는 `clusters/dev/main` 경로를 기준으로 합니다. 실제 상대 경로는 cluster
|
||||
leaf 깊이에 맞게 조정합니다.
|
||||
|
||||
## 규칙
|
||||
|
||||
- 이 디렉터리가 해당 클러스터의 유일한 root입니다.
|
||||
- 공통 manifest를 복사하지 않습니다.
|
||||
- 클러스터 고유 차이만 local patch로 둡니다.
|
||||
- dependency/sync 순서와 장애 시 reconcile 중지 절차를 문서화합니다.
|
||||
- `_template` 자체를 GitOps controller에 연결하지 않습니다.
|
||||
@@ -0,0 +1,5 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
# Add reusable platform, policy, tenant and app paths after copying this template.
|
||||
resources: []
|
||||
@@ -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`을 복사해 시작합니다.
|
||||
@@ -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으로 연결합니다.
|
||||
@@ -0,0 +1,7 @@
|
||||
# Base
|
||||
|
||||
환경과 클러스터를 모르는 재사용 가능한 기본 manifest만 둡니다.
|
||||
|
||||
- 실제 domain, account ID, credential을 하드코딩하지 않습니다.
|
||||
- resource request/limit와 security context의 안전한 기본값을 둡니다.
|
||||
- 환경 차이는 `overlays`에서 patch합니다.
|
||||
@@ -0,0 +1,4 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
resources: []
|
||||
@@ -0,0 +1,7 @@
|
||||
# Overlays
|
||||
|
||||
공통 base와 다른 값이 있을 때만 `<environment-or-profile>/` overlay를 만듭니다.
|
||||
overlay는 base를 참조하고 patch만 포함해야 합니다.
|
||||
|
||||
`dev`, `prod` 전체 manifest 복사보다 replica, resource, retention처럼 실제로
|
||||
달라지는 항목만 표현합니다.
|
||||
@@ -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, 사유와 만료일을 반드시 기록합니다.
|
||||
@@ -0,0 +1,17 @@
|
||||
# __REPLACE_ME_POLICY_SET_NAME__
|
||||
|
||||
## 목적과 범위
|
||||
|
||||
보호하는 대상, 위협과 제외 범위를 적습니다.
|
||||
|
||||
## 적용 단계
|
||||
|
||||
audit 결과, enforcement 전환 조건과 rollback을 적습니다.
|
||||
|
||||
## 예외
|
||||
|
||||
예외 schema, 승인자와 만료 정책을 적습니다.
|
||||
|
||||
## 테스트
|
||||
|
||||
허용/거부 fixture와 선택한 policy engine의 test 명령을 추가합니다.
|
||||
@@ -0,0 +1,4 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
resources: []
|
||||
@@ -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 정책만 소유합니다.
|
||||
@@ -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 삭제 절차를 적습니다.
|
||||
@@ -0,0 +1,4 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
resources: []
|
||||
@@ -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
|
||||
@@ -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로
|
||||
전달합니다.
|
||||
@@ -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`을 복사해 시작합니다.
|
||||
@@ -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와 테스트를 함께 갱신한다.
|
||||
@@ -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에 기록합니다.
|
||||
@@ -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 복구 절차 링크를 적습니다.
|
||||
@@ -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에서 평평하게 조합합니다.
|
||||
@@ -0,0 +1,19 @@
|
||||
# __REPLACE_ME_STACK_NAME__
|
||||
|
||||
## 목적
|
||||
|
||||
반복해서 함께 배포하는 component 조합을 설명합니다.
|
||||
|
||||
## 포함 Component
|
||||
|
||||
각 component의 version과 책임을 적습니다.
|
||||
|
||||
## 입력과 출력
|
||||
|
||||
live root에 노출하는 최소 interface를 적습니다.
|
||||
|
||||
## 제약
|
||||
|
||||
- backend와 environment credential을 선언하지 않습니다.
|
||||
- 특정 live 경로를 역참조하지 않습니다.
|
||||
- component가 한 번만 필요하면 stack 계층을 만들지 않습니다.
|
||||
@@ -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를 읽거나
|
||||
변경해서는 안 됩니다.
|
||||
@@ -0,0 +1,21 @@
|
||||
# Scripts
|
||||
|
||||
로컬과 CI가 동일하게 사용하는 얇고 명시적인 자동화만 둡니다.
|
||||
|
||||
- `doctor.sh`: 필수/선택 도구 가용성 확인
|
||||
- `validate.sh`: 구조, 민감 파일/평문 Secret, shell, Kustomize, Helm lint,
|
||||
IaC format 검증
|
||||
|
||||
검증 중 도구를 몰래 다운로드하거나 환경을 변경하지 않습니다. 실제 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 정책을
|
||||
반영합니다.
|
||||
Executable
+174
@@ -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'
|
||||
Executable
+392
@@ -0,0 +1,392 @@
|
||||
#!/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
|
||||
|
||||
if ((errors > 0)); then
|
||||
printf '\nValidation failed with %d error(s).\n' "${errors}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
printf '\nValidation passed.\n'
|
||||
@@ -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로 보관합니다.
|
||||
Reference in New Issue
Block a user