refactor: 폴더 구조 변경
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,25 @@
|
||||
name: infra-validate
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
validate:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install pinned tools
|
||||
uses: jdx/mise-action@v4.2.3
|
||||
with:
|
||||
install: true
|
||||
cache: true
|
||||
|
||||
- name: Validate infrastructure source
|
||||
env:
|
||||
VALIDATION_PROFILE: full
|
||||
run: make check
|
||||
@@ -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
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
# Local credentials and environment files
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
vault-init-keys.json
|
||||
*.key
|
||||
*.pem
|
||||
*.p12
|
||||
*.pfx
|
||||
*.jks
|
||||
credentials.json
|
||||
id_dsa
|
||||
id_ecdsa
|
||||
id_ed25519
|
||||
id_rsa
|
||||
|
||||
# Kubernetes local access
|
||||
kubeconfig
|
||||
kubeconfig.*
|
||||
*.kubeconfig
|
||||
|
||||
# Python
|
||||
.venv/
|
||||
venv/
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
.pytest_cache/
|
||||
.mypy_cache/
|
||||
.ruff_cache/
|
||||
|
||||
# Rendered and temporary infrastructure artifacts
|
||||
.rendered/
|
||||
rendered/
|
||||
*.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
|
||||
**/.terraform/*
|
||||
**/.terragrunt-cache/*
|
||||
*.backup
|
||||
*.snapshot
|
||||
*.tmp
|
||||
.decrypted/
|
||||
*.decrypted.yaml
|
||||
*.dec.yaml
|
||||
.build/
|
||||
.cache/
|
||||
dist/
|
||||
tmp/
|
||||
coverage/
|
||||
*.log
|
||||
|
||||
# Editor and operating-system files
|
||||
.idea/
|
||||
.vscode/
|
||||
.DS_Store
|
||||
*.swp
|
||||
*.swo
|
||||
@@ -0,0 +1,12 @@
|
||||
title = "project-infra secret scanning"
|
||||
|
||||
[extend]
|
||||
useDefault = true
|
||||
|
||||
[[allowlists]]
|
||||
description = "Documentation placeholders"
|
||||
regexes = [
|
||||
'''REPLACE_VIA_[A-Z0-9_]+''',
|
||||
'''<[^>]+>''',
|
||||
'''\$\{[A-Z0-9_]+\}''',
|
||||
]
|
||||
@@ -0,0 +1,10 @@
|
||||
checks:
|
||||
addAllBuiltIn: false
|
||||
exclude:
|
||||
# These relationships are created by controllers/CRs outside the rendered
|
||||
# Deployment set, so static object matching cannot resolve them.
|
||||
- dangling-service
|
||||
- non-existent-service-account
|
||||
# Lab intentionally runs single replicas; topology spread is still declared
|
||||
# on application workloads. staging/prod must add replicas and PDBs.
|
||||
- no-anti-affinity
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
[tools]
|
||||
kubectl = "1.36.3"
|
||||
kustomize = "5.8.1"
|
||||
kubeconform = "0.8.0"
|
||||
kube-linter = "0.8.3"
|
||||
shellcheck = "0.11.0"
|
||||
shfmt = "3.13.1"
|
||||
gitleaks = "8.30.1"
|
||||
helm = "3.19.4"
|
||||
jq = "1.8.1"
|
||||
ripgrep = "14.1.1"
|
||||
"aqua:minio/mc" = "RELEASE.2025-08-13T08-35-41Z"
|
||||
@@ -11,26 +11,38 @@ Read order:
|
||||
> **WARNING TO AI AGENTS**: You MUST READ the files in `/docs/standards/infra/` and `/docs/examples/infra/` using your file reading tools BEFORE proposing or writing any K8s manifests. DO NOT rely on generic Kubernetes knowledge. The standard documents contain mandatory strict rules (e.g., probe settings, security contexts, non-root constraints, resource limits, namespace strategies) that MUST be hardcoded into your output. Generating plain boilerplate YAML without strict standards compliance is a critical failure.
|
||||
|
||||
Repo role:
|
||||
- this repository owns Kubernetes/K3s infrastructure source
|
||||
- source of truth is Git + Kustomize directories under `k8s/`
|
||||
- this repository owns infrastructure lifecycle and Kubernetes/K3s desired state
|
||||
- source of truth is Git + IaC under `infrastructure/` + Kustomize under `gitops/`
|
||||
- scripts are helper tools, not the source of truth
|
||||
- runtime cluster state or server-local manifest files are not authoritative
|
||||
|
||||
Primary directories:
|
||||
- `k8s/base/`: environment-neutral Kustomize bases
|
||||
- `k8s/base/app/`: application-facing workload units
|
||||
- `k8s/base/managing/`: management/operations units such as bootstrap, migration, backup, restore, and admin jobs
|
||||
- `k8s/base/plugins/`: platform/plugin-style base resources
|
||||
- `k8s/overlays/<env>/`: environment overlays such as `dev`, `staging`, and `prod`
|
||||
- `k8s/scripts/`: render, diff, apply, validate, backup, and restore helpers
|
||||
- `bootstrap/`: foundation and selected GitOps controller bootstrap
|
||||
- `infrastructure/components/`: reusable IaC primitives
|
||||
- `infrastructure/stacks/`: optional reusable compositions
|
||||
- `infrastructure/live/`: the only IaC plan/apply and state entrypoints
|
||||
- `gitops/apps/`: app-owned workloads, data, and one-shot operations
|
||||
- `gitops/platform/`: shared services, operators, ingress, mesh, storage, and secret delivery
|
||||
- `gitops/policies/`: reusable governance and security policy
|
||||
- `gitops/tenants/`: namespace, RBAC, quota, and tenant boundaries
|
||||
- `gitops/clusters/<env>/<cluster>/namespaces` and `stages/`: current deployable Kubernetes entrypoints
|
||||
- `gitops/clusters/<env>/<cluster>/all/`: render/audit aggregate; never apply it
|
||||
- `scripts/`: render, diff, apply, validate, backup, and restore helpers
|
||||
- `docs/standards/infra/`: infra standards
|
||||
- `docs/examples/infra/`: approved examples
|
||||
|
||||
Current runtime exception:
|
||||
- `lab` has no Argo CD/Flux controller; `scripts/bin/bootstrap.sh` applies the
|
||||
cluster `namespaces` entrypoint and ordered `stages/*`; `all/` remains audit-only
|
||||
|
||||
Structure principle:
|
||||
- `k8s/base` is organized by operational ownership and workload role, not by environment.
|
||||
- `k8s/overlays` is organized by environment first, so large fleets can be rendered, diffed, applied, and audited environment-by-environment.
|
||||
- This repo intentionally does not use the small-project “each service owns its own base and overlays” pattern as the primary layout.
|
||||
- Service ownership still exists under `k8s/base/app/<domain>/<workload>/**`; environment rollout ownership lives under `k8s/overlays/<env>`.
|
||||
- catalogs are organized by ownership; every unit may contain an environment-neutral `base` and focused `overlays/<env>`
|
||||
- catalog directories are never deployment/controller roots
|
||||
- `infrastructure/live` and `gitops/clusters` are environment-first entrypoint trees
|
||||
- current `lab` rollout ownership lives under `gitops/clusters/lab/main/namespaces`
|
||||
and `gitops/clusters/lab/main/stages`
|
||||
- `_template` is copied to start new units and is never imported by live/cluster roots
|
||||
- approved manifest examples live only under `docs/examples/infra`
|
||||
|
||||
Hard bans:
|
||||
- do not treat `/var/lib/rancher/k3s/server/manifests` as source of truth
|
||||
@@ -38,6 +50,7 @@ Hard bans:
|
||||
- do not put production secrets in Git/plain manifests
|
||||
- do not bypass Kustomize with ad-hoc generated YAML as the primary path
|
||||
- do not mix app rollout, DB migration, and control-plane upgrade in one opaque step
|
||||
- do not apply `gitops/clusters/<env>/<cluster>/all`; deploy ordered stage entrypoints
|
||||
- do not expose health, metrics, admin, or management endpoints publicly by default
|
||||
- do not use `hostPath` as an operating default
|
||||
- do not use `start-dev`/dev-mode style configs for production components
|
||||
@@ -84,16 +97,17 @@ Component routing:
|
||||
-> `/docs/standards/infra/flyway.md`
|
||||
|
||||
Before editing:
|
||||
- identify target environment: dev / staging / prod
|
||||
- identify target unit: app / managing / plugin / script / docs
|
||||
- identify whether the change belongs in `k8s/base`, `k8s/overlays/<env>`, `k8s/scripts`, or docs
|
||||
- identify target environment: lab / staging / prod
|
||||
- identify target unit: app / data / platform / operation / script / docs
|
||||
- identify whether the change belongs in `bootstrap`, `infrastructure`, a `gitops` catalog,
|
||||
`gitops/clusters`, `scripts`, or docs
|
||||
- identify workload type: Deployment / StatefulSet / Job / CronJob
|
||||
- identify whether storage, secret delivery, ingress, migration, or rollback path changes
|
||||
- identify whether the change touches a K3s-specific rule
|
||||
|
||||
Default execution flow:
|
||||
- read the owning standard first
|
||||
- prefer changing Kustomize source under `k8s/` over live cluster state
|
||||
- prefer changing IaC/Kustomize source under `infrastructure/` or `gitops/` over live state
|
||||
- prefer render -> validate -> diff -> apply thinking
|
||||
- prefer explicit rollback/restore path before risky changes
|
||||
|
||||
|
||||
@@ -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,34 @@
|
||||
SHELL := /bin/bash
|
||||
.DEFAULT_GOAL := help
|
||||
|
||||
.PHONY: help doctor validate validate-structure validate-project check tree
|
||||
|
||||
help: ## 사용 가능한 명령을 표시합니다.
|
||||
@awk 'BEGIN {FS = ":.*## "; print "Usage: make <target>"} /^[a-zA-Z_-]+:.*## / {printf " %-20s %s\n", $$1, $$2}' $(MAKEFILE_LIST)
|
||||
|
||||
doctor: ## 현재 로컬 도구 상태를 확인합니다.
|
||||
@./scripts/doctor.sh
|
||||
|
||||
validate-structure: ## 템플릿 구조, 민감 파일, Kustomize, Helm과 IaC 형식을 검증합니다.
|
||||
@./scripts/validate.sh
|
||||
|
||||
validate-project: ## 프로젝트 entrypoint, schema, policy, shell과 문서를 검증합니다.
|
||||
@bash ./scripts/ci/validate.sh
|
||||
|
||||
validate: validate-structure validate-project ## 구조 및 프로젝트 검증을 모두 실행합니다.
|
||||
|
||||
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
|
||||
@@ -1,166 +1,156 @@
|
||||
# Project-Infra
|
||||
# Project Infra
|
||||
|
||||
K3s 기반 백엔드 실행 환경을 Git으로 관리하는 개인 프로젝트입니다.
|
||||
[Project-Auth-Server](https://github.com/donghyeon-ka/project-auth-server)가 실제로 실행될 때 필요한 인증 게이트, secret 전달, DB 마이그레이션, 네트워크 정책을 Kubernetes 리소스로 구성했습니다.
|
||||
K3s 기반 인증 플랫폼의 인프라 source-of-truth입니다. 범용
|
||||
`k8s-template`의 lifecycle/ownership 스켈레톤을 적용하고, 기존 리소스를
|
||||
IaC catalog와 Kubernetes desired-state catalog로 분리했습니다.
|
||||
|
||||
이 프로젝트에서 확인하고 싶었던 질문은 네 가지입니다.
|
||||
현재 실제 배포 환경은 폐기 가능한 단일 클러스터용 `lab`입니다. `dev`,
|
||||
`staging`, `prod` IaC root는 확장 위치만 예약되어 있으며 아직 실행 가능한
|
||||
Terraform/OpenTofu 구성이 아닙니다.
|
||||
|
||||
- 로그인 / 세션 처리를 애플리케이션 밖으로 빼면 ingress와 backend의 책임은 어떻게 나뉘는가?
|
||||
- secret을 Vault에 두면서도 Pod는 Kubernetes-native하게 실행할 수 있는가?
|
||||
- default-deny NetworkPolicy 환경에서 백엔드 워크로드가 어떤 통신을 명시해야 하는가?
|
||||
- dev에 집중하되, staging / prod 승격 시 달라질 정책 지점을 overlay 구조로 남길 수 있는가?
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **Runtime** | K3s 1.30, Kustomize, Helm, Bash |
|
||||
| **Ingress / Auth** | Traefik, oauth2-proxy, Keycloak Operator |
|
||||
| **Secrets** | HashiCorp Vault, Vault Secrets Operator |
|
||||
| **Data** | PostgreSQL, Flyway, MinIO, Docker Registry |
|
||||
| **Policy** | NetworkPolicy default-deny, PSS Restricted |
|
||||
| **Validation** | `kustomize build`, `kubeconform -strict`, `kube-linter` |
|
||||
| **Scope** | dev overlay 중심. staging / prod는 의도적으로 미구현 |
|
||||
|
||||
---
|
||||
|
||||
## Architecture
|
||||
|
||||

|
||||
|
||||
큰 흐름은 두 가지입니다.
|
||||
|
||||
- 요청 흐름: `Traefik → oauth2-proxy → auth-server → PostgreSQL / MinIO`
|
||||
- secret 흐름: `Vault → VSO → Kubernetes Secret → Pod envFrom`
|
||||
|
||||
상세 다이어그램:
|
||||
|
||||
- [ForwardAuth cold path](docs/diagrams/sequence/forward-auth-cold.md) / [warm path](docs/diagrams/sequence/forward-auth-warm.md)
|
||||
- [Secret pipeline bootstrap](docs/diagrams/sequence/secret-pipeline-bootstrap.md) / [runtime reconcile](docs/diagrams/sequence/secret-pipeline-runtime.md)
|
||||
- [전체 아키텍처 설명](docs/architecture.md)
|
||||
|
||||
---
|
||||
|
||||
## Engineering Decisions
|
||||
|
||||
### 1. 인증은 Ingress에서 먼저 막고, 백엔드는 JWT를 다시 검증
|
||||
|
||||
Traefik `ForwardAuth → oauth2-proxy → Keycloak` 조합으로 미인증 요청을 ingress 계층에서 먼저 차단합니다.
|
||||
그 뒤 auth-server는 Keycloak JWT를 Spring Security Resource Server로 다시 검증합니다.
|
||||
|
||||
이 구조는 로그인 / 세션 처리와 API 권한 검증을 분리하기 위한 선택입니다. 인증 정책 변경은 애플리케이션 코드보다 Kubernetes manifest 변경으로 다룰 수 있습니다.
|
||||
|
||||

|
||||
|
||||
### 2. Vault는 source of truth, Pod는 Kubernetes Secret만 소비
|
||||
|
||||
Pod마다 Vault Agent sidecar를 붙이지 않고, Vault Secrets Operator가 Vault KV 값을 Kubernetes Secret으로 동기화합니다.
|
||||
## Lifecycle
|
||||
|
||||
```text
|
||||
Vault KV-v2 → VSO reconcile → Kubernetes Secret → Pod envFrom
|
||||
bootstrap/foundation
|
||||
|
|
||||
v
|
||||
infrastructure/live
|
||||
|
|
||||
v
|
||||
bootstrap/gitops
|
||||
|
|
||||
v
|
||||
gitops/clusters ──> platform / policies / tenants / apps
|
||||
```
|
||||
|
||||
Pod는 Vault endpoint, token, template rendering을 직접 알지 않습니다. 대신 secret이 Kubernetes Secret으로 존재하므로, etcd encryption-at-rest가 다음 검증 항목으로 남습니다.
|
||||
- `bootstrap/foundation`: remote state와 초기 identity 같은 선행 조건
|
||||
- `infrastructure/live`: 네트워크, IAM, DNS, load balancer와 클러스터 생성 root
|
||||
- `bootstrap/gitops`: 선택한 GitOps controller와 cluster root 연결
|
||||
- `gitops/clusters`: Kubernetes desired state의 실제 배포 entrypoint
|
||||
|
||||
### 3. Vault role / policy는 도메인별로 분리
|
||||
현재는 Argo CD/Flux가 설치되어 있지 않으므로 `bootstrap/gitops`는 확장
|
||||
계약만 제공합니다. 실제 `lab` 배포는 readiness 경계를 보존하는
|
||||
`scripts/bin/bootstrap.sh`가 ordered stage를 server-side apply합니다.
|
||||
|
||||
VSO Operator의 ServiceAccount는 하나지만, Vault 쪽 role과 policy는 `vso-auth-platform` / `vso-storage`로 나눴습니다.
|
||||
각 `VaultStaticSecret`은 자기 도메인의 `VaultAuth`만 참조합니다.
|
||||
## Repository layout
|
||||
|
||||
auth-platform 권한으로 storage secret 경로에 접근하지 못하게 하려는 결정입니다.
|
||||
|
||||
상세 매핑은 [docs/vault-vso.md](docs/vault-vso.md#vaultauth--vaultstaticsecret-매핑)를 참고합니다.
|
||||
|
||||
### 4. NetworkPolicy는 default-deny에서 시작
|
||||
|
||||
`mnt` namespace 안에서도 모든 Pod 간 ingress / egress를 기본 차단합니다.
|
||||
새 워크로드를 추가하려면 필요한 통신을 NetworkPolicy로 명시해야 합니다.
|
||||
|
||||
이 마찰은 의도한 것입니다. wide-open으로 시작하는 실수를 줄이고, 워크로드 간 통신 관계를 코드로 남기기 위함입니다.
|
||||
|
||||
상세 매트릭스는 [docs/networking.md](docs/networking.md)를 참고합니다.
|
||||
|
||||
### 5. K3s packaged Traefik manifest는 직접 수정하지 않음
|
||||
|
||||
K3s가 관리하는 Traefik manifest를 직접 수정하면 재부팅 / 재적용 시 덮어써질 수 있습니다.
|
||||
그래서 `HelmChartConfig` overlay와 Middleware / TLSOption 리소스로 운영 정책을 관리합니다.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## Repository Layout
|
||||
|
||||
Kustomize `base / components / overlays` 구조입니다.
|
||||
|
||||
| Path | Role |
|
||||
| Path | Responsibility |
|
||||
| --- | --- |
|
||||
| `k8s/base/` | 환경 중립 매니페스트 |
|
||||
| `k8s/components/` | 재사용 component. 현재 ForwardAuth component |
|
||||
| `k8s/overlays/dev/` | 기본 dev 환경. 현재 ForwardAuth component 포함 |
|
||||
| `k8s/overlays/{staging,prod}/` | 의도적으로 비워둔 승격 지점 |
|
||||
| `k8s/scripts/` | bootstrap / validation / reusable tasks |
|
||||
| `terraform/` | contracts만 존재. 추후 구현 |
|
||||
| `docs/` | 상세 설계와 운영 문서 |
|
||||
| `bootstrap/` | foundation 및 GitOps controller bootstrap |
|
||||
| `infrastructure/components/` | 재사용 가능한 compute/database/networking/storage IaC 단위 |
|
||||
| `infrastructure/stacks/` | 반복되는 component 조합 |
|
||||
| `infrastructure/live/<env>/<leaf>/` | 실제 IaC plan/apply와 state 경계 |
|
||||
| `gitops/apps/` | auth-server, PostgreSQL, migration 같은 app 소유 배포 정의 |
|
||||
| `gitops/platform/` | Traefik, Vault, VSO, Keycloak, MinIO, registry와 operator |
|
||||
| `gitops/policies/` | 공통 보안·거버넌스 정책 |
|
||||
| `gitops/tenants/` | namespace, RBAC, quota 같은 tenant 경계 |
|
||||
| `gitops/clusters/lab/main/namespaces`, `stages/` | 현재 실제 배포 entrypoint |
|
||||
| `gitops/clusters/lab/main/all/` | 전체 render/schema/policy 감사 전용; apply 금지 |
|
||||
| `scripts/` | staged bootstrap, teardown와 검증 도구 |
|
||||
| `tests/kustomize-entrypoints.txt` | 검증할 Kustomize entrypoint inventory |
|
||||
|
||||
---
|
||||
`_template` 디렉터리는 새 component/root를 만들 때 복제하는 뼈대입니다.
|
||||
이 저장소는 이미 실제 `lab` 구성이 조립된 프로젝트이므로 루트에 별도
|
||||
`examples/`를 유지하지 않습니다. 현재 구현을 참고하고 새 단위는 각 책임
|
||||
폴더의 `_template`에서 시작합니다.
|
||||
|
||||
## Validation
|
||||
예를 들어 새 환경과 app을 시작할 때 다음처럼 복제합니다.
|
||||
|
||||
```bash
|
||||
bash k8s/scripts/ci/validate.sh
|
||||
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/apps/_template gitops/apps/example-api
|
||||
```
|
||||
|
||||
`validate.sh`는 주요 overlay에 대해 세 단계를 수행합니다.
|
||||
복제 후 `__REPLACE_ME_*__` 값을 교체하고, 실제 cluster root에서 필요한
|
||||
catalog만 참조합니다.
|
||||
|
||||
1. `kustomize build` — overlay 조립과 patch 유효성 확인
|
||||
2. `kubeconform -strict` — Kubernetes / CRD schema 확인
|
||||
3. `kube-linter` — securityContext, resources, image tag 등 정적 점검
|
||||
|
||||
목표 상태:
|
||||
## Current architecture
|
||||
|
||||
```text
|
||||
k8s/overlays/dev build=ok schema=ok lint=ok
|
||||
k8s/overlays/dev/vso build=ok schema=ok lint=ok
|
||||
Traefik -> oauth2-proxy -> auth-server -> PostgreSQL
|
||||
\-> Keycloak
|
||||
docker-registry -> MinIO
|
||||
|
||||
Vault KV-v2 -> Vault Secrets Operator -> Kubernetes Secret -> workload
|
||||
```
|
||||
|
||||
운영 절차는 [docs/operations.md](docs/operations.md), 실행 매뉴얼은 [guide.md](guide.md)를 참고합니다.
|
||||
- Namespace `mnt`에는 PSS Restricted와 default-deny NetworkPolicy를 적용합니다.
|
||||
- K3s packaged Traefik manifest는 수정하지 않고 `HelmChartConfig`만 관리합니다.
|
||||
- Flyway와 Keycloak realm import는 versioned one-shot operation입니다.
|
||||
- `lab`의 PostgreSQL, Vault와 MinIO는 운영 HA/backup 설계가 아닙니다.
|
||||
|
||||
---
|
||||
## Deploy
|
||||
|
||||
## Current Status
|
||||
```bash
|
||||
export KUBE_CONTEXT_LAB='<expected-context>'
|
||||
bash scripts/bin/bootstrap.sh lab
|
||||
```
|
||||
|
||||
| Area | Status |
|
||||
|---|---|
|
||||
| dev overlay 매니페스트 | 구성됨 (kustomize / kubeconform / kube-linter 검증 가능) |
|
||||
| Vault + VSO 부트스트랩 | idempotent script 로 구성 |
|
||||
| Traefik HelmChartConfig + Middleware | 구성됨 |
|
||||
| NetworkPolicy default-deny | 구성됨 |
|
||||
| ForwardAuth | dev overlay에 포함됨 |
|
||||
| KeycloakRealmImport | overlay 준비됨. 실제 적용 결과 확인 필요 |
|
||||
| cert-manager + ClusterIssuer | manifest 준비됨. 외부 DNS 필요 |
|
||||
| staging / prod overlay | 의도적으로 비워둠 |
|
||||
| Terraform | 디렉토리 contracts만 존재 |
|
||||
배포 순서는 다음과 같습니다.
|
||||
|
||||
---
|
||||
```text
|
||||
namespaces
|
||||
00-platform
|
||||
10-vault
|
||||
20-secrets
|
||||
30-data
|
||||
35-registry
|
||||
40-operations
|
||||
50-apps
|
||||
```
|
||||
|
||||
## Limitations
|
||||
`gitops/clusters/lab/main/all`은 직접 apply하지 않습니다. CRD establishment,
|
||||
Vault 초기화, Secret materialization과 migration 완료는 한 번의 Kustomize
|
||||
apply로 표현할 수 없습니다.
|
||||
|
||||
운영 완료 상태가 아니라 dev 환경에서 실행 구조를 검증한 프로젝트입니다.
|
||||
## Validate
|
||||
|
||||
- Vault는 file backend 단일 노드입니다. dev에서는 secret 전달 경로와 VSO reconcile을 검증하는 데 충분하다고 보고 선택했습니다. prod에서는 `raft` storage와 KMS auto-unseal로 전환해야 합니다.
|
||||
- cert-manager / ClusterIssuer manifest는 있지만, 실제 ACME 인증서 발급은 외부 DNS가 Traefik 진입점을 가리켜야 완료됩니다.
|
||||
- ForwardAuth 로그인 / 로그아웃 / deny-allow E2E 검증은 인증서와 DNS 정리 후 진행할 항목입니다.
|
||||
- Postgres 백업, Terraform 실제 모듈, staging/prod overlay는 아직 구현하지 않았습니다.
|
||||
```bash
|
||||
make doctor
|
||||
make check
|
||||
|
||||
---
|
||||
# CI와 같은 pinned toolchain을 사용한 full gate
|
||||
mise install
|
||||
VALIDATION_PROFILE=full make check
|
||||
```
|
||||
|
||||
`make check`는 템플릿 구조/민감 파일/IaC 계약 검증과 프로젝트별
|
||||
Kustomize/schema/policy/shell/docs 검증을 모두 실행합니다.
|
||||
|
||||
## Extension rules
|
||||
|
||||
- 외부 load balancer, VPC와 DNS는 `infrastructure/components`에서 구현하고
|
||||
`infrastructure/live` root가 조립합니다.
|
||||
- in-cluster ingress/LB controller, Istio 같은 service mesh는
|
||||
`gitops/platform/<unit>`에 두고 cluster stage가 선택합니다.
|
||||
- sidecar는 workload 기본 계약이면 app `base`, 환경별 선택이면 해당
|
||||
`overlays/<env>`에 patch/component로 구성합니다.
|
||||
- VSO와 secret delivery controller는 `gitops/platform/secret-delivery`,
|
||||
실제 비밀값은 Vault에 둡니다.
|
||||
- 규모가 커지면 `live/<provider>/<account>/<region>/<env>/<stack>`과
|
||||
`clusters/<env>/<region>/<cluster>`로 깊이만 확장하고 ownership 경계는
|
||||
유지합니다.
|
||||
|
||||
## Secret incident boundary
|
||||
|
||||
`vault-init-keys.json`은 Git에서 제외되며 로컬 권한은 `0600`이어야 합니다.
|
||||
과거 Git 이력에 포함된 값은 이미 노출된 것으로 간주해야 하므로 실제 Vault
|
||||
root token과 unseal/recovery material을 회전한 뒤, 협업자와 조율하여 원격
|
||||
이력을 별도로 정리해야 합니다.
|
||||
|
||||
## Documentation
|
||||
|
||||
- [docs/architecture.md](docs/architecture.md) — 전체 구조, 워크로드 표, 이미지 정책
|
||||
- [docs/networking.md](docs/networking.md) — NetworkPolicy 매트릭스 / 작성 규칙
|
||||
- [docs/ingress-traefik.md](docs/ingress-traefik.md) — Traefik, ForwardAuth, cert-manager, KeycloakRealmImport
|
||||
- [docs/vault-vso.md](docs/vault-vso.md) — Vault auth, policy/role, VSO Secret catalog
|
||||
- [docs/operations.md](docs/operations.md) — bootstrap 단계, validate.sh, 환경별 차등
|
||||
- [docs/troubleshooting.md](docs/troubleshooting.md) — 운영 중 만난 함정 7건 (사건 카탈로그)
|
||||
- [docs/diagrams/architecture/](docs/diagrams/architecture/) — draw.io 아키텍처 그림
|
||||
- [docs/diagrams/sequence/](docs/diagrams/sequence/) — Mermaid sequence diagrams
|
||||
- [guide.md](guide.md) — 실제 실행 매뉴얼
|
||||
- [실행 가이드](guide.md)
|
||||
- [프로젝트 아키텍처](docs/architecture.md)
|
||||
- [스켈레톤 구조 계약](docs/architecture/repository-structure.md)
|
||||
- [운영과 검증](docs/operations.md)
|
||||
- [Vault와 VSO](docs/vault-vso.md)
|
||||
- [Ingress와 Traefik](docs/ingress-traefik.md)
|
||||
- [보안 강화](docs/security-hardening.md)
|
||||
- [장애 기록](docs/troubleshooting.md)
|
||||
- [템플릿 사용 가이드](docs/guides/getting-started.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,25 @@
|
||||
# GitOps Bootstrap
|
||||
|
||||
Flux, Argo CD 등 **하나의** GitOps controller를 선택해 설치하고 cluster root에
|
||||
연결하는 확장 위치입니다. 현재 프로젝트에는 controller가 없으며 `lab`은
|
||||
`scripts/bin/bootstrap.sh`가 stage를 순서대로 적용합니다.
|
||||
|
||||
포함 범위:
|
||||
|
||||
- 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`에 문서화합니다.
|
||||
|
||||
현재 `gitops/clusters/lab/main/all`은 readiness 순서를 표현하지 못하는 감사용
|
||||
aggregate이므로 controller root로 연결하지 않습니다. Controller를 도입할 때는
|
||||
`docs/decisions`에 선택과 ordering/health 모델을 기록한 뒤 각 stage를 순차
|
||||
reconcile하도록 구성합니다.
|
||||
+85
-139
@@ -1,147 +1,93 @@
|
||||
# Architecture 상세
|
||||
# Architecture
|
||||
|
||||
README 의 Architecture / Key Components 를 보충하는 문서. 폴더 구조 전체와 워크로드, 이미지 정책을 모은다.
|
||||
## Ownership model
|
||||
|
||||
## 폴더 구조
|
||||
Catalog는 환경이 아니라 workload ownership으로 나눕니다.
|
||||
|
||||
```
|
||||
Project-Infra/
|
||||
├── .gitignore # vault-init-keys.json 제외
|
||||
├── .kube-linter.yaml # kube-linter 규칙 (컨텍스트 오탐 4종 제외)
|
||||
├── README.md # 본 프로젝트 진입 문서
|
||||
├── guide.md # 운영 절차서
|
||||
├── docs/ # README 보조 분할 문서
|
||||
│
|
||||
├── k8s/
|
||||
│ ├── base/
|
||||
│ │ ├── managing/
|
||||
│ │ │ ├── namespace/ # mnt namespace + PSS restricted 라벨
|
||||
│ │ │ └── migration-flyway/ # Flyway Job (공식 이미지)
|
||||
│ │ │
|
||||
│ │ ├── app/ # 애플리케이션 워크로드 (소유권 = 개발팀)
|
||||
│ │ │ ├── identity/auth/
|
||||
│ │ │ │ ├── stateful/identity-postgres/
|
||||
│ │ │ │ └── stateless/auth-server/
|
||||
│ │ │ ├── storage/minio/stateful/minio/
|
||||
│ │ │ └── test/stateless/test-server-{1,2,3}/
|
||||
│ │ │
|
||||
│ │ └── plugins/ # 플랫폼 플러그인 (다른 워크로드가 의존)
|
||||
│ │ ├── vault/ # Vault StatefulSet (공식 이미지)
|
||||
│ │ ├── docker-registry/ # Registry Deployment (공식 이미지)
|
||||
│ │ ├── oauth2-proxy/ # ForwardAuth 용 auth proxy
|
||||
│ │ └── vso/ # VSO CRD 리소스 (VaultConnection / VaultAuth / VaultStaticSecret)
|
||||
│ │
|
||||
│ ├── components/
|
||||
│ │ └── forward-auth/ # oauth2-proxy + Traefik ForwardAuth 재사용 component
|
||||
│ │
|
||||
│ ├── overlays/
|
||||
│ │ ├── dev/
|
||||
│ │ │ ├── kustomization.yaml # dev 전체 집계 (namespace: mnt)
|
||||
│ │ │ ├── networkpolicy-baseline.yaml # default-deny + DNS egress
|
||||
│ │ │ ├── platform/
|
||||
│ │ │ │ ├── traefik/ # HelmChartConfig + Middleware + TLSOption
|
||||
│ │ │ │ ├── cert-manager/ # cert-manager v1.20.2
|
||||
│ │ │ │ ├── cert-manager-issuers/ # letsencrypt-staging/prod ClusterIssuer
|
||||
│ │ │ │ └── keycloak-operator/ # Keycloak Operator 26.6.1
|
||||
│ │ │ ├── tls/ # cert-manager 적용 후 Certificate
|
||||
│ │ │ ├── vault/ # Vault overlay + NetworkPolicy + storage patch
|
||||
│ │ │ ├── registry/ # Registry overlay + NetworkPolicy + storage patch
|
||||
│ │ │ ├── vso/ # VSO CRDs (Helm 설치 후 별도 apply)
|
||||
│ │ │ ├── database/ # identity-postgres + VaultStaticSecret
|
||||
│ │ │ ├── auth/ # auth-server + Ingress(project.com) + flyway
|
||||
│ │ │ ├── keycloak/ # Keycloak CR + public Ingress
|
||||
│ │ │ ├── keycloak-realm/ # KeycloakRealmImport (Git-managed realm/client)
|
||||
│ │ │ ├── storage/ # minio + VaultStaticSecret + certConfig FQDN patch
|
||||
│ │ │ └── test/ # test-server 1/2/3
|
||||
│ │ ├── components/forward-auth/ # dev overlay 에 포함되는 ForwardAuth component
|
||||
│ │ ├── staging/ # 의도적으로 비어둠
|
||||
│ │ └── prod/ # 의도적으로 비어둠
|
||||
│ │
|
||||
│ └── scripts/
|
||||
│ ├── bin/ # 사용자 진입점 (bootstrap.sh / teardown.sh)
|
||||
│ ├── ci/validate.sh # kustomize + kubeconform + kube-linter
|
||||
│ ├── lib/ # 공통 라이브러리 (common.sh / vault.sh)
|
||||
│ └── tasks/ # 재사용 작업 (vault-init / vault-seed-apps / vso-install)
|
||||
│
|
||||
└── terraform/ # contracts 만 존재, 추후 구현
|
||||
```
|
||||
|
||||
## 네임스페이스 전략
|
||||
|
||||
`mnt` 단일 namespace. 학습 단계의 단순성 우선. 실무에서는 역할별 namespace(`auth`, `storage`, `security`, `registry`) 분리가 원칙이며, base 는 환경 중립이라 overlay 재구성으로 분리 가능하다.
|
||||
|
||||
- Pod Security Standards: `pod-security.kubernetes.io/enforce=restricted` (audit + warn 동시).
|
||||
- 모든 리소스는 overlay 의 `namespace: mnt` 로 일괄 주입.
|
||||
|
||||
## 워크로드 목록
|
||||
|
||||
| 워크로드 | 종류 | 위치 | 참조 Secret |
|
||||
|---|---|---|---|
|
||||
| `identity-postgres` | StatefulSet | `base/app/identity/auth/stateful/` | `identity-postgres-superuser`, `keycloak-db`, `auth-server-db` |
|
||||
| [`auth-server`](https://github.com/donghyeon-ka/project-auth-server/tree/develop) | Deployment | `base/app/identity/auth/stateless/` | `auth-server-db` |
|
||||
| `keycloak` | Keycloak CR (Operator 생성 StatefulSet) | `overlays/dev/keycloak/` | `keycloak-db-operator`, `keycloak-bootstrap-admin-operator` |
|
||||
| `minio` | Tenant CRD | `base/app/storage/minio/stateful/` | `minio-tenant-env` |
|
||||
| `test-server-1/2/3` | Deployment | `base/app/test/stateless/` | — |
|
||||
| `migration-flyway` | Job (PreSync / sync-wave=-1) | `base/managing/migration-flyway/` | `auth-server-db` |
|
||||
| `vault` | StatefulSet | `base/plugins/vault/` | — |
|
||||
| `docker-registry` | Deployment | `base/plugins/docker-registry/` | `docker-registry-basic-auth`, `docker-registry-pull-credentials` |
|
||||
|
||||
auth-server / keycloak 모두 `jdbc:postgresql://identity-postgres:5432/<db>` 로 short name 접속 (같은 namespace).
|
||||
|
||||
### Flyway 실행 순서
|
||||
|
||||
dev overlay 가 migration-flyway Job 에 ArgoCD annotation 을 patch:
|
||||
|
||||
```
|
||||
argocd.argoproj.io/sync-wave: "-1"
|
||||
argocd.argoproj.io/hook: PreSync
|
||||
```
|
||||
|
||||
ArgoCD 배포 시 Job 이 앱보다 먼저 돌고 스키마 마이그레이션을 마친 뒤 `auth-server` 가 뜬다.
|
||||
|
||||
### Keycloak hostname patch
|
||||
|
||||
dev overlay JSON patch 가 Keycloak ConfigMap 에 다음을 주입:
|
||||
|
||||
- `KC_HOSTNAME=https://keycloak.dev.example.com`
|
||||
- `KC_HOSTNAME_ADMIN=https://keycloak-admin.dev.example.com`
|
||||
|
||||
staging / prod 는 자체 hostname 을 overlay 에서 주입.
|
||||
|
||||
### MinIO certConfig.dnsNames
|
||||
|
||||
base 는 short name(`minio`, `minio-hl`) 만 둔다. dev overlay 에서 `minio.mnt.svc.cluster.local`, `*.minio-hl.mnt.svc.cluster.local` 을 patch — base 환경 중립성 원칙.
|
||||
|
||||
## 이미지 정책
|
||||
|
||||
| 구분 | 이미지 | 근거 |
|
||||
|---|---|---|
|
||||
| 공식 upstream | `hashicorp/vault:1.17.2` | HashiCorp 공식 |
|
||||
| | `registry:2.8.3` | Docker library 공식 |
|
||||
| | `postgres:16.4` | PostgreSQL 공식 |
|
||||
| | `quay.io/keycloak/keycloak:26.6.1` | Keycloak Operator 26.6.1 관리 |
|
||||
| | `minio/minio:RELEASE.2025-01-20T14-49-07Z` | MinIO 공식 |
|
||||
| | `flyway/flyway:10.20.1` | Flyway 공식 |
|
||||
| 사용자 개발 | `registry.example.com/auth-platform/auth-server:0.1.0` | 조직 개발 서비스 |
|
||||
| | `registry.example.com/test-platform/test-server-{1,2,3}:0.1.0` | 조직 개발 서비스 |
|
||||
|
||||
prod 승격 시 공식 이미지도 digest pin(`@sha256:…`)으로 전환.
|
||||
|
||||
## Docker Registry
|
||||
|
||||
| 항목 | 값 |
|
||||
| Area | Owned resources |
|
||||
| --- | --- |
|
||||
| 이미지 | `registry:2.8.3` |
|
||||
| 내부 서비스 | `docker-registry.mnt.svc.cluster.local:5000` |
|
||||
| 외부 Ingress | `registry.project.com` (`/v2` only) |
|
||||
| 인증 | 내부 Service 무인증, 외부 Ingress + kubelet pull 만 credential 사용 |
|
||||
| 저장 | MinIO S3 bucket `docker-registry` |
|
||||
| `gitops/apps/auth-server` | auth-server |
|
||||
| `gitops/apps/identity-postgres` | identity-postgres |
|
||||
| `gitops/apps/auth-migration` | versioned Flyway Job |
|
||||
| `gitops/platform` | Vault, Keycloak, MinIO, registry와 operator |
|
||||
| `gitops/platform/forward-auth` | oauth2-proxy와 Traefik ForwardAuth integration |
|
||||
|
||||
### 인증 경계
|
||||
환경별 차이는 각 catalog의 `overlays/<env>`에 두고, rollout ownership은
|
||||
`gitops/clusters/<env>/<cluster>/stages`가 가집니다. 따라서 경로만 보고
|
||||
리소스 소유자와 실제 적용 단계를 구분할 수 있습니다.
|
||||
|
||||
Registry 자체 auth 는 켜지 않는다. 인증 경계는 두 곳:
|
||||
## Deployment graph
|
||||
|
||||
- 외부 Ingress: Traefik `Middleware/docker-registry-basic-auth` 가 VSO 로 생성된 `docker-registry-basic-auth` Secret 의 htpasswd 를 검증
|
||||
- 내부 pull: 앱 ServiceAccount 에 `docker-registry-pull-credentials` imagePullSecret
|
||||
```text
|
||||
namespaces
|
||||
|
|
||||
00-platform ---- cert-manager / Keycloak Operator / Traefik policy
|
||||
|
|
||||
+---- Helm: MinIO Operator / Vault Secrets Operator
|
||||
|
|
||||
10-vault ---- Vault Running -> init/unseal/policies/roles -> KV seed
|
||||
|
|
||||
20-secrets ---- VaultConnection / VaultAuth / pre-data VaultStaticSecret
|
||||
|
|
||||
30-data ---- PostgreSQL / MinIO / Keycloak
|
||||
|
|
||||
+---- MinIO registry bucket/access-key provisioning -> Vault
|
||||
|
|
||||
35-registry ---- registry VaultStaticSecret / docker-registry
|
||||
|
|
||||
40-operations ---- auth-server-migrate-0-1-0 / platform-realm-v1
|
||||
|
|
||||
50-apps ---- auth-server / oauth2-proxy / ingress
|
||||
```
|
||||
|
||||
따라서 `docker-registry-ingress-traefik` NetworkPolicy + BasicAuth Secret + imagePullSecret 이 함께 있어야 push/pull 양쪽이 안전하다.
|
||||
`all/`은 이 그래프를 하나로 렌더하지만 실행 순서를 보장하지 않습니다.
|
||||
따라서 validation 전용입니다.
|
||||
|
||||
## Runtime boundaries
|
||||
|
||||
| Workload | Kind | Namespace | Base |
|
||||
| --- | --- | --- | --- |
|
||||
| Vault | StatefulSet | `mnt` | `gitops/platform/vault` |
|
||||
| identity-postgres | StatefulSet | `mnt` | `gitops/apps/identity-postgres` |
|
||||
| MinIO | Tenant CR | `mnt` | `gitops/platform/minio` |
|
||||
| Keycloak | Keycloak CR | `mnt` | `gitops/platform/keycloak` |
|
||||
| docker-registry | Deployment | `mnt` | `gitops/platform/registry` |
|
||||
| auth-server | Deployment | `mnt` | `gitops/apps/auth-server` |
|
||||
| oauth2-proxy | Deployment | `mnt` | `gitops/platform/forward-auth` |
|
||||
| DB migration | Job | `mnt` | `gitops/apps/auth-migration` |
|
||||
| realm import | KeycloakRealmImport | `mnt` | `gitops/apps/keycloak-realm-import` |
|
||||
|
||||
## One-shot operation contract
|
||||
|
||||
Flyway Job 이름은 migration release를 포함합니다:
|
||||
`auth-server-migrate-0-1-0`. SQL을 변경해 새 migration release를 만들 때는
|
||||
Job instance/name도 함께 올립니다. 같은 이름의 완료된 Job을 지웠다가
|
||||
묵시적으로 재실행하지 않습니다.
|
||||
|
||||
KeycloakRealmImport도 `platform-realm-v1`처럼 versioned name을 씁니다.
|
||||
Operator는 기존 import CR의 spec 변경을 일반 workload rollout처럼
|
||||
재실행하지 않으므로, realm 변경은 새 version의 명시적 operation으로 냅니다.
|
||||
|
||||
## Namespace decision
|
||||
|
||||
현재 lab은 `mnt` 단일 namespace입니다. 이는 운영 권장 구조가 아니라 기존
|
||||
runtime을 깨지 않고 먼저 deployment lifecycle을 분리하기 위한 전환 단계입니다.
|
||||
|
||||
역할별 namespace 분리는 다음을 원자적으로 바꿔야 합니다.
|
||||
|
||||
- Service DNS와 issuer/JWK/DB endpoint
|
||||
- Vault Kubernetes auth의 bound ServiceAccount/namespace
|
||||
- VSO destination Secret 위치
|
||||
- cross-namespace NetworkPolicy
|
||||
- Flyway와 DB init credential ownership
|
||||
- operator watch namespace와 RBAC
|
||||
|
||||
따라서 단순 폴더 이동과 함께 수행하지 않습니다.
|
||||
|
||||
## Environment meaning
|
||||
|
||||
- `lab`: 폐기 가능한 K3s 검증 환경. stateful overlay의 local-path, HTTP
|
||||
ingress, 단일 replica 허용.
|
||||
- `staging`: production과 같은 보안·TLS·backup path의 승격 검증 환경.
|
||||
- `prod`: HA, digest pin, backup/restore evidence, TLS, disruption budget가
|
||||
준비되지 않으면 생성하지 않습니다.
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
# 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에서 필요한 항목만 참조합니다.
|
||||
|
||||
현재 프로젝트의 `lab`은 아직 GitOps controller가 없으므로
|
||||
`clusters/lab/main/namespaces`와 `stages/*`를 bootstrap script가 순서대로 적용합니다.
|
||||
`clusters/lab/main/all`은 reconcile root가 아닌 감사용 aggregate입니다.
|
||||
|
||||
작은 구성은 `clusters/dev/main`, 다중 리전 구성은
|
||||
`clusters/<environment>/<region>/<cluster>` 형태를 사용할 수 있습니다. 여기에도
|
||||
고정된 경로 깊이를 강제하지 않습니다.
|
||||
|
||||
### `platform`
|
||||
|
||||
cluster-wide controller와 addon을 둡니다. 예시는 다음과 같습니다.
|
||||
|
||||
- ingress/gateway, external DNS, certificate
|
||||
- storage class/CSI, autoscaling
|
||||
- metrics, logs, traces, alerting
|
||||
- secret operator와 delivery controller
|
||||
|
||||
각 component는 `base`와 필요한 `overlays`를 같은 디렉터리 안에 응집시킵니다.
|
||||
환경 차이는 전체 파일 복사 대신 Kustomize patch 또는 별도 values로 표현합니다.
|
||||
|
||||
### `policies`, `tenants`, `apps`
|
||||
|
||||
- `policies`: cluster-wide admission 규칙, 거버넌스와 예외
|
||||
- `tenants`: 구체적인 namespace, RBAC, quota, limit range, 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
|
||||
|
||||
현재 `bootstrap/gitops`에는 구현이 없으며 controller 도입 전까지 lab의 staged
|
||||
bootstrap이 이 역할을 대신합니다.
|
||||
|
||||
ingress, cert-manager, observability 같은 addon은 bootstrap이 아니라 GitOps가
|
||||
소유합니다. bootstrap 이후의 변경을 계속 수동 명령으로 누적하지 않습니다.
|
||||
|
||||
## 규모 확장 기준
|
||||
|
||||
| 단계 | 추가하는 것 | 그대로 유지하는 것 |
|
||||
|---|---|---|
|
||||
| 소형 | 단일 live root, 단일 cluster root, 최소 platform | lifecycle/ownership 경계 |
|
||||
| 중형 | reusable stack, staging/prod, 정책, 관측성 | component와 entrypoint 분리 |
|
||||
| 대형 | 계정·리전별 state, 다중 cluster, tenants, CODEOWNERS | 한 리소스 한 소유자 |
|
||||
| 조직 분리 | lifecycle/team별 repository 분리 가능 | 각 repository 내부의 동일한 계약 |
|
||||
|
||||
repository를 분리하는 시점은 폴더 수가 아니라 권한, 배포 주기와 소유 팀이
|
||||
실제로 달라졌을 때입니다.
|
||||
|
||||
## 설계 참고 자료
|
||||
|
||||
- [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을 대체했다고
|
||||
표시합니다.
|
||||
@@ -96,41 +96,38 @@ spec:
|
||||
## 좋은 예시 2: multi-region prod overlay 디렉터리 (kr-main + kr-dr)
|
||||
|
||||
```text
|
||||
k8s/
|
||||
gitops/
|
||||
apps/
|
||||
identity-auth/
|
||||
base/
|
||||
app/
|
||||
units/
|
||||
identity/
|
||||
auth/
|
||||
kustomization.yaml
|
||||
deployment.yaml
|
||||
service.yaml
|
||||
servicemonitor.yaml
|
||||
pdb.yaml
|
||||
hpa.yaml
|
||||
plugins/
|
||||
ingress-nginx/
|
||||
cert-manager/
|
||||
external-secrets/
|
||||
managing/
|
||||
flyway-migrate-identity/
|
||||
overlays/
|
||||
dev/
|
||||
kustomization.yaml
|
||||
staging/
|
||||
kustomization.yaml
|
||||
prod/
|
||||
kr-main/
|
||||
kustomization.yaml
|
||||
patches/
|
||||
auth-replicas.yaml
|
||||
auth-resources.yaml
|
||||
auth-topology-spread.yaml
|
||||
kr-dr/
|
||||
kustomization.yaml
|
||||
patches/
|
||||
auth-replicas.yaml
|
||||
auth-image-pull-mirror.yaml
|
||||
flyway-migrate-identity/
|
||||
base/
|
||||
overlays/
|
||||
platform/
|
||||
ingress-nginx/
|
||||
cert-manager/
|
||||
secret-delivery/
|
||||
clusters/
|
||||
dev/main/
|
||||
staging/main/
|
||||
prod/kr-main/
|
||||
prod/kr-dr/
|
||||
```
|
||||
|
||||
**왜 좋은가:**
|
||||
@@ -375,7 +372,7 @@ spec:
|
||||
image: registry.example.com/auth:1.24.3
|
||||
```
|
||||
|
||||
**문제:** `selector.matchLabels`는 Deployment/StatefulSet에서 **immutable**이다. `version`은 배포마다 바뀌고 `environment`는 overlay가 주입한다 → 첫 배포 이후 재apply 시 `field is immutable` 에러로 영구 차단. selector에는 불변 3종(`name`/`instance`/`component`)만.
|
||||
**문제:** `selector.matchLabels`는 Deployment/StatefulSet에서 **immutable**이다. `version`은 배포마다 바뀌고 `environment`는 overlay가 주입한다 → 첫 배포 이후 재apply 시 `field is immutable` 에러로 영구 차단. selector에는 불변 2종(`name`/`instance`)만.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -600,7 +600,7 @@ resources:
|
||||
- aescbc:
|
||||
keys:
|
||||
- name: fallback-2026-q1
|
||||
secret: c2VjcmV0LTMyLWJ5dGUtZmFsbGJhY2sta2V5LTIwMjZxMS1leGFtcGxl
|
||||
secret: <kms-fallback-key-base64>
|
||||
- identity: {}
|
||||
```
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# db / migration 예시
|
||||
|
||||
모든 YAML은 `kubectl apply` 가능하다. 상세 Flyway Job 예시는 `examples/infra/flyway.md` 참조.
|
||||
모든 YAML은 `kubectl apply` 가능하다. 상세 Flyway Job 예시는
|
||||
`docs/examples/infra/flyway.md` 참조.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -105,7 +105,7 @@ metadata:
|
||||
app.kubernetes.io/managed-by: keycloak-operator
|
||||
spec:
|
||||
instances: 3
|
||||
image: registry.example.com/platform/keycloak:26.0.7-optimized
|
||||
image: registry.example.com/platform/keycloak:26.0.7-optimized # gitleaks:allow
|
||||
startOptimized: true
|
||||
db:
|
||||
vendor: postgres
|
||||
|
||||
@@ -8,12 +8,13 @@ kubectl kustomize <dir> | kubectl apply --server-side --field-manager=ci --dry-r
|
||||
|
||||
---
|
||||
|
||||
## 좋은 예시 1: base / components / overlays 전체 구조 + 실제 base `kustomization.yaml`
|
||||
## 좋은 예시 1: catalog unit의 base / components / overlays 구조
|
||||
|
||||
```text
|
||||
k8s/
|
||||
gitops/
|
||||
apps/
|
||||
identity-auth/
|
||||
base/
|
||||
app/units/identity/auth/
|
||||
kustomization.yaml
|
||||
deployment.yaml
|
||||
service.yaml
|
||||
@@ -33,10 +34,13 @@ k8s/
|
||||
patches/
|
||||
auth-resources.yaml
|
||||
auth-ingress-host.yaml
|
||||
clusters/
|
||||
prod/kr-main/
|
||||
kustomization.yaml
|
||||
```
|
||||
|
||||
```yaml
|
||||
# k8s/base/app/units/identity/auth/kustomization.yaml
|
||||
# gitops/apps/identity-auth/base/kustomization.yaml
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
@@ -66,7 +70,7 @@ labels:
|
||||
## 좋은 예시 2: base Deployment (완전 apply-ready)
|
||||
|
||||
```yaml
|
||||
# k8s/base/app/units/identity/auth/deployment.yaml
|
||||
# gitops/apps/identity-auth/base/deployment.yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
@@ -206,12 +210,12 @@ spec:
|
||||
## 좋은 예시 3: overlay prod/kr-main — 환경 차이만
|
||||
|
||||
```yaml
|
||||
# k8s/overlays/prod/kr-main/kustomization.yaml
|
||||
# gitops/apps/identity-auth/overlays/prod/kr-main/kustomization.yaml
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
namespace: prod-identity-auth
|
||||
resources:
|
||||
- ../../../base/app/units/identity/auth
|
||||
- ../../../base
|
||||
components:
|
||||
- ../../../components/with-topology-spread-zone
|
||||
- ../../../components/with-pdb-tier1
|
||||
@@ -246,7 +250,7 @@ patches:
|
||||
```
|
||||
|
||||
```yaml
|
||||
# k8s/overlays/prod/kr-main/patches/auth-resources.yaml
|
||||
# gitops/apps/identity-auth/overlays/prod/kr-main/patches/auth-resources.yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
@@ -302,7 +306,7 @@ spec:
|
||||
## 좋은 예시 4: Kustomize Component — `with-pdb-tier1`
|
||||
|
||||
```yaml
|
||||
# k8s/components/with-pdb-tier1/kustomization.yaml
|
||||
# gitops/apps/identity-auth/components/with-pdb-tier1/kustomization.yaml
|
||||
apiVersion: kustomize.config.k8s.io/v1alpha1
|
||||
kind: Component
|
||||
resources:
|
||||
@@ -310,7 +314,7 @@ resources:
|
||||
```
|
||||
|
||||
```yaml
|
||||
# k8s/components/with-pdb-tier1/pdb.yaml
|
||||
# gitops/apps/identity-auth/components/with-pdb-tier1/pdb.yaml
|
||||
apiVersion: policy/v1
|
||||
kind: PodDisruptionBudget
|
||||
metadata:
|
||||
@@ -343,7 +347,7 @@ spec:
|
||||
## 좋은 예시 5: ConfigMap generator + hash suffix를 활용한 자동 rollout
|
||||
|
||||
```yaml
|
||||
# k8s/base/app/units/identity/auth/kustomization.yaml (with generator)
|
||||
# gitops/apps/identity-auth/base/kustomization.yaml (with generator)
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
@@ -373,7 +377,7 @@ generatorOptions:
|
||||
## 좋은 예시 6: HPA v2 + behavior (base 리소스)
|
||||
|
||||
```yaml
|
||||
# k8s/base/app/units/identity/auth/hpa.yaml
|
||||
# gitops/apps/identity-auth/base/hpa.yaml
|
||||
apiVersion: autoscaling/v2
|
||||
kind: HorizontalPodAutoscaler
|
||||
metadata:
|
||||
@@ -433,12 +437,12 @@ spec:
|
||||
## 나쁜 예시 1: `commonLabels`로 environment 주입 → selector immutable 에러
|
||||
|
||||
```yaml
|
||||
# k8s/overlays/prod/kustomization.yaml (BAD)
|
||||
# gitops/apps/identity-auth/overlays/prod/kustomization.yaml (BAD)
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
namespace: prod-identity-auth
|
||||
resources:
|
||||
- ../../base/app/units/identity/auth
|
||||
- ../../base
|
||||
commonLabels:
|
||||
example.com/environment: prod
|
||||
```
|
||||
@@ -450,10 +454,10 @@ commonLabels:
|
||||
## 나쁜 예시 2: overlay가 base를 거의 재작성
|
||||
|
||||
```text
|
||||
k8s/base/app/units/identity/auth/deployment.yaml (150 lines)
|
||||
k8s/overlays/prod/deployment.yaml (140 lines, 95% identical)
|
||||
k8s/overlays/staging/deployment.yaml (140 lines)
|
||||
k8s/overlays/dev/deployment.yaml (135 lines)
|
||||
gitops/apps/identity-auth/base/deployment.yaml (150 lines)
|
||||
gitops/apps/identity-auth/overlays/prod/deployment.yaml (140 lines, 95% identical)
|
||||
gitops/apps/identity-auth/overlays/staging/deployment.yaml (140 lines)
|
||||
gitops/apps/identity-auth/overlays/dev/deployment.yaml (135 lines)
|
||||
```
|
||||
|
||||
**문제:** overlay가 base의 95%를 복붙 + 몇 줄 수정. drift 발생 시점부터 base가 의미 없어진다. 해결: overlay는 `patches:` + `images:` + `replicas:` + `labels:`만 쓰고 전체 리소스는 base에서 가져온다.
|
||||
@@ -463,7 +467,7 @@ k8s/overlays/dev/deployment.yaml (135 lines)
|
||||
## 나쁜 예시 3: 운영 secret을 `secretGenerator`로 plaintext Git 커밋
|
||||
|
||||
```yaml
|
||||
# k8s/overlays/prod/kustomization.yaml (BAD)
|
||||
# gitops/apps/identity-auth/overlays/prod/kustomization.yaml (BAD)
|
||||
secretGenerator:
|
||||
- name: auth-secrets
|
||||
literals:
|
||||
@@ -478,7 +482,7 @@ secretGenerator:
|
||||
## 나쁜 예시 4: `patchesStrategicMerge` / `patchesJson6902` (deprecated)
|
||||
|
||||
```yaml
|
||||
# k8s/overlays/prod/kustomization.yaml (BAD, v5 deprecated)
|
||||
# gitops/apps/identity-auth/overlays/prod/kustomization.yaml (BAD, v5 deprecated)
|
||||
patchesStrategicMerge:
|
||||
- patches/auth-resources.yaml
|
||||
patchesJson6902:
|
||||
@@ -497,7 +501,7 @@ patchesJson6902:
|
||||
## 나쁜 예시 5: base에 환경 host / domain 고정
|
||||
|
||||
```yaml
|
||||
# k8s/base/app/units/identity/auth/ingress.yaml (BAD)
|
||||
# gitops/apps/identity-auth/base/ingress.yaml (BAD)
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
@@ -523,11 +527,11 @@ spec:
|
||||
## 나쁜 예시 6: `bases:` 사용 (v2.1에서 `resources:`로 통합됨)
|
||||
|
||||
```yaml
|
||||
# k8s/overlays/prod/kustomization.yaml (BAD)
|
||||
# gitops/apps/identity-auth/overlays/prod/kustomization.yaml (BAD)
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
bases:
|
||||
- ../../base/app/units/identity/auth
|
||||
- ../../base
|
||||
```
|
||||
|
||||
**문제:** `bases:`는 v2.1에서 `resources:`에 흡수됨. 신규 코드에서 사용 금지. 해결: `resources:` 사용.
|
||||
@@ -537,7 +541,7 @@ bases:
|
||||
## 나쁜 예시 7: HPA가 있는 Deployment에 overlay `replicas:`로 고정값 주입
|
||||
|
||||
```yaml
|
||||
# k8s/overlays/prod/kustomization.yaml (BAD — conflicts with HPA)
|
||||
# gitops/apps/identity-auth/overlays/prod/kustomization.yaml (BAD — conflicts with HPA)
|
||||
replicas:
|
||||
- name: auth
|
||||
count: 3
|
||||
|
||||
@@ -581,7 +581,7 @@ metadata:
|
||||
namespace: minio-prod
|
||||
type: Opaque
|
||||
stringData:
|
||||
token: "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9..."
|
||||
token: "<minio-prometheus-token>"
|
||||
---
|
||||
apiVersion: monitoring.coreos.com/v1
|
||||
kind: ServiceMonitor
|
||||
@@ -636,42 +636,31 @@ data:
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
mc alias set minio https://minio.minio-prod.svc.cluster.local "$MINIO_ROOT_USER" "$MINIO_ROOT_PASSWORD" --api S3v4
|
||||
mc --config-dir /tmp/mc alias import minio <<EOF
|
||||
{
|
||||
"url": "https://minio.minio-prod.svc.cluster.local",
|
||||
"accessKey": "$MINIO_ROOT_USER",
|
||||
"secretKey": "$MINIO_ROOT_PASSWORD",
|
||||
"api": "s3v4",
|
||||
"path": "auto"
|
||||
}
|
||||
EOF
|
||||
|
||||
# Object Lock은 bucket 생성 시점에만 활성화 가능
|
||||
mc mb --with-lock minio/critical-audit || true
|
||||
mc retention set --default COMPLIANCE 2555d minio/critical-audit # 7년 보관
|
||||
mc --config-dir /tmp/mc mb --with-lock minio/critical-audit || true
|
||||
mc --config-dir /tmp/mc retention set --default COMPLIANCE 2555d minio/critical-audit
|
||||
|
||||
# Versioning + lifecycle
|
||||
mc mb minio/app-data || true
|
||||
mc version enable minio/app-data
|
||||
mc ilm add --expire-noncurrent-days 90 minio/app-data
|
||||
mc ilm add --expire-incomplete-upload-days 7 minio/app-data
|
||||
mc --config-dir /tmp/mc mb minio/app-data || true
|
||||
mc --config-dir /tmp/mc version enable minio/app-data
|
||||
mc --config-dir /tmp/mc ilm add --expire-noncurrent-days 90 minio/app-data
|
||||
mc --config-dir /tmp/mc ilm add --expire-incomplete-upload-days 7 minio/app-data
|
||||
|
||||
# SSE-KMS 기본 적용
|
||||
mc encrypt set sse-kms minio-app-key minio/app-data
|
||||
mc encrypt set sse-kms minio-critical-key minio/critical-audit
|
||||
|
||||
# Service account 발급 (앱 전용, 최소 권한 policy)
|
||||
mc admin policy create minio auth-server-rw /policies/auth-server-rw.json
|
||||
mc admin user svcacct add minio "$MINIO_ROOT_USER" \
|
||||
--access-key "$AUTH_SERVER_ACCESS_KEY" \
|
||||
--secret-key "$AUTH_SERVER_SECRET_KEY" \
|
||||
--policy /policies/auth-server-rw.json || true
|
||||
mc --config-dir /tmp/mc encrypt set sse-kms minio-app-key minio/app-data
|
||||
mc --config-dir /tmp/mc encrypt set sse-kms minio-critical-key minio/critical-audit
|
||||
|
||||
echo "bootstrap complete"
|
||||
|
||||
auth-server-rw.json: |
|
||||
{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": ["s3:PutObject", "s3:GetObject", "s3:DeleteObject", "s3:ListBucket"],
|
||||
"Resource": ["arn:aws:s3:::app-data/*", "arn:aws:s3:::app-data"]
|
||||
}
|
||||
]
|
||||
}
|
||||
---
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
@@ -703,21 +692,11 @@ spec:
|
||||
secretKeyRef:
|
||||
name: minio-root-creds
|
||||
key: password
|
||||
- name: AUTH_SERVER_ACCESS_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: auth-server-minio-svcacct
|
||||
key: access_key
|
||||
- name: AUTH_SERVER_SECRET_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: auth-server-minio-svcacct
|
||||
key: secret_key
|
||||
volumeMounts:
|
||||
- name: scripts
|
||||
mountPath: /scripts
|
||||
- name: policies
|
||||
mountPath: /policies
|
||||
- name: tmp
|
||||
mountPath: /tmp
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 1000
|
||||
@@ -734,12 +713,10 @@ spec:
|
||||
items:
|
||||
- key: init.sh
|
||||
path: init.sh
|
||||
- name: policies
|
||||
configMap:
|
||||
name: minio-bootstrap
|
||||
items:
|
||||
- key: auth-server-rw.json
|
||||
path: auth-server-rw.json
|
||||
- name: tmp
|
||||
emptyDir:
|
||||
medium: Memory
|
||||
sizeLimit: 64Mi
|
||||
```
|
||||
|
||||
**왜 좋은가:**
|
||||
@@ -747,7 +724,8 @@ spec:
|
||||
- `mc mb --with-lock`은 bucket 생성 시점에만 Object Lock 활성화 가능 — Job이 그 타이밍을 보장
|
||||
- COMPLIANCE 모드 7년 retention = 감사/규제 요구 충족 (root도 bypass 불가)
|
||||
- `app-data` bucket은 versioning + lifecycle (90일 noncurrent expire + 7일 incomplete abort)
|
||||
- service account는 특정 bucket prefix만 접근 가능한 policy로 제한
|
||||
- 애플리케이션 access key 발급은 별도 bootstrap에서 inline 최소 권한 policy와
|
||||
함께 수행하고, 생성 secret은 Vault로 직접 전달
|
||||
- `backoffLimit: 3` + idempotent 명령 (`|| true`) → 재실행 안전
|
||||
|
||||
---
|
||||
|
||||
@@ -6,13 +6,13 @@
|
||||
|
||||
```bash
|
||||
# 1) render
|
||||
kubectl kustomize k8s/overlays/prod > /tmp/render.yaml
|
||||
kubectl kustomize gitops/clusters/prod/main/stages/50-apps > /tmp/render.yaml
|
||||
|
||||
# 2) diff
|
||||
kubectl diff -k k8s/overlays/prod
|
||||
kubectl diff -k gitops/clusters/prod/main/stages/50-apps
|
||||
|
||||
# 3) apply
|
||||
kubectl apply -k k8s/overlays/prod
|
||||
kubectl apply -k gitops/clusters/prod/main/stages/50-apps
|
||||
|
||||
# 4) rollout status with timeout
|
||||
kubectl rollout status deployment/auth-server -n auth-prod --timeout=10m
|
||||
@@ -607,10 +607,10 @@ kubectl get pods -A -o wide --field-selector spec.nodeName="${NODE}"
|
||||
git checkout v1.24.0
|
||||
|
||||
# 2) diff
|
||||
kubectl diff -k k8s/overlays/prod
|
||||
kubectl diff -k gitops/clusters/prod/main/stages/50-apps
|
||||
|
||||
# 3) apply
|
||||
kubectl apply -k k8s/overlays/prod
|
||||
kubectl apply -k gitops/clusters/prod/main/stages/50-apps
|
||||
|
||||
# 4) rollout status
|
||||
kubectl rollout status deployment/auth-server -n auth-prod --timeout=10m
|
||||
@@ -627,7 +627,7 @@ kubectl rollout status deployment/auth-server -n auth-prod --timeout=10m
|
||||
## 나쁜 예시 1: diff 없이 apply
|
||||
|
||||
```bash
|
||||
kubectl apply -k k8s/overlays/prod
|
||||
kubectl apply -k gitops/clusters/prod/main/stages/50-apps
|
||||
```
|
||||
|
||||
**문제:**
|
||||
|
||||
@@ -106,8 +106,8 @@ Environment:
|
||||
CONFIRM=yes non-interactive confirmation (alternative to --yes)
|
||||
|
||||
Examples:
|
||||
render-diff-apply --overlay k8s/overlays/prod --context prod-eu
|
||||
CONFIRM=yes render-diff-apply --overlay k8s/overlays/prod --context prod-eu --timeout 15m
|
||||
render-diff-apply --overlay gitops/clusters/prod/main/stages/50-apps --context prod-eu
|
||||
CONFIRM=yes render-diff-apply --overlay gitops/clusters/prod/main/stages/50-apps --context prod-eu --timeout 15m
|
||||
EOF
|
||||
}
|
||||
|
||||
|
||||
@@ -216,7 +216,7 @@ spec:
|
||||
- stateless 장기 실행 → Deployment 정답
|
||||
- PDB + HPA + topologySpread (zone+hostname) 모두 tier-1에 맞게 동반
|
||||
- digest pinning, restricted PodSecurity 호환, preStop sleep으로 graceful drain
|
||||
- selector에는 불변 3종만 (version/environment 없음)
|
||||
- selector에는 불변 2종만 (`name`/`instance`; version/environment 없음)
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
# Getting Started
|
||||
|
||||
## 현재 구현과 템플릿 확인하기
|
||||
|
||||
이 저장소에는 실제 `lab` 구성이 이미 조립되어 있습니다.
|
||||
|
||||
1. `gitops/clusters/lab/main`에서 namespace와 ordered stage 조립 방식을
|
||||
확인합니다.
|
||||
2. 다음 명령으로 platform과 app이 감사용 cluster root에서 합쳐지는 결과를
|
||||
확인합니다. `all`은 render/schema/policy 감사 전용이며 직접 apply하지
|
||||
않습니다.
|
||||
|
||||
```bash
|
||||
kubectl kustomize gitops/clusters/lab/main/all >/dev/null
|
||||
```
|
||||
|
||||
3. 다중 계정·리전·클러스터가 필요하면 account/project, region, cluster 경계를
|
||||
ADR로 결정한 뒤 `infrastructure/live/<env>/<leaf>`와
|
||||
`gitops/clusters/<env>/<cluster>`로 확장합니다.
|
||||
4. 새 파일은 현재 구현을 참고하되 각 책임 폴더의 `_template`에서 생성합니다.
|
||||
|
||||
```text
|
||||
현재 구현 확인 ──▶ 경계 결정 ──▶ _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 gitops/clusters/lab/main/namespaces
|
||||
kubectl kustomize gitops/clusters/lab/main/all >/dev/null
|
||||
```
|
||||
|
||||
프로젝트에서 실제 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 기준
|
||||
|
||||
운영 준비가 끝나기 전에는 템플릿 placeholder 값을 production에 재사용하지
|
||||
않습니다.
|
||||
+42
-227
@@ -1,248 +1,63 @@
|
||||
# Ingress / Traefik 운영
|
||||
# Lab ingress and Traefik
|
||||
|
||||
dev 환경은 K3s packaged Traefik 을 그대로 유지한다. 단 **`/var/lib/rancher/k3s/server/manifests/traefik.yaml` 는 수정하지 않는다.** 운영 설정은 `k8s/overlays/dev/platform/traefik/` 의 `HelmChartConfig` 로만 오버라이드한다.
|
||||
K3s packaged Traefik은 유지하며
|
||||
`gitops/platform/traefik/overlays/lab/helmchartconfig.yaml`의
|
||||
`HelmChartConfig`로만 설정합니다.
|
||||
|
||||
## 현재 구성
|
||||
금지:
|
||||
|
||||
| 위치 | 역할 |
|
||||
|---|---|
|
||||
| `k8s/overlays/dev/platform/traefik/helmchartconfig.yaml` | Traefik replica, 기본 ingressClass, HTTP→HTTPS redirect, metrics, 기본 TLS option 연결 |
|
||||
| `k8s/overlays/dev/platform/traefik/middleware.yaml` | 공용 `security-headers` Middleware + `modern-tls` TLSOption |
|
||||
| `k8s/overlays/dev/auth/ingress.yaml` | `project.com` → `auth-server` |
|
||||
| `k8s/overlays/dev/keycloak/ingress-public.yaml` | `keycloak.dev.example.com` → Keycloak 공개 path (`/realms/`, `/resources/`, `/.well-known/`, `/js/`) |
|
||||
| `k8s/overlays/dev/platform/cert-manager/` | cert-manager `v1.20.2` CRD/controller 설치 overlay |
|
||||
| `k8s/overlays/dev/platform/cert-manager-issuers/` | `letsencrypt-staging` / `letsencrypt-prod` ClusterIssuer |
|
||||
| `k8s/overlays/dev/platform/keycloak-operator/` | Keycloak Operator `26.6.1`. dev 제약상 `mnt` 에 설치해 `mnt` 의 Keycloak CR 을 watch. K8s API egress NetworkPolicy 포함 |
|
||||
| `k8s/overlays/dev/tls/*.yaml` | cert-manager 설치 후 발급할 `Certificate` 리소스 |
|
||||
| `k8s/components/forward-auth/` | oauth2-proxy + Traefik ForwardAuth 재사용 component |
|
||||
| `k8s/overlays/dev/` | 기본 dev overlay. 현재 forward-auth component 를 직접 포함 |
|
||||
| `k8s/overlays/dev/keycloak-realm/` | `KeycloakRealmImport` 로 realm/client 를 Git 관리 |
|
||||
- `/var/lib/rancher/k3s/server/manifests/traefik.yaml` 직접 수정
|
||||
- health/metrics/admin endpoint 공개
|
||||
- 인증 없는 management ingress
|
||||
- lab HTTP/self-signed 구성을 staging/prod로 승격
|
||||
|
||||
## 설계 원칙
|
||||
|
||||
- 앱은 `Ingress` 만 선언하고, 공통 보안 정책은 Traefik Middleware / TLSOption 으로 재사용
|
||||
- Keycloak 은 외부 전체 공개가 아니라 **최소 공개 path** 만 연다. `/admin`, `/metrics`, `/health` 는 비공개
|
||||
- TLS 리소스는 cert-manager + ClusterIssuer 적용 후 `tls/` overlay 에서 발급
|
||||
- north-south ingress 는 `kube-system` 의 Traefik Pod 에서만 시작 → app NetworkPolicy 도 그에 맞춰 작성
|
||||
|
||||
## ForwardAuth variant
|
||||
|
||||
`k8s/components/forward-auth/` 는 oauth2-proxy + ForwardAuth Middleware 를 담은 Kustomize component 다. 현재 `k8s/overlays/dev/` 가 이 component 를 직접 포함한다.
|
||||
|
||||
구성:
|
||||
|
||||
- `oauth2-proxy` Deployment / Service / ConfigMap / VaultStaticSecret
|
||||
- `project.com/oauth2/*` 경로용 Ingress
|
||||
- `oauth2-proxy-auth` Traefik Middleware
|
||||
- `auth-server` Ingress patch — `project.com/` 요청은 oauth2-proxy 를 거친 인증된 사용자만 통과
|
||||
|
||||
흐름: `Traefik ForwardAuth → oauth2-proxy → Keycloak`.
|
||||
|
||||
### 적용 전제
|
||||
|
||||
- `k8s/overlays/dev/keycloak-realm/` 또는 동등한 방법으로 `platform` realm + `auth-server-ingress` client 가 준비됨
|
||||
- redirect URI: `https://project.com/oauth2/callback`
|
||||
- Vault path `secret/oauth2-proxy/forward-auth` 에 `client_secret`, `cookie_secret` 저장
|
||||
- `project.com`, `keycloak.dev.example.com` 이 실제 Traefik 진입점으로 해석됨
|
||||
|
||||
### 브라우저 접속 전제
|
||||
|
||||
curl 검증은 `--resolve project.com:443:<ingress-ip>` 와 `-k` 로 DNS/TLS 문제를 우회할 수 있다. 브라우저는 이 옵션이 없으므로 dev 환경에서 직접 접속하려면 운영자가 아래를 별도로 맞춰야 한다.
|
||||
## Request path
|
||||
|
||||
```text
|
||||
<ingress-ip> project.com
|
||||
<ingress-ip> keycloak.dev.example.com
|
||||
client
|
||||
-> Traefik Ingress
|
||||
-> ForwardAuth Middleware
|
||||
-> oauth2-proxy
|
||||
-> Keycloak authorization endpoint
|
||||
-> oauth2-proxy callback/session
|
||||
-> auth-server
|
||||
```
|
||||
|
||||
예: Traefik `LoadBalancer` IP 중 하나가 `10.208.141.123` 이면 로컬 `/etc/hosts` 에 두 host 를 추가한다. dev overlay 는 현재 외부 ACME 발급 대신 `dev-selfsigned` ClusterIssuer 를 사용하므로 브라우저에서는 인증서 경고를 허용하거나 해당 인증서를 로컬 trust store 에 등록해야 한다. 공인 DNS 가 Traefik 진입점으로 향하고 ACME 인증서가 Ready 가 되면 이 임시 조치는 제거한다.
|
||||
auth-server는 ForwardAuth 결과만 신뢰하지 않고 Keycloak JWT를 다시 검증합니다.
|
||||
Ingress 인증과 API authorization은 서로 다른 경계입니다.
|
||||
|
||||
Chrome 에서 계속 실패하면 먼저 boundary 를 나눈다.
|
||||
## Source paths
|
||||
|
||||
| Boundary | 확인 |
|
||||
| Path | Role |
|
||||
| --- | --- |
|
||||
| 로컬 DNS | `getent hosts project.com keycloak.dev.example.com` 이 Traefik IP 를 반환해야 한다. |
|
||||
| 브라우저 DNS cache | `/etc/hosts` 수정 후 Chrome 재시작 또는 `chrome://net-internals/#dns` 에서 cache clear. |
|
||||
| TLS trust | `ERR_CERT_*` 가 나오면 dev self-signed 인증서를 허용하거나 trust store 에 등록한다. |
|
||||
| 인증 redirect | `curl -k -D - --resolve project.com:443:<ingress-ip> https://project.com/swagger-ui.html` 가 `302 Location: https://keycloak...` 를 반환해야 한다. |
|
||||
| 로그인 후 app route | 인증 후 `404 PRES-005` 는 ForwardAuth 실패가 아니라 auth-server 에 해당 route 가 없다는 뜻이다. |
|
||||
| `gitops/platform/traefik/overlays/lab` | K3s HelmChartConfig와 공통 security headers |
|
||||
| `gitops/platform/forward-auth/component` | oauth2-proxy, Middleware, protected ingress patch |
|
||||
| `gitops/platform/forward-auth/overlays/lab` | component의 lab composition |
|
||||
| `gitops/apps/auth-server/overlays/lab` | auth-server ingress와 issuer/JWK setting |
|
||||
| `gitops/platform/keycloak/overlays/lab` | Keycloak public/admin ingress split |
|
||||
| `gitops/apps/keycloak-realm-import/overlays/lab` | versioned realm/client import |
|
||||
|
||||
### 브라우저 검증 순서
|
||||
## Apply boundary
|
||||
|
||||
dev ForwardAuth 를 브라우저에서 직접 확인할 때는 아래 순서로 진행한다. 중간 단계를 건너뛰면 "Chrome 이 안 된다" 만 보이고 어느 boundary 가 깨졌는지 알기 어렵다.
|
||||
|
||||
#### 1. Traefik 진입 IP 확인
|
||||
개별 ingress 디렉터리를 임의 순서로 적용하지 않습니다.
|
||||
|
||||
```bash
|
||||
kubectl -n kube-system get svc traefik \
|
||||
-o jsonpath='{.status.loadBalancer.ingress[*].ip}{"\n"}'
|
||||
export KUBE_CONTEXT_LAB='<expected-context>'
|
||||
bash scripts/bin/bootstrap.sh lab
|
||||
```
|
||||
|
||||
예상 예시:
|
||||
bootstrap은 operator와 Keycloak 준비, realm import 완료 후 auth-server를
|
||||
적용합니다.
|
||||
|
||||
```text
|
||||
10.208.141.123 10.208.141.14
|
||||
```
|
||||
## Lab DNS/TLS
|
||||
|
||||
이 문서의 예시는 `10.208.141.123` 을 사용한다. 실제 클러스터에서 나온 IP 중 하나를 선택한다.
|
||||
현재 host 값은 lab 검증용이며 공개 서비스 계약이 아닙니다. 브라우저 E2E를
|
||||
하려면 운영자가 DNS/hosts와 인증서 trust를 별도로 맞춰야 합니다.
|
||||
|
||||
#### 2. curl 로 클러스터 경로 먼저 확인
|
||||
staging/prod는 다음이 준비된 뒤 별도 overlay로 만듭니다.
|
||||
|
||||
브라우저를 열기 전에 curl 로 Traefik / oauth2-proxy / Keycloak boundary 가 살아있는지 확인한다.
|
||||
|
||||
```bash
|
||||
curl -k -sS -L \
|
||||
-D /tmp/project-infra-login.headers \
|
||||
-o /tmp/project-infra-login.body \
|
||||
--resolve project.com:443:10.208.141.123 \
|
||||
--resolve keycloak.dev.example.com:443:10.208.141.123 \
|
||||
https://project.com/swagger-ui.html
|
||||
```
|
||||
|
||||
정상 신호:
|
||||
|
||||
```bash
|
||||
sed -n '1,80p' /tmp/project-infra-login.headers
|
||||
grep -o '<title>[^<]*' /tmp/project-infra-login.body
|
||||
```
|
||||
|
||||
정상이라면 헤더에는 첫 응답 `HTTP/2 302` 와 `location: https://keycloak.dev.example.com/.../auth` 가 보이고, body title 은 아래처럼 나온다.
|
||||
|
||||
```text
|
||||
<title>Sign in to platform
|
||||
```
|
||||
|
||||
이 단계가 실패하면 브라우저를 볼 필요가 없다. 먼저 `docs/troubleshooting.md` 의 `ForwardAuth 로그인 E2E 검증 실패` 사건에서 해당 boundary 를 찾는다.
|
||||
|
||||
#### 3. 빠른 Chrome 임시 프로필로 확인
|
||||
|
||||
로컬 `/etc/hosts` 와 인증서 trust 를 건드리기 전에, Chrome 실행 옵션으로 DNS/TLS 를 임시 우회해 본다.
|
||||
|
||||
```bash
|
||||
google-chrome \
|
||||
--user-data-dir=/tmp/project-infra-chrome \
|
||||
--ignore-certificate-errors \
|
||||
--host-resolver-rules="MAP project.com 10.208.141.123, MAP keycloak.dev.example.com 10.208.141.123" \
|
||||
https://project.com/swagger-ui.html
|
||||
```
|
||||
|
||||
정상 흐름:
|
||||
|
||||
1. `https://project.com/swagger-ui.html` 접속
|
||||
2. Traefik ForwardAuth 가 미인증 요청을 감지
|
||||
3. `302` 로 Keycloak 로그인 화면 이동
|
||||
4. `Sign in to platform` 화면 표시
|
||||
5. 로그인 성공 후 `project.com` 으로 callback
|
||||
|
||||
이 방식으로 성공하면 Kubernetes / Traefik / oauth2-proxy / Keycloak 경로는 정상이다. 평소 Chrome 에서 안 되는 원인은 로컬 DNS cache, `/etc/hosts`, 인증서 trust, 기존 쿠키 중 하나다.
|
||||
|
||||
#### 4. 일반 Chrome 으로 볼 수 있게 hosts 등록
|
||||
|
||||
임시 Chrome 이 성공하면 로컬 OS resolver 를 맞춘다.
|
||||
|
||||
```bash
|
||||
sudo tee -a /etc/hosts >/dev/null <<'EOF'
|
||||
|
||||
# Project-Infra dev ingress
|
||||
10.208.141.123 project.com
|
||||
10.208.141.123 keycloak.dev.example.com
|
||||
EOF
|
||||
```
|
||||
|
||||
확인:
|
||||
|
||||
```bash
|
||||
getent hosts project.com keycloak.dev.example.com
|
||||
```
|
||||
|
||||
두 host 가 선택한 Traefik IP 를 반환해야 한다.
|
||||
|
||||
#### 5. Chrome DNS cache / 기존 세션 정리
|
||||
|
||||
hosts 를 바꾼 뒤에도 Chrome 이 이전 DNS / 쿠키를 들고 있을 수 있다.
|
||||
|
||||
권장 순서:
|
||||
|
||||
1. `chrome://net-internals/#dns` 에서 DNS cache clear
|
||||
2. `chrome://net-internals/#sockets` 에서 socket pools flush
|
||||
3. `project.com`, `keycloak.dev.example.com` 사이트 데이터 삭제
|
||||
4. Chrome 완전 종료 후 재시작
|
||||
|
||||
그래도 헷갈리면 아래처럼 새 임시 프로필을 쓰는 게 가장 빠르다.
|
||||
|
||||
```bash
|
||||
google-chrome --user-data-dir=/tmp/project-infra-normal https://project.com/swagger-ui.html
|
||||
```
|
||||
|
||||
#### 6. 인증서 경고 처리
|
||||
|
||||
dev overlay 는 현재 `dev-selfsigned` ClusterIssuer 로 TLS Secret 을 만든다. 따라서 일반 Chrome 에서는 인증서 경고가 뜰 수 있다.
|
||||
|
||||
검증 목적이면 고급 옵션에서 예외를 허용한다. 장기적으로 반복 검증할 예정이면 `project-com-tls`, `keycloak-dev-example-com-tls` 인증서를 로컬 trust store 에 등록한다.
|
||||
|
||||
이 경고는 dev self-signed 인증서 때문에 생기는 것으로, ForwardAuth 실패와는 다른 boundary 다.
|
||||
|
||||
#### 7. 로그인 후 결과 해석
|
||||
|
||||
로그인 후 `swagger-ui.html` 이 열리면 브라우저 검증은 성공이다.
|
||||
|
||||
로그인 후 `/api/me` 를 열어 `404 PRES-005` 가 나오면 이것도 ForwardAuth 실패가 아니다. 인증은 통과했고 auth-server 애플리케이션에 `/api/me` route 가 없다는 뜻이다.
|
||||
|
||||
판단 기준:
|
||||
|
||||
| 결과 | 의미 |
|
||||
|---|---|
|
||||
| Keycloak 로그인 화면이 뜸 | 미인증 redirect 정상 |
|
||||
| 로그인 후 `project.com` 으로 돌아옴 | callback / token exchange / session cookie 정상 |
|
||||
| `/oauth2/auth` 가 `202` | oauth2-proxy 세션 인증 정상 |
|
||||
| auth-server 가 `404 PRES-005` 반환 | 인증 통과 후 application route 없음 |
|
||||
| auth-server 가 `401` 반환 | Authorization header 또는 JWT validation boundary 문제 |
|
||||
|
||||
### 인증 실패 처리
|
||||
|
||||
Traefik ForwardAuth 는 `/oauth2/auth` 를 호출한다. oauth2-proxy 가 `401` 또는 `403` 을 반환하면 `oauth2-proxy-errors` Middleware 가 `/oauth2/start?rd={url}` 로 넘겨 로그인 흐름을 시작한다.
|
||||
|
||||
중요: Traefik errors middleware 는 기본적으로 원래 status code 를 유지할 수 있다. 그러면 oauth2-proxy 가 `Location` 을 내려도 브라우저는 `401` 응답을 자동 redirect 로 처리하지 않는다. dev 구성은 `statusRewrites` 로 `401`/`403` 을 `302` 로 바꿔 브라우저가 바로 Keycloak 로그인 화면으로 이동하게 한다.
|
||||
|
||||
## cert-manager / ClusterIssuer
|
||||
|
||||
repo 에 `k8s/overlays/dev/platform/cert-manager/` 와 `k8s/overlays/dev/platform/cert-manager-issuers/` 가 추가되어 있다.
|
||||
|
||||
- 설치 overlay: 공식 static install `v1.20.2`
|
||||
- issuer overlay: ACME HTTP-01 용 `letsencrypt-staging` / `letsencrypt-prod`
|
||||
|
||||
source-of-truth 관점에서 cert-manager 도 이 repo 의 선언형 관리 대상. 단, **실제 인증서 발급은 DNS 가 Traefik 외부 진입점을 가리키고 80/443 도달이 가능해야** 완료된다.
|
||||
|
||||
```bash
|
||||
kubectl apply -k k8s/overlays/dev/platform/cert-manager
|
||||
kubectl apply -k k8s/overlays/dev/platform/cert-manager-issuers
|
||||
kubectl apply -k k8s/overlays/dev/tls
|
||||
```
|
||||
|
||||
운영 보정 필요: `admin@project.com` 은 실제 운영 수신 가능한 메일로 교체.
|
||||
|
||||
## Keycloak realm / client Git 관리
|
||||
|
||||
`k8s/overlays/dev/keycloak-realm/` 는 `KeycloakRealmImport` 로 `platform` realm 과 `auth-server-ingress` client 를 선언한다. `k8s/overlays/dev/keycloak/` 도 수제 `Deployment` 가 아니라 `Keycloak` CR 기반으로 전환되어 있다.
|
||||
|
||||
`KeycloakRealmImport` 는 같은 `mnt` namespace 의 `Keycloak/keycloak` 을 대상으로 동작한다.
|
||||
|
||||
### 적용 순서
|
||||
|
||||
```bash
|
||||
kubectl apply -k k8s/overlays/dev/platform/keycloak-operator
|
||||
# 기존 수제 Deployment/Service/ConfigMap/ServiceAccount keycloak* 정리
|
||||
kubectl apply -k k8s/overlays/dev
|
||||
kubectl apply -k k8s/overlays/dev/keycloak-realm
|
||||
```
|
||||
|
||||
## 적용 범위
|
||||
|
||||
repo 가 커버하는 것:
|
||||
|
||||
- Traefik 운영 정책의 Git 관리
|
||||
- app ingress host / path / policy 정의
|
||||
- Traefik → app 방향 ingress allow NetworkPolicy
|
||||
- TLS `Certificate` 선언 준비
|
||||
- ForwardAuth variant 와 KeycloakRealmImport 선언
|
||||
|
||||
> 미완 항목(DNS / ACME 발급 / end-to-end 테스트)은 README 의 [Limitations](../README.md#limitations-honest-scope) 섹션을 참고.
|
||||
- 실제 DNS ownership
|
||||
- cert-manager Issuer/Certificate
|
||||
- TLS redirect와 HSTS
|
||||
- 신뢰 가능한 oauth2-proxy OIDC issuer
|
||||
- registry ingress의 인증·TLS
|
||||
|
||||
+24
-24
@@ -1,31 +1,31 @@
|
||||
# NetworkPolicy 매트릭스
|
||||
# Lab networking
|
||||
|
||||
단일 namespace(`mnt`) 내부에서도 서비스 간 트래픽을 **최소권한** 으로 제한한다. baseline 은 모든 Pod 의 ingress/egress 를 차단하고, 컴포넌트별로 필요한 경로만 명시적으로 연다.
|
||||
Namespace `mnt`는 `gitops/policies/baseline`에서 ingress와 egress를 모두 default-deny로
|
||||
시작하고 kube-dns UDP/TCP 53만 공통 허용합니다.
|
||||
|
||||
## 정책 목록
|
||||
|
||||
| 정책 파일 | 역할 |
|
||||
| Owner | Allowed traffic |
|
||||
| --- | --- |
|
||||
| `overlays/dev/networkpolicy-baseline.yaml` | `default-deny-all` (전 Pod ingress/egress 기본 차단) + `allow-dns-egress` (kube-system/kube-dns 53) |
|
||||
| `overlays/dev/database/networkpolicy.yaml` | identity-postgres ingress ← keycloak / auth-server / migration-flyway (5432) |
|
||||
| `overlays/dev/auth/networkpolicy.yaml` | auth-server ingress ← `kube-system/traefik`(8080); egress → postgres(5432) + keycloak(8080); flyway egress → postgres(5432) |
|
||||
| `overlays/dev/keycloak/networkpolicy.yaml` | keycloak ingress ← `kube-system/traefik`(8080) + auth-server(8080); egress → postgres(5432); Keycloak Pod 간 peer 통신 (Infinispan/JGroups) |
|
||||
| `overlays/dev/storage/networkpolicy.yaml` | minio ingress ← `part-of=auth-platform`(9000); 자체 peer(9000/9001) |
|
||||
| `overlays/dev/test/networkpolicy.yaml` | test-server 3 대 내부 상호 통신만 허용 |
|
||||
| `overlays/dev/vault/networkpolicy.yaml` | vault ingress ← VSO Operator Pod(8200) |
|
||||
| `overlays/dev/registry/networkpolicy.yaml` | docker-registry ingress ← namespace 내 전 Pod(5000) + `kube-system/traefik`(5000); egress → minio(9000) |
|
||||
| `gitops/apps/auth-server` | Traefik -> auth-server:8080; auth-server -> PostgreSQL:5432, Keycloak:8080 |
|
||||
| `gitops/apps/auth-migration` | migration Job -> PostgreSQL:5432 |
|
||||
| `gitops/apps/identity-postgres` | Keycloak, auth-server, migration Job -> PostgreSQL:5432 |
|
||||
| `gitops/platform/minio` | MinIO peer; MinIO Operator; approved in-namespace clients |
|
||||
| `gitops/platform/keycloak` | Traefik/auth-server ingress; PostgreSQL egress; Keycloak peer |
|
||||
| `gitops/platform/registry` | Traefik/in-namespace ingress; MinIO egress |
|
||||
| `gitops/platform/vault` | VSO controller ingress; kube-apiserver TokenReview egress |
|
||||
| `gitops/platform/keycloak-operator` | kube-apiserver egress |
|
||||
|
||||
## 작성 규칙
|
||||
NetworkPolicy CIDR `10.43.0.0/16`과 `10.208.141.0/24`는 lab K3s의 service
|
||||
CIDR/control-plane subnet 계약입니다. 클러스터 설정이 다르면 overlay에서
|
||||
명시적으로 변경해야 합니다. staging/prod로 복사하지 않습니다.
|
||||
|
||||
- cross-namespace 참조가 필요한 항목(예: `kube-system/traefik`)은 `namespaceSelector` + `podSelector` 를 한 블록에 조합해 **AND 시맨틱** 으로 작성한다. 두 selector 를 별도 블록에 두면 OR 가 되어 정책이 헐거워진다.
|
||||
- north-south ingress 는 `kube-system` 의 Traefik Pod 에서만 시작되므로, app 측 NetworkPolicy 도 실제 클러스터 기준으로 `kube-system` 을 허용해야 한다 (`ingressClassName=traefik` 만으로는 부족).
|
||||
- baseline default-deny 가 켜져 있는 한, 새 워크로드를 올릴 때마다 ingress / egress 를 **명시적으로** 추가해야 한다. 이게 의도된 마찰이다 (실수로 wide-open 으로 시작하지 않도록).
|
||||
정책 추가 순서:
|
||||
|
||||
## Keycloak Operator 추가 고려사항
|
||||
1. source/destination workload label과 namespace를 확인합니다.
|
||||
2. 실제 named port와 protocol을 확인합니다.
|
||||
3. 가장 좁은 pod/namespace selector로 ingress와 egress 양쪽을 작성합니다.
|
||||
4. 해당 stage를 렌더하고 정적 검증합니다.
|
||||
5. server-side dry-run과 diff 후 적용합니다.
|
||||
6. 허용 요청과 차단되어야 할 요청을 모두 확인합니다.
|
||||
|
||||
Keycloak Operator 가 Keycloak Pod 를 만들고 watch 하기 때문에 default-deny 환경에서는 다음 두 가지를 NetworkPolicy 로 명시한다:
|
||||
|
||||
- Keycloak Operator 의 Kubernetes API egress (CR reconcile)
|
||||
- Keycloak Pod 간 Infinispan/JGroups peer 통신 (cluster mode)
|
||||
|
||||
해당 정책은 `overlays/dev/keycloak/networkpolicy.yaml` 에 함께 들어 있다.
|
||||
`ipBlock`은 Kubernetes API처럼 selector로 표현할 수 없는 lab 경계에서만
|
||||
사용합니다. 넓은 RFC1918 CIDR 전체 허용을 기본값으로 만들지 않습니다.
|
||||
|
||||
+99
-62
@@ -1,83 +1,120 @@
|
||||
# 운영 / 검증
|
||||
# Operations and validation
|
||||
|
||||
bootstrap, teardown, validate.sh, 환경별 차등 계획의 **설계 의도** 를 정리한 문서. 단계별 실제 실행 절차는 [guide.md](../guide.md) 에 있다.
|
||||
|
||||
## bootstrap 단계
|
||||
|
||||
`VaultConnection` / `VaultAuth` / `VaultStaticSecret` 은 VSO Helm 설치로 CRD 가 등록된 뒤에만 apply 할 수 있다. 그래서 `overlays/dev/vso/` 는 dev kustomization 집계에 포함되지 않으며, `bin/bootstrap.sh` 마지막 단계에서 별도로 `kubectl apply -k overlays/dev/vso/` 한다.
|
||||
|
||||
| Phase | 작업 | 의존하는 직전 상태 | 멱등 안전? |
|
||||
|:---:|---|---|---|
|
||||
| 0 | MinIO Operator Helm install (`tasks/minio-operator-install.sh`) | helm 가능한 클러스터 | ✅ `helm upgrade --install` |
|
||||
| 1 | `kubectl apply -k base/managing/namespace/` (PSS restricted 라벨 선행) | — | ✅ `kubectl apply` |
|
||||
| 2 | VSO-managed Secret 점검 | namespace 존재 | ⚠️ `RESET_STALE_SECRETS=yes` 옵션 시 파괴적 |
|
||||
| 3 | `kubectl apply -k overlays/dev/` (vault + registry + 앱) | namespace + PSS 라벨 | ✅ `kubectl apply` |
|
||||
| 4 | `vault-0` Pod Running 대기 | Phase 3 의 Vault StatefulSet | ✅ wait 만 |
|
||||
| 5 | `tasks/vault-init.sh` (init / unseal / auth / policy×2 / role×2) | `vault-0` Running | ✅ 상태 체크 후 차이만 적용 |
|
||||
| 6 | `tasks/vso-install.sh` (helm upgrade --install) | Vault auth/role 준비 | ✅ `helm upgrade --install` |
|
||||
| 7 | `kubectl apply -k overlays/dev/vso/` (VaultConnection / VaultAuth / VaultStaticSecret) | Phase 6 의 VSO CRD 등록 | ✅ `kubectl apply` |
|
||||
|
||||
Phase 5 의 1 회성 셋업 흐름은 [secret-pipeline-bootstrap 시퀀스](diagrams/sequence/secret-pipeline-bootstrap.md), Phase 7 이후의 정상 reconcile 은 [secret-pipeline-runtime 시퀀스](diagrams/sequence/secret-pipeline-runtime.md) 참고.
|
||||
## Bootstrap
|
||||
|
||||
```bash
|
||||
# dev — 비밀번호를 프롬프트에서 무음 입력 (bash history 에 안 남음)
|
||||
bash k8s/scripts/bin/bootstrap.sh dev
|
||||
|
||||
# teardown — 대화형 y/N
|
||||
bash k8s/scripts/bin/teardown.sh dev
|
||||
export KUBE_CONTEXT_LAB='<expected-context>'
|
||||
bash scripts/bin/bootstrap.sh lab
|
||||
```
|
||||
|
||||
## 스크립트 구조
|
||||
각 stage는 `render -> server-side dry-run -> diff -> confirm -> apply` 순서로
|
||||
실행됩니다. `CONFIRM=yes`는 비대화 환경에서만 사용하고 context 확인을
|
||||
우회하지 않습니다.
|
||||
|
||||
`k8s/scripts/` 는 `bin / ci / lib / tasks` 4 축:
|
||||
| Order | Entrypoint / task | Completion boundary |
|
||||
| ---: | --- | --- |
|
||||
| 1 | `namespaces` | Namespace Active |
|
||||
| 2 | `00-platform` | cert-manager와 Keycloak Operator Available |
|
||||
| 3 | MinIO/VSO Helm task | controller Ready, CRD registered |
|
||||
| 4 | `10-vault` | `vault-0` Running |
|
||||
| 5 | Vault init/unseal/policy/seed | KV와 Kubernetes auth 준비 |
|
||||
| 6 | `20-secrets` | data 선행 destination Secret 생성 |
|
||||
| 7 | `30-data` | PostgreSQL/MinIO/Keycloak Ready |
|
||||
| 8 | MinIO registry provision task | bucket/access key 생성 후 Vault 기록 |
|
||||
| 9 | `35-registry` | registry Secret 생성과 Deployment rollout 완료 |
|
||||
| 10 | `40-operations` | Flyway Complete, RealmImport Done |
|
||||
| 11 | `50-apps` | auth-server/oauth2-proxy rollout 완료 |
|
||||
|
||||
| 디렉토리 | 역할 |
|
||||
|---|---|
|
||||
| `bin/` | 사용자 진입점. `bootstrap.sh` / `teardown.sh` |
|
||||
| `ci/` | CI / 로컬 검증. `validate.sh` (kustomize + kubeconform + kube-linter) |
|
||||
| `lib/` | 공통 Bash 라이브러리. `common.sh` (strict mode / trap / log / confirm / retry / mask_secret) + `vault.sh` |
|
||||
| `tasks/` | 재사용 작업. `vault-init.sh` / `vault-seed-apps.sh` / `vso-install.sh` |
|
||||
`gitops/clusters/lab/main/all`은 절대 apply하지 않습니다.
|
||||
|
||||
모든 쉘 스크립트는 `set -Eeuo pipefail` + `IFS=$'\n\t'` + `trap_cleanup` 으로 공통 에러 처리. root token / registry BasicAuth 같은 민감 값은 **stdin 파이프** 로만 전달하고 stdout 에 찍지 않는다.
|
||||
## Vault init material
|
||||
|
||||
## 검증 (validate.sh)
|
||||
기본 lab 경로는 repo root의 ignored `vault-init-keys.json`입니다. 스크립트는
|
||||
`0600`으로 쓰지만 암호화 파일은 아닙니다. `VAULT_KEYS_FILE`로 repo 밖의
|
||||
안전한 위치를 지정하는 방식을 권장하며 prod에서는 필수입니다.
|
||||
|
||||
키, root token, password, MinIO secret key를 argv로 전달하지 않습니다.
|
||||
unseal/login, JSON 조립, Vault 기록은 stdin 경로를 사용합니다.
|
||||
|
||||
`docker-registry/minio`는 일반 seed 대상이 아닙니다. `30-data`에서 MinIO가
|
||||
Ready가 된 뒤 MinIO가 bucket-scoped access key를 생성하고, bootstrap task가
|
||||
그 결과를 Vault에 기록합니다. `35-registry`는 그 이후에만 VSO CR과 registry
|
||||
Deployment를 적용하므로 missing Secret 상태의 Pod를 만들지 않습니다.
|
||||
|
||||
## Secret rotation
|
||||
|
||||
VSO destination은 `overwrite: true`로 선언되어 Vault 변경을 Kubernetes
|
||||
Secret에 반영합니다. auth-server와 oauth2-proxy는 지원되는 Secret 변경 시
|
||||
rollout target을 사용합니다.
|
||||
|
||||
다음 credential은 외부 시스템 상태와 함께 회전해야 하므로 Vault 값만 바꾸면
|
||||
안 됩니다.
|
||||
|
||||
- PostgreSQL role password
|
||||
- Keycloak DB password
|
||||
- MinIO access key credential
|
||||
- registry basic-auth/pull credential
|
||||
|
||||
각 소비자와 backend credential을 순서대로 갱신하고 stage health를 확인하는
|
||||
별도 rotation runbook이 필요합니다.
|
||||
|
||||
## Teardown
|
||||
|
||||
기본 teardown은 앱과 one-shot operation만 삭제합니다.
|
||||
|
||||
```bash
|
||||
bash k8s/scripts/ci/validate.sh
|
||||
bash scripts/bin/teardown.sh lab
|
||||
```
|
||||
|
||||
3 단계:
|
||||
데이터, Vault, namespace까지 삭제하려면 명시적으로 opt-in합니다.
|
||||
|
||||
1. 각 overlay 에 대해 `kustomize build` (환경 중립성 / patch 유효성)
|
||||
2. 렌더 결과에 `kubeconform -strict -ignore-missing-schemas` (Kubernetes OpenAPI + Datree CRD catalog)
|
||||
3. 렌더 결과에 `kube-linter lint --config .kube-linter.yaml` (securityContext / resources / PSS / image tag 등)
|
||||
|
||||
`.kube-linter.yaml` 은 **블록 단위 분석으로 생기는 컨텍스트 오탐 4 종**(`dangling-service`, `non-existent-service-account`, `mismatching-selector`, `no-anti-affinity`) 만 제외한다. 나머지는 모두 활성.
|
||||
|
||||
목표 상태:
|
||||
|
||||
```
|
||||
k8s/overlays/dev build=ok schema=ok lint=ok
|
||||
k8s/overlays/dev/vso build=ok schema=ok lint=ok
|
||||
```bash
|
||||
DELETE_DATA=yes bash scripts/bin/teardown.sh lab
|
||||
```
|
||||
|
||||
## 환경별 배포
|
||||
공유 operator와 cluster-scoped 리소스까지 삭제하는 것은 전용 lab cluster에서만
|
||||
사용합니다.
|
||||
|
||||
현재 `dev` overlay 만 완성. `staging` / `prod` 는 의도적으로 비어 있고 추후 확장 예정. validate.sh 는 `kustomization.yaml` 이 없는 환경을 자동 스킵한다 — 빈 overlay 가 CI 를 빨갛게 만들지 않기 위함.
|
||||
```bash
|
||||
DELETE_DATA=yes TEARDOWN_PLATFORM=yes \
|
||||
bash scripts/bin/teardown.sh lab
|
||||
```
|
||||
|
||||
### 계획된 환경별 차등
|
||||
`FORCE_FINALIZERS=yes`는 정상 삭제가 반복해서 실패한 namespace 복구의 최후
|
||||
수단입니다. orphaned volume과 controller state를 만들 수 있습니다.
|
||||
|
||||
| 리소스 | dev | staging | prod |
|
||||
|---|---|---|---|
|
||||
| Vault replicas / storage | 1 / 1Gi | 1 / 5Gi | 3 (HA Raft) / 20Gi |
|
||||
| Registry replicas / storage | 1 / 5Gi | 1 / 10Gi | 2 / 50Gi |
|
||||
| PostgreSQL retention policy | Delete | Retain | Retain |
|
||||
| 이미지 tag 정책 | semver tag | semver tag | `@sha256:` digest pin |
|
||||
| TLS | 비활성화 | cert-manager | cert-manager + HSTS |
|
||||
## Validation
|
||||
|
||||
prod 승격 시 필수 작업:
|
||||
```bash
|
||||
make check
|
||||
```
|
||||
|
||||
- Vault storage `file` → `raft` + KMS auto-unseal
|
||||
- Postgres backup CronJob (Velero / pgBackRest)
|
||||
- cert-manager ClusterIssuer 로 TLS 전환
|
||||
- 이미지 tag → digest pin
|
||||
로컬 profile은 render를 항상 수행하고 설치되지 않은 부가 도구는 알려준 뒤
|
||||
건너뜁니다. full profile은 다음 도구가 모두 없으면 실패합니다.
|
||||
|
||||
- kustomize 또는 kubectl
|
||||
- kubeconform
|
||||
- kube-linter
|
||||
- shellcheck
|
||||
- shfmt
|
||||
- gitleaks
|
||||
|
||||
```bash
|
||||
mise install
|
||||
VALIDATION_PROFILE=full make check
|
||||
```
|
||||
|
||||
Gitea workflow는 full profile을 실행합니다. 검증 entrypoint의 source of truth는
|
||||
`tests/kustomize-entrypoints.txt`입니다.
|
||||
|
||||
## External incident actions
|
||||
|
||||
현재 tree에서 민감 파일을 untrack/ignore하는 것만으로 과거 노출은 해결되지
|
||||
않습니다. 다음 작업은 live Vault와 모든 협업자에게 영향을 주므로 repository
|
||||
refactor와 분리합니다.
|
||||
|
||||
1. root token과 unseal/recovery material 회전
|
||||
2. 영향 credential 전체 회전
|
||||
3. 백업과 감사 로그에서 노출 범위 확인
|
||||
4. 협업자에게 force-fetch/reclone 절차 공지
|
||||
5. 승인된 maintenance window에서 원격 Git history 정리
|
||||
|
||||
@@ -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 제목
|
||||
|
||||
## 목적
|
||||
|
||||
## 사전 조건
|
||||
|
||||
## 영향
|
||||
|
||||
## 절차
|
||||
|
||||
## 검증
|
||||
|
||||
## 롤백
|
||||
|
||||
## 에스컬레이션
|
||||
@@ -127,7 +127,7 @@ root token 은 **비상시 (rekey / generate-root / 전체 복구) 전용**으
|
||||
REPO_ROOT="$(pwd)" \
|
||||
VAULT_ADMIN_USERNAME='alice' \
|
||||
VAULT_ADMIN_PASSWORD='<초기 비밀번호>' \
|
||||
bash k8s/scripts/tasks/vault-setup-admin.sh
|
||||
bash scripts/tasks/vault-setup-admin.sh
|
||||
```
|
||||
|
||||
이 스크립트가 수행:
|
||||
@@ -185,7 +185,7 @@ vault token revoke <old-root-token>
|
||||
REPO_ROOT="$(pwd)" \
|
||||
VAULT_ADMIN_USERNAME='bob' \
|
||||
VAULT_ADMIN_PASSWORD='<임시 pw>' \
|
||||
bash k8s/scripts/tasks/vault-setup-admin.sh
|
||||
bash scripts/tasks/vault-setup-admin.sh
|
||||
|
||||
# 제거
|
||||
vault delete auth/userpass/users/bob
|
||||
@@ -227,7 +227,7 @@ vault audit enable socket address=loki-syslog.monitoring.svc:514 socket_type=tcp
|
||||
### 권장 — 대화형 입력
|
||||
|
||||
```bash
|
||||
bash k8s/scripts/bin/bootstrap.sh dev
|
||||
bash scripts/bin/bootstrap.sh lab
|
||||
# 프롬프트에서 무음 입력 (echo 안 됨)
|
||||
```
|
||||
|
||||
@@ -245,7 +245,7 @@ KEYCLOAK_DB_PASSWORD='...' \
|
||||
AUTH_SERVER_DB_PASSWORD='...' \
|
||||
KEYCLOAK_ADMIN_PASSWORD='...' \
|
||||
MINIO_ROOT_PASSWORD='...' \
|
||||
bash k8s/scripts/bin/bootstrap.sh dev
|
||||
bash scripts/bin/bootstrap.sh lab
|
||||
|
||||
# 또는 세션 전체 history 비활성화
|
||||
set +o history
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
|
||||
| 키 | 허용 값 |
|
||||
| --- | --- |
|
||||
| `example.com/environment` | `dev` \| `staging` \| `prod` |
|
||||
| `example.com/environment` | `lab` \| `dev` \| `staging` \| `prod` |
|
||||
| `example.com/owner-team` | 팀 slug (e.g., `auth-platform`, `sre`) |
|
||||
| `example.com/cost-center` | 회계 코스트 센터 ID |
|
||||
| `example.com/data-classification` | `public` \| `internal` \| `confidential` \| `restricted` |
|
||||
@@ -149,7 +149,7 @@ spec:
|
||||
|
||||
1. **prod 환경**: `<registry>/<path>@sha256:<digest>` 형태 digest pin **필수**. 뮤터블 태그(`:1`, `:latest`, `:main`) 금지.
|
||||
2. **staging**: digest 권장, 최소 semver tag(`:1.24.0`) 허용. 절대 `:latest` 금지.
|
||||
3. **dev**: semver tag 허용, `:latest` 지양 (로컬 / 노드 cache invalidation 이슈).
|
||||
3. **lab / dev**: semver tag 허용, `:latest` 지양 (로컬 / 노드 cache invalidation 이슈). `lab` 은 폐기 가능한 단일 클러스터 실험 환경에만 사용한다.
|
||||
4. `imagePullPolicy`:
|
||||
- digest 사용 시 `IfNotPresent` (이미지 콘텐츠는 immutable)
|
||||
- 뮤터블 태그 사용 시 `Always`
|
||||
@@ -265,7 +265,7 @@ spec:
|
||||
|
||||
이 문서의 규약은 CI 에서 기계 검증된다.
|
||||
|
||||
- 실행: `k8s/scripts/ci/validate-docs.sh`
|
||||
- 실행: `scripts/ci/validate-docs.sh`
|
||||
- Lint 설정 위치: `.kube-linter.yaml` (repo root)
|
||||
- 목표 스코어:
|
||||
- syntax 에러: **0**
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
이 문서의 목표:
|
||||
|
||||
- dev / staging / prod 환경 분리를 **label·namespace·selector 레벨에서** 일관되게 만든다
|
||||
- lab / dev / staging / prod 환경 분리를 **label·namespace·selector 레벨에서** 일관되게 만든다
|
||||
- 서비스별 리소스 소유권(팀·도메인·컴포넌트)을 label로 쿼리 가능하게 한다
|
||||
- K3s packaged component와 사용자 AddOn을 혼동하지 않는다
|
||||
- 멀티 서버에서 `manifests/` 디렉터리를 source-of-truth로 쓰는 사고를 원천 차단한다
|
||||
@@ -35,11 +35,13 @@
|
||||
|
||||
기본 환경:
|
||||
|
||||
- `lab` — 폐기 가능한 실험 환경
|
||||
- `dev`
|
||||
- `staging`
|
||||
- `prod`
|
||||
|
||||
필요 시 `sandbox` / `canary` / `dr`을 추가할 수 있으나 dev/staging/prod 의미를 흐리지 않는다.
|
||||
`lab`은 운영 승격 단계가 아니다. 필요 시 `canary` / `dr`을 추가할 수 있으나
|
||||
dev/staging/prod 의미를 흐리지 않는다.
|
||||
|
||||
각 리소스는 두 곳에 동시에 환경이 드러나야 한다.
|
||||
|
||||
@@ -95,7 +97,7 @@
|
||||
|
||||
well-known label 6종으로 표현되지 않는 축은 다음 키로 고정한다.
|
||||
|
||||
- `example.com/environment` — `dev|staging|prod|canary|dr`
|
||||
- `example.com/environment` — `lab|dev|staging|prod|canary|dr`
|
||||
- `example.com/team` — 소유 팀 (예: `identity-sre`)
|
||||
- `example.com/tier` — `frontend|backend|data|platform`
|
||||
- `example.com/data-classification` — `public|internal|confidential|restricted`
|
||||
@@ -107,7 +109,7 @@ well-known label 6종으로 표현되지 않는 축은 다음 키로 고정한
|
||||
- `app.kubernetes.io/environment` 사용 (well-known set에 없음)
|
||||
- 도메인 없는 커스텀 키 (`environment: prod` 같은 top-level key)
|
||||
|
||||
### 6. selector에 들어가는 label은 **불변 3종만**
|
||||
### 6. selector에 들어가는 label은 **불변 2종만**
|
||||
|
||||
Deployment / StatefulSet의 `selector.matchLabels`는 일단 apply 후 수정 불가다. 여기에는 운영 중 **절대 바뀌지 않는** 값만 넣는다.
|
||||
|
||||
@@ -115,11 +117,11 @@ Deployment / StatefulSet의 `selector.matchLabels`는 일단 apply 후 수정
|
||||
|
||||
- `app.kubernetes.io/name`
|
||||
- `app.kubernetes.io/instance`
|
||||
- `app.kubernetes.io/component`
|
||||
|
||||
금지 (selector에 넣지 말 것):
|
||||
|
||||
- `app.kubernetes.io/version` (배포 때마다 바뀜)
|
||||
- `app.kubernetes.io/component` (역할 재분류 시 immutable selector 충돌)
|
||||
- `app.kubernetes.io/managed-by` (툴 교체 시 drift)
|
||||
- `example.com/environment` (overlay에서 주입되면 selector immutable 위반)
|
||||
|
||||
@@ -146,10 +148,11 @@ Deployment / StatefulSet의 `selector.matchLabels`는 일단 apply 후 수정
|
||||
|
||||
기본:
|
||||
|
||||
- Git repo의 `k8s/` 디렉터리가 SoT
|
||||
- Git repo의 `gitops/` 디렉터리가 Kubernetes desired state의 SoT
|
||||
- CI/ArgoCD/Flux가 `kubectl apply --server-side`로 push
|
||||
- 서버별 scp / vim 절대 금지
|
||||
- 멀티 서버 bootstrap AddOn도 Git 관리(예: `k8s/bootstrap/*`를 첫 서버에만 배치)
|
||||
- 멀티 서버 bootstrap AddOn도 Git 관리(`bootstrap/`에서 최소 설치 후
|
||||
`gitops/clusters/` root로 인계)
|
||||
|
||||
### 9. GitOps apply는 Server-Side Apply가 기본
|
||||
|
||||
@@ -168,20 +171,25 @@ kubectl diff --server-side -k <overlay>
|
||||
|
||||
이후 `kustomize.md`에서 상세히 다룬다. 이 문서에서는 원칙만 박는다.
|
||||
|
||||
- `k8s/base/` — 공통 shape, 환경-agnostic
|
||||
- `k8s/overlays/{dev,staging,prod}/` — patches / images / replicas / resources / labels
|
||||
- `gitops/{apps,platform,policies,tenants}/<unit>/base` — 공통 shape, 환경-agnostic
|
||||
- 각 unit의 `overlays/{lab,dev,staging,prod}` — patches / images / replicas / resources / labels
|
||||
- `gitops/clusters/<env>/<cluster>` — catalog를 선택하는 실제 rollout entrypoint
|
||||
|
||||
overlay는 base를 재작성하지 않는다. overlay diff가 100줄을 넘으면 base 설계 실패 신호다.
|
||||
|
||||
### 11. `app/managing/plugins` 책임 분리
|
||||
### 11. catalog와 rollout 책임 분리
|
||||
|
||||
`k8s/base/` 하위는 다음 3축으로 고정한다.
|
||||
`gitops/` 하위 소유권은 다음 축으로 고정한다.
|
||||
|
||||
- `app/units/<domain>/<service>/` — 애플리케이션 유닛 (auth, keycloak, test-server)
|
||||
- `managing/` — Job/CronJob 운영 작업 (flyway-migrate, backup, restore, bootstrap admin)
|
||||
- `plugins/` — 플랫폼 (ingress-controller, cert-manager, external-secrets, observability, policy)
|
||||
- `apps/` — application-facing workload와 그 app이 독점 소유하는 data/operation
|
||||
- `platform/` — 여러 app이 공유하는 platform service와 operator
|
||||
- `policies/` — admission, security와 governance policy
|
||||
- `tenants/` — namespace, RBAC, quota와 tenant boundary
|
||||
- `clusters/` — 환경/클러스터별 최종 조립과 rollout entrypoint
|
||||
|
||||
이 축은 **소유 팀이 다르다**는 가정 위에 있다. 각 축은 독립된 Git owner (CODEOWNERS)를 가진다.
|
||||
stateful 여부보다 실제 lifecycle owner를 우선합니다. 예를 들어 app 전용
|
||||
PostgreSQL과 Flyway는 해당 app catalog가, 공용 MinIO와 Vault는 platform이
|
||||
소유합니다. 각 축은 독립된 Git owner(CODEOWNERS)를 가질 수 있습니다.
|
||||
|
||||
### 12. 상태 저장 / 외부 공개 범위를 architecture 단계에서 분류
|
||||
|
||||
@@ -247,50 +255,41 @@ K3s multi-server에서는 아래가 모든 서버에서 동일해야 한다(불
|
||||
## 추천 디렉터리 구조
|
||||
|
||||
```text
|
||||
k8s/
|
||||
bootstrap/
|
||||
foundation/
|
||||
gitops/
|
||||
gitops/
|
||||
apps/
|
||||
auth-server/
|
||||
base/
|
||||
app/
|
||||
shared/
|
||||
units/
|
||||
identity/
|
||||
auth/
|
||||
kustomization.yaml
|
||||
keycloak/
|
||||
kustomization.yaml
|
||||
data/
|
||||
postgres-identity/
|
||||
kustomization.yaml
|
||||
managing/
|
||||
flyway-migrate-identity/
|
||||
backup-postgres/
|
||||
plugins/
|
||||
overlays/{lab,staging,prod}/
|
||||
identity-postgres/
|
||||
auth-migration/
|
||||
platform/
|
||||
ingress-nginx/
|
||||
cert-manager/
|
||||
external-secrets/
|
||||
kube-prometheus-stack/
|
||||
overlays/
|
||||
dev/
|
||||
kustomization.yaml
|
||||
staging/
|
||||
kustomization.yaml
|
||||
prod/
|
||||
kustomization.yaml
|
||||
region-kr-main/
|
||||
region-kr-dr/
|
||||
bootstrap/
|
||||
k3s-addons-disabled/
|
||||
secret-delivery/
|
||||
policies/
|
||||
baseline/
|
||||
tenants/
|
||||
identity/
|
||||
clusters/
|
||||
lab/main/stages/
|
||||
staging/main/stages/
|
||||
prod/kr-main/stages/
|
||||
prod/kr-dr/stages/
|
||||
scripts/
|
||||
render.sh
|
||||
diff.sh
|
||||
apply.sh
|
||||
bin/
|
||||
ci/
|
||||
tasks/
|
||||
```
|
||||
|
||||
## 프로젝트 기준 요약
|
||||
|
||||
- 환경 3종(`dev`/`staging`/`prod`) + namespace prefix 고정
|
||||
- 환경 4종(`lab`/`dev`/`staging`/`prod`) + namespace prefix 고정
|
||||
- well-known `app.kubernetes.io/*` 6개 + 자체 도메인 운영 label 필수
|
||||
- `app.kubernetes.io/environment` 사용 금지, `example.com/environment`로 대체
|
||||
- selector에는 불변 3종만
|
||||
- selector에는 불변 2종(`name`/`instance`)만
|
||||
- K3s packaged component는 초기에 disable 여부 결정, 직접 수정 금지
|
||||
- `manifests/`는 apply sink, Git이 SoT
|
||||
- `kubectl apply --server-side` GitOps 기본
|
||||
|
||||
@@ -90,7 +90,8 @@ Flyway migration은 **독립 실행 단계**다.
|
||||
- CI/CD 명시 단계
|
||||
- 운영자 명시 실행 절차
|
||||
|
||||
장기 실행 Deployment에 넣지 않는다. Flyway 예시는 `examples/infra/flyway.md` 참조.
|
||||
장기 실행 Deployment에 넣지 않는다. Flyway 예시는
|
||||
`docs/examples/infra/flyway.md` 참조.
|
||||
|
||||
### 5. migration Job은 배포 흐름 안에서 app보다 먼저 실행
|
||||
migration을 app보다 **선행**시키는 것은 manifest 메타데이터로 선언한다.
|
||||
@@ -139,7 +140,9 @@ metadata:
|
||||
4. `flyway info` (결과 확인)
|
||||
5. app rollout
|
||||
|
||||
`validateOnMigrate=true` 기본값이 있더라도, 운영 runbook에서는 validate 단계를 **분리 Job** 또는 **initContainer**로 분리한다. `examples/infra/flyway.md` 참조.
|
||||
`validateOnMigrate=true` 기본값이 있더라도, 운영 runbook에서는 validate 단계를
|
||||
**분리 Job** 또는 **initContainer**로 분리한다.
|
||||
`docs/examples/infra/flyway.md` 참조.
|
||||
|
||||
### 8. migration source는 Git이 source of truth
|
||||
중요한 것은 아래다.
|
||||
|
||||
@@ -75,7 +75,7 @@ Pod 내부에서 TLS 재암호화가 필요 없으면 Pod는 HTTP로 수신한
|
||||
|
||||
기본:
|
||||
- `KC_DB=postgres`
|
||||
- `KC_DB_URL=jdbc:postgresql://keycloak-db-rw:5432/keycloak` (CloudNativePG `-rw` RW endpoint 권장)
|
||||
- `KC_DB_URL=jdbc:postgresql://keycloak-db-rw:5432/keycloak` (CloudNativePG `-rw` RW endpoint 권장) <!-- gitleaks:allow -->
|
||||
- `KC_DB_USERNAME`, `KC_DB_PASSWORD` → Secret `secretKeyRef`
|
||||
- Keycloak schema와 auth-server schema는 **다른 DB 또는 다른 database**로 분리
|
||||
|
||||
|
||||
@@ -57,10 +57,10 @@ Kustomize는 Kubernetes 리소스를 **template-free**로 조합하고 환경별
|
||||
|
||||
overlay 한 디렉터리의 `kustomization.yaml`은 짧아야 한다. diff가 몇 백 줄을 넘으면 base 설계 실패 신호.
|
||||
|
||||
권장 구조:
|
||||
권장 unit overlay 구조:
|
||||
|
||||
```
|
||||
overlays/prod/
|
||||
gitops/apps/auth-server/overlays/prod/
|
||||
kustomization.yaml
|
||||
patches/
|
||||
auth-replicas.yaml
|
||||
@@ -70,21 +70,26 @@ overlays/prod/
|
||||
postgres-storage.yaml
|
||||
```
|
||||
|
||||
### 4. 디렉터리 구조는 base / components / overlays 3축
|
||||
### 4. catalog unit과 cluster entrypoint를 분리
|
||||
|
||||
```
|
||||
k8s/
|
||||
gitops/
|
||||
apps/<unit>/
|
||||
base/
|
||||
app/units/<domain>/<service>/
|
||||
managing/<job>/
|
||||
plugins/<platform>/
|
||||
components/
|
||||
<reusable-cross-cutting>/
|
||||
overlays/
|
||||
<env>/[region/]
|
||||
overlays/<env>/
|
||||
platform/<unit>/
|
||||
base/
|
||||
components/
|
||||
overlays/<env>/
|
||||
policies/<unit>/
|
||||
tenants/<unit>/
|
||||
clusters/<env>/<region-or-cluster>/
|
||||
```
|
||||
|
||||
`components/`는 "Kustomize Components"로, 여러 overlay에서 재사용.
|
||||
`components/`는 소유 unit 내부의 Kustomize Component입니다. 여러 catalog를
|
||||
조립하는 최종 경계는 `clusters/`이며 catalog 디렉터리를 controller root로
|
||||
직접 사용하지 않습니다.
|
||||
|
||||
### 5. `commonLabels` 금지, `labels:` 사용
|
||||
|
||||
@@ -108,15 +113,14 @@ labels:
|
||||
|
||||
기존 `commonLabels` 사용 코드는 migration plan을 세워 교체. selector에 이미 들어간 label이 있다면 해당 리소스를 **재배포** (delete + recreate) 없이는 변경 불가.
|
||||
|
||||
### 6. selector에는 불변 3종만
|
||||
### 6. selector에는 불변 2종만
|
||||
|
||||
overlay에서 selector를 건드리지 않는다. selector에 허용되는 label은:
|
||||
|
||||
- `app.kubernetes.io/name`
|
||||
- `app.kubernetes.io/instance`
|
||||
- `app.kubernetes.io/component`
|
||||
|
||||
이 3종은 base에서 고정. overlay가 `labels:`로 추가하는 label은 반드시 `includeSelectors: false`.
|
||||
이 2종은 base에서 고정. overlay가 `labels:`로 추가하는 label은 반드시 `includeSelectors: false`.
|
||||
|
||||
### 7. `patches:` (v5 스타일) 사용, `patchesStrategicMerge` / `patchesJson6902` 금지
|
||||
|
||||
@@ -146,7 +150,7 @@ patches:
|
||||
multiple overlay에서 공통으로 끼워야 하는 변경(예: mTLS 활성화, sidecar 주입, monitoring label 추가)은 component로.
|
||||
|
||||
```
|
||||
components/
|
||||
gitops/apps/auth-server/components/
|
||||
with-istio-sidecar/
|
||||
kustomization.yaml # kind: Component
|
||||
patches/
|
||||
@@ -215,11 +219,15 @@ field manager 이름을 환경별로 통일해야 `managedFields` 충돌이 예
|
||||
CI가 아래를 순서대로 실행:
|
||||
|
||||
```bash
|
||||
kubectl kustomize overlays/prod > /tmp/rendered.yaml
|
||||
kubectl kustomize gitops/clusters/prod/kr-main/all > /tmp/rendered.yaml
|
||||
kubeconform -strict -summary -schema-location default -schema-location 'https://raw.githubusercontent.com/datreeio/CRDs-catalog/main/{{.Group}}/{{.ResourceKind}}_{{.ResourceAPIVersion}}.json' /tmp/rendered.yaml
|
||||
kubectl diff --server-side --field-manager=ci -k overlays/prod
|
||||
kubectl diff --server-side --field-manager=ci -k gitops/clusters/prod/kr-main/stages/50-apps
|
||||
```
|
||||
|
||||
`all`은 schema/policy 감사용이고 diff/apply는 의존성이 준비된 개별 stage를
|
||||
대상으로 합니다. 여러 stage 변경은 승인된 orchestrator/controller가 순서와
|
||||
health gate를 보장해야 합니다.
|
||||
|
||||
- kubeconform / kubeval: schema validation
|
||||
- kyverno / OPA Gatekeeper: policy validation (post-render)
|
||||
- conftest: opa policy bundle 실행
|
||||
@@ -254,62 +262,41 @@ v2.1에서 `bases:`가 `resources:`로 통합됨. 신규 파일에서 `bases:`
|
||||
## 추천 폴더 구조
|
||||
|
||||
```text
|
||||
k8s/
|
||||
base/
|
||||
app/
|
||||
kustomization.yaml
|
||||
units/
|
||||
identity/
|
||||
gitops/
|
||||
apps/
|
||||
auth/
|
||||
base/
|
||||
kustomization.yaml
|
||||
deployment.yaml
|
||||
service.yaml
|
||||
servicemonitor.yaml
|
||||
pdb.yaml
|
||||
hpa.yaml
|
||||
keycloak/
|
||||
kustomization.yaml
|
||||
data/
|
||||
postgres-identity/
|
||||
kustomization.yaml
|
||||
statefulset.yaml
|
||||
service-headless.yaml
|
||||
service.yaml
|
||||
managing/
|
||||
flyway-migrate-identity/
|
||||
kustomization.yaml
|
||||
job.yaml
|
||||
backup-postgres/
|
||||
kustomization.yaml
|
||||
cronjob.yaml
|
||||
plugins/
|
||||
ingress-nginx/
|
||||
cert-manager/
|
||||
external-secrets/
|
||||
kube-prometheus-stack/
|
||||
fluent-bit/
|
||||
components/
|
||||
with-service-monitor/
|
||||
with-pdb-tier1/
|
||||
with-topology-spread-zone/
|
||||
with-network-policy-deny-default/
|
||||
overlays/
|
||||
dev/
|
||||
kustomization.yaml
|
||||
staging/
|
||||
kustomization.yaml
|
||||
prod/
|
||||
kr-main/
|
||||
kustomization.yaml
|
||||
patches/
|
||||
kr-dr/
|
||||
kustomization.yaml
|
||||
patches/
|
||||
overlays/{lab,staging,prod}/
|
||||
postgres-identity/
|
||||
base/
|
||||
overlays/{lab,staging,prod}/
|
||||
flyway-migrate-identity/
|
||||
base/
|
||||
overlays/{lab,staging,prod}/
|
||||
platform/
|
||||
ingress-nginx/
|
||||
cert-manager/
|
||||
secret-delivery/
|
||||
policies/
|
||||
network-policy-deny-default/
|
||||
clusters/
|
||||
lab/main/stages/
|
||||
staging/main/stages/
|
||||
prod/kr-main/stages/
|
||||
prod/kr-dr/stages/
|
||||
scripts/
|
||||
render.sh
|
||||
diff.sh
|
||||
apply.sh
|
||||
validate.sh
|
||||
bin/
|
||||
ci/
|
||||
```
|
||||
|
||||
## 프로젝트 기준 요약
|
||||
@@ -317,9 +304,10 @@ k8s/
|
||||
- Kustomize v5 문법 기준, `commonLabels` 금지, `labels:` 사용
|
||||
- `patches:` 단일 키, `target:` + `path:` 또는 `patch:` inline
|
||||
- `components:`로 cross-cutting 재사용
|
||||
- selector에는 불변 3종만 (name / instance / component)
|
||||
- selector에는 불변 2종만 (name / instance)
|
||||
- generator는 configMap만 기본, secret은 External Secrets
|
||||
- `kubectl apply --server-side --field-manager=<id>` 전제
|
||||
- render + schema + policy 검증을 CI에서 강제
|
||||
- base / components / overlays 3축 디렉터리
|
||||
- catalog unit의 base/components/overlays와 cluster entrypoint를 분리
|
||||
- `stages/`만 apply하고 `all/`은 render/schema/policy audit에만 사용
|
||||
- overlay diff는 짧아야 한다 (base 재작성 금지)
|
||||
|
||||
@@ -88,7 +88,10 @@
|
||||
기본:
|
||||
- `spec.configuration.name`에 root credential Secret (MINIO_ROOT_USER, MINIO_ROOT_PASSWORD)
|
||||
- Vault KV에 root credential 저장, VSO로 Secret 동기화
|
||||
- 앱용 access는 `mc admin user svcacct add` 로 service account 발급
|
||||
- 앱용 access는 `mc admin accesskey create`로 서버 생성 credential을 발급하고
|
||||
inline policy로 권한을 축소한다
|
||||
- 생성 결과의 secret key는 한 번만 수신해 stdin으로 Vault에 기록하며,
|
||||
`--secret-key` 또는 `mc admin user add`로 secret을 argv에 전달하지 않는다
|
||||
- service account는 최소 권한 policy 바인딩
|
||||
|
||||
### 7. TLS는 기본 활성화
|
||||
|
||||
@@ -126,18 +126,17 @@ Flux 플랫폼에서는 `Kustomization.spec.dependsOn` 으로 순서를 명시
|
||||
apiVersion: kustomize.toolkit.fluxcd.io/v1
|
||||
kind: Kustomization
|
||||
metadata:
|
||||
name: auth-server
|
||||
name: prod-50-apps
|
||||
namespace: flux-system
|
||||
spec:
|
||||
interval: 5m
|
||||
path: ./k8s/overlays/prod
|
||||
path: ./gitops/clusters/prod/main/stages/50-apps
|
||||
prune: true
|
||||
sourceRef:
|
||||
kind: GitRepository
|
||||
name: platform
|
||||
dependsOn:
|
||||
- name: cert-manager
|
||||
- name: postgres-operator
|
||||
- name: prod-40-operations
|
||||
```
|
||||
|
||||
### 11. node 작업 (drain / cordon) 은 PDB 존중 흐름
|
||||
|
||||
+40
-36
@@ -1,19 +1,23 @@
|
||||
# 운영 중 만난 함정 9건 — 사건 카탈로그
|
||||
|
||||
K3s 기반 로컬 클러스터에서 Project-Infra 를 부트스트랩 / 운영하면서 실제로 만났던 사건들의 narrative 정리.
|
||||
운영자 절차서 톤은 [`guide.md`](../guide.md) 의 15장 (`15.1` ~ `15.11`) 에 있고, 이 문서는 사건 단위로 "무엇을 보고 / 왜 그랬고 / 어떻게 풀었는지" 를 짧게 쓰기 위한 자료다.
|
||||
> 이 문서는 과거 `dev` 단일-aggregate 시기의 incident 기록이다. node 이름,
|
||||
> host, 인증서, 수동 apply 명령은 당시 상태를 설명하며 현재 실행 runbook이
|
||||
> 아니다. 현재 배포와 복구는 `docs/operations.md`와 staged bootstrap을 따른다.
|
||||
|
||||
| # | 사건 | guide.md cross-ref |
|
||||
|---|---|---|
|
||||
| 1 | Registry image pull 실패 (`ImagePullBackOff`) | (별건 — guide.md 15.1 은 ContainerCreating 사건) |
|
||||
| 2 | `vault-0` 가 `0/1 Running` 에서 멈춤 | [15.10](../guide.md#1510-vault-0-이-01-running-에서-멈춤) |
|
||||
| 3 | `helm upgrade` 가 `has no deployed releases` 로 실패 | [15.11](../guide.md#1511-helm-upgrade-가-has-no-deployed-releases-로-실패) |
|
||||
| 4 | VSO 가 기존 K8s Secret 을 덮어쓰지 않음 | [15.8](../guide.md#158-기존-k8s-secret-이-남아있을-때) |
|
||||
| 5 | ForwardAuth 로그인 E2E 검증 실패 | (현장 검증 사건) |
|
||||
| 6 | namespace 가 `Terminating` 에 걸림 | [15.9](../guide.md#159-namespace-가-terminating-에-걸림) |
|
||||
| 7 | PodSecurity 위반 경고 (admission) | [15.7](../guide.md#157-podsecurity-위반-경고) |
|
||||
| 8 | VSO 가 Vault 로그인 실패 | [15.2](../guide.md#152-vso-가-vault-에-로그인-실패) |
|
||||
| 9 | Registry 는 살아났지만 auth-server 새 이미지 pull 이 끝나지 않음 | (해결됨 — registries.yaml 제거 + hosts.toml 직접 작성) |
|
||||
K3s 기반 로컬 클러스터에서 Project-Infra 를 부트스트랩 / 운영하면서 실제로
|
||||
만났던 사건을 "무엇을 보고 / 왜 그랬고 / 어떻게 풀었는지" 순서로 기록한다.
|
||||
|
||||
| # | 사건 |
|
||||
| --- | --- |
|
||||
| 1 | Registry image pull 실패 (`ImagePullBackOff`) |
|
||||
| 2 | `vault-0` 가 `0/1 Running` 에서 멈춤 |
|
||||
| 3 | `helm upgrade` 가 `has no deployed releases` 로 실패 |
|
||||
| 4 | VSO 가 기존 K8s Secret 을 덮어쓰지 않음 |
|
||||
| 5 | ForwardAuth 로그인 E2E 검증 실패 |
|
||||
| 6 | namespace 가 `Terminating` 에 걸림 |
|
||||
| 7 | PodSecurity 위반 경고 (admission) |
|
||||
| 8 | VSO 가 Vault 로그인 실패 |
|
||||
| 9 | Registry 는 살아났지만 auth-server 새 이미지 pull 이 끝나지 않음 |
|
||||
|
||||
---
|
||||
|
||||
@@ -133,8 +137,8 @@ Git/Kustomize 원천 파일을 수정했다.
|
||||
|
||||
변경 파일:
|
||||
|
||||
- `k8s/base/plugins/docker-registry/configmap.yaml`
|
||||
- `k8s/overlays/dev/registry/networkpolicy.yaml`
|
||||
- `gitops/platform/registry/base/configmap.yaml`
|
||||
- `gitops/platform/registry/overlays/lab/networkpolicy.yaml`
|
||||
|
||||
변경 내용:
|
||||
|
||||
@@ -156,8 +160,8 @@ ports:
|
||||
적용:
|
||||
|
||||
```bash
|
||||
kubectl diff -k k8s/overlays/dev/registry
|
||||
kubectl apply -k k8s/overlays/dev/registry
|
||||
kubectl diff -k gitops/platform/registry/overlays/lab
|
||||
kubectl apply -k gitops/platform/registry/overlays/lab
|
||||
kubectl -n mnt rollout restart deployment/docker-registry
|
||||
kubectl -n mnt rollout status deployment/docker-registry --timeout=180s
|
||||
```
|
||||
@@ -340,8 +344,8 @@ server = "http://registry.project.com"
|
||||
skip_verify = true
|
||||
|
||||
[host."http://registry.project.com".auth]
|
||||
username = "testuser"
|
||||
password = "abcd6845"
|
||||
username = "<registry-username>"
|
||||
password = "<registry-password>"
|
||||
EOF
|
||||
|
||||
# 3) 적용
|
||||
@@ -360,7 +364,7 @@ sudo systemctl restart k3s # control-plane
|
||||
```bash
|
||||
# 노드의 registry 연결 직접 검증
|
||||
sudo /usr/local/bin/k3s ctr -a /run/k3s/containerd/containerd.sock \
|
||||
images pull --plain-http --user 'testuser:abcd6845' \
|
||||
images pull --plain-http --user '<registry-username>:<registry-password>' \
|
||||
registry.project.com/auth-platform/auth-server:manual-20260512071751
|
||||
|
||||
# Pod 가 새 이미지로 정상 Running 되는지
|
||||
@@ -425,7 +429,7 @@ vault-0 0/1 Running 0 5m
|
||||
### 해결
|
||||
|
||||
```bash
|
||||
REPO_ROOT="$(pwd)" ENV_NAME=dev bash k8s/scripts/tasks/vault-init.sh
|
||||
REPO_ROOT="$(pwd)" ENV_NAME=lab bash scripts/tasks/vault-init.sh
|
||||
```
|
||||
|
||||
이 스크립트는 idempotent — 다음을 차례로 처리한다.
|
||||
@@ -526,7 +530,7 @@ Vault KV 에 새 값을 넣었는데 K8s Secret 은 옛날 값을 유지. `Vault
|
||||
bootstrap 스크립트에 명시적 opt-in 환경변수를 둠.
|
||||
|
||||
```bash
|
||||
RESET_STALE_SECRETS=yes bash k8s/scripts/bin/bootstrap.sh dev
|
||||
RESET_STALE_SECRETS=yes bash scripts/bin/bootstrap.sh lab
|
||||
```
|
||||
|
||||
이 옵션이 있을 때만 VSO-managed K8s Secret 후보들을 선제 삭제 → VSO 가 새로 생성.
|
||||
@@ -640,7 +644,7 @@ unknown TLS options: kube-system-modern-tls@kubernetescrd
|
||||
Traefik packaged manifest 를 직접 수정하지 않고, Git source-of-truth 인 overlay 를 적용했다.
|
||||
|
||||
```bash
|
||||
kubectl apply -k k8s/overlays/dev/platform/traefik
|
||||
kubectl apply -k gitops/platform/traefik/overlays/lab
|
||||
```
|
||||
|
||||
적용된 리소스:
|
||||
@@ -680,17 +684,17 @@ dev 전용 `ClusterIssuer/dev-selfsigned` 를 추가하고, dev TLS `Certificate
|
||||
|
||||
변경 파일:
|
||||
|
||||
- `k8s/overlays/dev/platform/cert-manager-issuers/dev-selfsigned-clusterissuer.yaml`
|
||||
- `k8s/overlays/dev/platform/cert-manager-issuers/kustomization.yaml`
|
||||
- `k8s/overlays/dev/tls/project-com-certificate.yaml`
|
||||
- `k8s/overlays/dev/tls/keycloak-dev-certificate.yaml`
|
||||
- `k8s/overlays/dev/tls/registry-project-com-certificate.yaml`
|
||||
- `gitops/platform/cert-manager/overlays/lab/issuers/dev-selfsigned-clusterissuer.yaml`
|
||||
- `gitops/platform/cert-manager/overlays/lab/issuers/kustomization.yaml`
|
||||
- `gitops/platform/cert-manager/overlays/lab/certificates/project-com-certificate.yaml`
|
||||
- `gitops/platform/cert-manager/overlays/lab/certificates/keycloak-dev-certificate.yaml`
|
||||
- `gitops/platform/cert-manager/overlays/lab/certificates/registry-project-com-certificate.yaml`
|
||||
|
||||
적용:
|
||||
|
||||
```bash
|
||||
kubectl apply -k k8s/overlays/dev/platform/cert-manager-issuers
|
||||
kubectl apply -k k8s/overlays/dev/tls
|
||||
kubectl apply -k gitops/platform/cert-manager/overlays/lab/issuers
|
||||
kubectl apply -k gitops/platform/cert-manager/overlays/lab/certificates
|
||||
kubectl -n mnt wait --for=condition=Ready certificate/project-com --timeout=120s
|
||||
kubectl -n mnt wait --for=condition=Ready certificate/keycloak-dev-example-com --timeout=120s
|
||||
```
|
||||
@@ -846,7 +850,7 @@ set_authorization_header = true
|
||||
|
||||
변경 파일:
|
||||
|
||||
- `k8s/components/forward-auth/oauth2-proxy-config.yaml`
|
||||
- `gitops/platform/forward-auth/component/oauth2-proxy-config.yaml`
|
||||
|
||||
반영 후 oauth2-proxy 를 재시작했다.
|
||||
|
||||
@@ -1071,15 +1075,15 @@ spec:
|
||||
type: RuntimeDefault
|
||||
```
|
||||
|
||||
이 패턴은 [`docs/security-hardening.md`](./security-hardening.md) 의 체크리스트에 정리되어 있고, [`k8s/scripts/ci/validate.sh`](../k8s/scripts/ci/validate.sh) 가 `kube-linter` 로 회귀를 막는다.
|
||||
이 패턴은 [`docs/security-hardening.md`](./security-hardening.md) 의 체크리스트에 정리되어 있고, [`scripts/ci/validate.sh`](../scripts/ci/validate.sh) 가 `kube-linter` 로 회귀를 막는다.
|
||||
|
||||
### 검증
|
||||
|
||||
```bash
|
||||
bash k8s/scripts/ci/validate.sh
|
||||
bash scripts/ci/validate.sh
|
||||
# build=ok schema=ok lint=ok
|
||||
|
||||
kubectl apply -k k8s/overlays/dev
|
||||
bash scripts/bin/bootstrap.sh lab
|
||||
# Warning 없음
|
||||
```
|
||||
|
||||
@@ -1123,10 +1127,10 @@ K8s 인증의 의존 그래프는 두 단:
|
||||
|
||||
```bash
|
||||
# 1. ClusterRoleBinding 적용
|
||||
kubectl apply -k k8s/overlays/<env>/vault/
|
||||
kubectl apply -k gitops/platform/vault/overlays/<env>/
|
||||
|
||||
# 2. vault auth/kubernetes/config 설정 (idempotent)
|
||||
REPO_ROOT="$(pwd)" ENV_NAME=<env> bash k8s/scripts/tasks/vault-init.sh
|
||||
REPO_ROOT="$(pwd)" ENV_NAME=<env> bash scripts/tasks/vault-init.sh
|
||||
```
|
||||
|
||||
`vault-init.sh` 는 다음을 자동으로 한다:
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# Docs validation report
|
||||
|
||||
> Historical report for the documentation examples at the timestamp below.
|
||||
> It is not the current repository gate. Use `make check`; current deployable
|
||||
> entrypoints are listed in `tests/kustomize-entrypoints.txt`.
|
||||
|
||||
Generated: 2026-04-20T08:51:44Z
|
||||
Tools: kubeconform v0.6.7, kube-linter v0.7.4, yq v4.44.3
|
||||
CRD schemas: datreeio/CRDs-catalog (remote fetch)
|
||||
|
||||
+59
-86
@@ -1,98 +1,71 @@
|
||||
# Vault / VSO 상세
|
||||
# Vault and Vault Secrets Operator
|
||||
|
||||
README 의 Vault·VSO 핵심 섹션을 보충한다. Kubernetes auth 초기화 명령, policy/role 매핑, VaultStaticSecret 카탈로그, dockerconfigjson `.auth` 이슈를 모은다.
|
||||
## Ownership
|
||||
|
||||
## Vault 기본 정보
|
||||
- Vault workload: `gitops/platform/vault`
|
||||
- VSO Helm values: `gitops/platform/secret-delivery/base/helm/values.yaml`
|
||||
- VaultConnection/VaultAuth: `gitops/platform/secret-delivery/base`
|
||||
- lab secret declarations: `gitops/platform/secret-delivery/overlays/lab`
|
||||
|
||||
| 항목 | 값 |
|
||||
Vault KV-v2가 secret source of truth이며, workload는 VSO가 만든 Kubernetes
|
||||
Secret만 소비합니다.
|
||||
|
||||
## Bootstrap order
|
||||
|
||||
```text
|
||||
VSO controller/CRD install
|
||||
Vault workload Running
|
||||
Vault init + unseal
|
||||
Kubernetes auth + policy/role
|
||||
KV seed
|
||||
VaultConnection/VaultAuth/pre-data VaultStaticSecret apply
|
||||
pre-data destination Secret wait
|
||||
PostgreSQL/MinIO/Keycloak apply
|
||||
MinIO registry access key generate -> Vault
|
||||
registry VaultStaticSecret/apply
|
||||
consumer workloads apply
|
||||
```
|
||||
|
||||
이 순서 때문에 `all/` aggregate를 직접 apply할 수 없습니다.
|
||||
|
||||
## Access split
|
||||
|
||||
| VaultAuth | Policy | Read paths |
|
||||
| --- | --- | --- |
|
||||
| `vault-auth-auth-platform` | `vso-auth-platform` | `identity-postgres/*`, `auth-server/*`, `keycloak/*`, `oauth2-proxy/*` |
|
||||
| `vault-auth-storage` | `vso-storage` | `minio/*`, `docker-registry/*` |
|
||||
|
||||
현재 lab은 두 auth boundary가 같은 namespace와 ServiceAccount를 공유합니다.
|
||||
실무 namespace 분리 시 workload/domain별 ServiceAccount, VaultAuth, policy,
|
||||
bound namespace를 함께 분리해야 합니다.
|
||||
|
||||
## Secret catalog
|
||||
|
||||
| Domain | Kubernetes Secret |
|
||||
| --- | --- |
|
||||
| 이미지 | `hashicorp/vault:1.17.2` |
|
||||
| 배포 | StatefulSet (replicas 1, file backend) |
|
||||
| 실행 | `vault server -config=/vault/config/vault.hcl` |
|
||||
| 포트 | 8200 (http) / 8201 (cluster) — 내부 ClusterIP, NodePort 없음 |
|
||||
| 저장 | PVC 5Gi (dev overlay 1Gi patch) |
|
||||
| UI 접근 | `kubectl -n mnt port-forward svc/vault 8200:8200` (외부 노출 금지) |
|
||||
| PostgreSQL | `identity-postgres-superuser`, `keycloak-db`, `auth-server-db` |
|
||||
| Keycloak | `keycloak-db-operator`, `keycloak-bootstrap-admin-operator`, `keycloak-client-auth-server-ingress` |
|
||||
| oauth2-proxy | `oauth2-proxy-secrets` |
|
||||
| MinIO | `minio-tenant-env` |
|
||||
| registry | `docker-registry-minio`, `docker-registry-basic-auth`, `docker-registry-pull-credentials` |
|
||||
|
||||
## 설계 결정
|
||||
VSO destinations use `overwrite: true`. auth-server와 oauth2-proxy는 지원되는
|
||||
Secret 변경에 rollout target을 선언합니다. DB/MinIO/registry credential은
|
||||
backend 상태를 먼저 바꾸는 조정된 rotation이 필요합니다.
|
||||
|
||||
- **file backend (학습 환경 전용)**: 단일 노드 + 학습 목적으로 `storage "file"`. HA 불가. prod 승격 시 `storage "raft"` + KMS 기반 auto-unseal 로 전환.
|
||||
- **`disable_mlock = true`**: 컨테이너에 `IPC_LOCK` capability 를 부여하지 않고 PSS Restricted 프로필을 유지하기 위함. 대신 swap 이 꺼진 노드에서 실행해야 한다.
|
||||
- **`tls_disable = 1`**: 단일 namespace 내부 통신만 발생하고 cert-manager 전에 부트스트랩이 끝나야 해서 현재는 비활성화. 클러스터 밖 노출 시 cert-manager 발급 인증서로 TLS 활성화 필수.
|
||||
- **`api_addr: http://vault:8200` + `cluster_addr: http://vault:8201`**: 짧은 Service 이름. 모든 소비자가 같은 `mnt` namespace 에 있어 FQDN 불필요.
|
||||
`docker-registry-minio`는 MinIO가 생성한 access key를 Vault에 기록한 뒤
|
||||
`35-registry`에서 동기화합니다. 다른 정적 seed와 같은 시점에 미리 만들지
|
||||
않습니다.
|
||||
|
||||
## RBAC
|
||||
## Key material
|
||||
|
||||
ClusterRoleBinding `vault-tokenreview-binding` ← `system:auth-delegator`. Vault 의 Kubernetes auth method 는 클라이언트(VSO 등)가 제출한 ServiceAccount JWT 를 `TokenReview` + `SubjectAccessReview` API 로 검증한다. 이 ClusterRoleBinding 이 없으면 VSO 로그인이 `permission denied` 로 실패한다.
|
||||
|
||||
Vault Pod 의 ServiceAccount 는 `automountServiceAccountToken: true` (기본). Vault 는 `/var/run/secrets/kubernetes.io/serviceaccount/{token,ca.crt}` 를 읽어 `auth/kubernetes/config` 의 `token_reviewer_jwt` / `kubernetes_ca_cert` 를 채운다.
|
||||
|
||||
## Kubernetes auth 초기 설정
|
||||
|
||||
`tasks/vault-init.sh` 가 수행:
|
||||
`vault-init-keys.json`에는 root token과 unseal material이 들어가므로 Git에
|
||||
들어가면 안 됩니다. 기본 lab 파일도 평문이며 단지 ignored/0600일 뿐입니다.
|
||||
|
||||
```bash
|
||||
vault auth enable kubernetes # idempotent 체크
|
||||
vault write auth/kubernetes/config \
|
||||
kubernetes_host="https://kubernetes.default.svc.cluster.local:443" \
|
||||
kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt \
|
||||
token_reviewer_jwt=@/var/run/secrets/kubernetes.io/serviceaccount/token
|
||||
|
||||
vault secrets enable -path=secret kv-v2 # idempotent 체크
|
||||
|
||||
# policy 2개 (역할별 least-privilege)
|
||||
vault policy write vso-auth-platform - \
|
||||
# identity-postgres/* + auth-server/* + keycloak/* read
|
||||
vault policy write vso-storage - \
|
||||
# minio/* read
|
||||
|
||||
# role 2개 (같은 SA, 다른 policy)
|
||||
vault write auth/kubernetes/role/vso-auth-platform \
|
||||
policies=vso-auth-platform bound_sa=vault-secrets-operator/mnt ttl=1h
|
||||
vault write auth/kubernetes/role/vso-storage \
|
||||
policies=vso-storage bound_sa=vault-secrets-operator/mnt ttl=1h
|
||||
VAULT_KEYS_FILE=/secure/path/lab-vault-init.json \
|
||||
bash scripts/bin/bootstrap.sh lab
|
||||
```
|
||||
|
||||
## VaultAuth / VaultStaticSecret 매핑
|
||||
|
||||
policy 분리에 따라 VaultAuth CR 도 2 개. 각 VaultStaticSecret 은 자기 도메인의 VaultAuth 를 참조한다:
|
||||
|
||||
| VaultAuth CR | Vault role | 참조 VaultStaticSecret |
|
||||
|---|---|---|
|
||||
| `vault-auth-auth-platform` | `vso-auth-platform` | `identity-postgres-superuser`, `keycloak-db-creds`, `auth-server-db-creds`, `keycloak-bootstrap-admin` |
|
||||
| `vault-auth-storage` | `vso-storage` | `minio-tenant-env` |
|
||||
|
||||
VSO Operator SA(`vault-secrets-operator`) 는 한 개이지만 Vault 쪽에서 role 별 policy 가 분리되어 있다. auth-platform 토큰이 유출돼도 MinIO secret 은 보호된다.
|
||||
|
||||
## VSO 가 관리하는 Secret 카탈로그
|
||||
|
||||
Registry 는 auth 없이 운영(NetworkPolicy 로 `mnt` 내부 전용 보호)이라 base 에는 VaultStaticSecret 이 없다. dev overlay 에서 BasicAuth / pull credential 두 개를 추가한다.
|
||||
|
||||
| VaultStaticSecret (dev overlay) | Vault 경로 | K8s Secret | 소비 방식 |
|
||||
|---|---|---|---|
|
||||
| `identity-postgres-superuser` | `secret/identity-postgres/superuser` | `identity-postgres-superuser` | file mount (`/run/secrets/superuser/`) → `POSTGRES_USER_FILE`, `POSTGRES_PASSWORD_FILE` |
|
||||
| `keycloak-db-creds` | `secret/keycloak/db` | `keycloak-db` | file mount — postgres initdb + keycloak `KC_DB_PASSWORD_FILE` |
|
||||
| `auth-server-db-creds` | `secret/auth-server/db` | `auth-server-db` | file mount — `SPRING_CONFIG_IMPORT=configtree:/etc/secrets/` + Flyway sh wrapper |
|
||||
| `keycloak-bootstrap-admin` | `secret/keycloak/bootstrap-admin` | `keycloak-bootstrap-admin` | file mount — `KC_BOOTSTRAP_ADMIN_{USERNAME,PASSWORD}_FILE` |
|
||||
| `minio-tenant-env` | `secret/minio/tenant-env` | `minio-tenant-env` | MinIO Operator `spec.configuration.name` (env file) |
|
||||
| `docker-registry-basic-auth` (dev) | `secret/docker-registry/basic-auth` | `docker-registry-basic-auth` | Traefik Middleware basicAuth |
|
||||
| `docker-registry-pull-credentials` (dev) | `secret/docker-registry/pull-cred` | `docker-registry-pull-credentials` | imagePullSecret (`.dockerconfigjson`) |
|
||||
|
||||
모든 VaultStaticSecret 은 `destination.overwrite` 기본값(`false`) 사용. 기존 Secret 이 수동으로 존재하면 VSO 가 덮어쓰지 않는다 (소유권 경합 방지).
|
||||
|
||||
> **Vault 값 교체 후 즉시 반영**: `kubectl -n mnt delete secret <name>` 으로 기존 Secret 을 지우면 VSO 가 다음 reconcile 에 새 값으로 재생성한다.
|
||||
|
||||
`refreshAfter: 1h` — Vault 값 변경 시 1 시간 내 K8s Secret 에 자동 반영.
|
||||
|
||||
## dockerconfigjson `.auth` 필드
|
||||
|
||||
Docker 공식 config 스키마는 `.auth = base64("<username>:<password>")` 형태다. 기존에는 password 만 base64 하던 버그가 있었고 현재는 다음으로 수정되어 있다:
|
||||
|
||||
```
|
||||
{{ printf "%s:%s" username password | b64enc }}
|
||||
```
|
||||
|
||||
Docker daemon 이 Registry 에 로그인할 때 이 필드를 디코드하므로 정확한 포맷이 필수.
|
||||
|
||||
## VaultConnection address
|
||||
|
||||
base 는 `http://vault:8200` (짧은 이름) 만 둔다. VSO Operator Pod 가 같은 `mnt` namespace 에 있으면 Kubernetes DNS 가 짧은 이름을 해결한다. 다른 namespace 에서 운영할 때는 overlay 에서 FQDN(`http://vault.mnt.svc.cluster.local:8200`) 으로 patch.
|
||||
과거 Git 이력에 포함된 값은 삭제만으로 복구되지 않습니다. live credential
|
||||
회전과 원격 history 정리는 별도 incident response로 수행합니다.
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
# gitops AGENTS
|
||||
|
||||
Role:
|
||||
- own Kubernetes/K3s source-of-truth manifests and Kustomize composition
|
||||
- keep catalog bases, environment overlays, cluster entrypoints, and Vault Secrets Operator assets separate
|
||||
- make render / validate / diff / apply possible from Git without relying on live cluster state
|
||||
|
||||
Scope:
|
||||
- `apps/`: app-owned workload/data/operation catalogs
|
||||
- `platform/`: shared services and operator catalogs
|
||||
- `policies/`: security and governance catalogs
|
||||
- `tenants/`: namespace/RBAC/quota catalogs
|
||||
- `clusters/<env>/<cluster>/namespaces` and `stages/`: deployable environment composition
|
||||
- `clusters/<env>/<cluster>/all/`: render/audit aggregate only
|
||||
|
||||
Read first:
|
||||
- `/docs/standards/infra/architecture-environments.md`
|
||||
- `/docs/standards/infra/kustomize.md`
|
||||
- `/docs/standards/infra/k3s-specific.md`
|
||||
- `/docs/standards/infra/operations-runbook-upgrade-rollback.md`
|
||||
|
||||
Rules:
|
||||
- prefer `kubectl kustomize`, `kubectl diff -k`, and `kubectl apply -k`
|
||||
- apply stage entrypoints only; never apply an environment `all/` aggregate
|
||||
- do not use server-local manifests as source of truth
|
||||
- do not place environment differences in a catalog `base/`
|
||||
- do not place base resource definitions directly in overlays unless the resource is environment-only by design
|
||||
- do not commit production secret values
|
||||
- keep scripts as helpers; manifests remain declarative source
|
||||
|
||||
Catalog base contract:
|
||||
- shared labels, selectors, probes, resources, services, storage and policy shape are allowed
|
||||
- environment hostnames, cluster contexts, production-only sizing, rollout image
|
||||
references, deletion overrides and secret values are forbidden
|
||||
- every base must be reusable by lab, staging and prod overlays
|
||||
- ownership must remain visible in the catalog unit path
|
||||
@@ -0,0 +1,20 @@
|
||||
# Project catalog ownership
|
||||
|
||||
| Catalog | Ownership |
|
||||
| --- | --- |
|
||||
| `apps/auth-server` | API workload와 lab ingress |
|
||||
| `apps/identity-postgres` | auth platform 전용 PostgreSQL |
|
||||
| `apps/auth-migration` | versioned Flyway migration Job |
|
||||
| `apps/keycloak-realm-import` | versioned realm/client import |
|
||||
| `platform/forward-auth` | oauth2-proxy와 Traefik ForwardAuth component |
|
||||
| `platform/keycloak` | Keycloak instance와 ingress |
|
||||
| `platform/minio` | MinIO Tenant |
|
||||
| `platform/registry` | Docker registry |
|
||||
| `platform/vault` | Vault workload |
|
||||
| `platform/secret-delivery` | VSO auth/connection, Helm values와 VaultStaticSecret |
|
||||
| `platform/*-operator` | cluster-scoped controller source와 environment patch |
|
||||
| `policies/baseline` | namespace 보안 baseline |
|
||||
| `tenants/mnt` | `mnt` namespace 경계 |
|
||||
|
||||
Catalog는 직접 controller root로 지정하지 않습니다. 실제 환경은
|
||||
`gitops/clusters/<...>` 아래 entrypoint에서 필요한 catalog만 선택합니다.
|
||||
@@ -0,0 +1,44 @@
|
||||
# Project GitOps layout
|
||||
|
||||
Project-specific Kubernetes resources are split into reusable catalogs and
|
||||
cluster entrypoints.
|
||||
|
||||
```text
|
||||
gitops/
|
||||
├── apps/
|
||||
│ ├── auth-server/
|
||||
│ ├── identity-postgres/
|
||||
│ ├── auth-migration/
|
||||
│ └── keycloak-realm-import/
|
||||
├── platform/
|
||||
│ ├── cert-manager/
|
||||
│ ├── forward-auth/
|
||||
│ ├── keycloak/
|
||||
│ ├── keycloak-operator/
|
||||
│ ├── minio/
|
||||
│ ├── registry/
|
||||
│ ├── secret-delivery/
|
||||
│ ├── traefik/
|
||||
│ └── vault/
|
||||
├── policies/baseline/
|
||||
├── tenants/mnt/
|
||||
└── clusters/lab/main/
|
||||
├── stages/
|
||||
└── all/
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
- Catalog `base`는 환경 중립 shape를 소유하고 `overlays/<env>`는 차이만
|
||||
표현합니다.
|
||||
- `clusters/lab/main/stages/*`만 현재 deployable entrypoint입니다.
|
||||
- `clusters/lab/main/all`은 전체 렌더와 정적 검사 전용이며 apply하지 않습니다.
|
||||
- operator readiness, Vault init, Secret 생성과 Job 완료는 stage 사이에서
|
||||
`scripts/bin/bootstrap.sh`가 확인합니다.
|
||||
- 새 deployable entrypoint는 `tests/kustomize-entrypoints.txt`에 추가합니다.
|
||||
|
||||
```bash
|
||||
kubectl kustomize gitops/clusters/lab/main/all >/tmp/project-infra-lab.yaml
|
||||
export KUBE_CONTEXT_LAB='<expected-context>'
|
||||
bash scripts/bin/bootstrap.sh lab
|
||||
```
|
||||
@@ -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,6 @@
|
||||
# gitops/apps AGENTS
|
||||
|
||||
Application-owned workloads, stateful dependencies, and independently runnable
|
||||
operations live here. Keep environment-neutral manifests in each unit's `base`.
|
||||
Ingress hosts, image rollout references, environment labels, storage classes,
|
||||
retention, and Secret delivery differences belong in `overlays/<env>`.
|
||||
@@ -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에 있다면 이곳에서 중복 생성하지 않습니다.
|
||||
Executable → Regular
+1
-2
@@ -1,5 +1,4 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
resources:
|
||||
- namespace
|
||||
resources: []
|
||||
@@ -0,0 +1,6 @@
|
||||
# Overlays
|
||||
|
||||
필요한 환경에만 overlay를 추가합니다. 각 overlay는 `../../base`를 참조하고
|
||||
환경별 patch만 포함합니다.
|
||||
|
||||
비밀값, 임시 debug 설정과 수동 hotfix 결과를 overlay에 커밋하지 않습니다.
|
||||
@@ -0,0 +1,5 @@
|
||||
# gitops/apps/auth-migration AGENTS
|
||||
|
||||
Jobs and CronJobs live here. Operations are versioned, independently runnable
|
||||
units and must not be hidden inside an application base. Define completion,
|
||||
retry, timeout, rollback, and rerun semantics before changing them.
|
||||
+3
-3
@@ -1,10 +1,10 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: migration-flyway-sql
|
||||
name: auth-server-migrate-sql
|
||||
labels:
|
||||
app.kubernetes.io/name: migration-flyway
|
||||
app.kubernetes.io/instance: migration-flyway
|
||||
app.kubernetes.io/name: auth-server
|
||||
app.kubernetes.io/instance: auth-server-migrate-0-1-0
|
||||
app.kubernetes.io/version: "0.1.0"
|
||||
app.kubernetes.io/component: migration
|
||||
app.kubernetes.io/part-of: auth-platform
|
||||
+7
-7
@@ -1,10 +1,10 @@
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: migration-flyway
|
||||
name: auth-server-migrate-0-1-0
|
||||
labels:
|
||||
app.kubernetes.io/name: migration-flyway
|
||||
app.kubernetes.io/instance: migration-flyway
|
||||
app.kubernetes.io/name: auth-server
|
||||
app.kubernetes.io/instance: auth-server-migrate-0-1-0
|
||||
app.kubernetes.io/version: "0.1.0"
|
||||
app.kubernetes.io/component: migration
|
||||
app.kubernetes.io/part-of: auth-platform
|
||||
@@ -18,14 +18,14 @@ spec:
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: migration-flyway
|
||||
app.kubernetes.io/instance: migration-flyway
|
||||
app.kubernetes.io/name: auth-server
|
||||
app.kubernetes.io/instance: auth-server-migrate-0-1-0
|
||||
app.kubernetes.io/version: "0.1.0"
|
||||
app.kubernetes.io/component: migration
|
||||
app.kubernetes.io/part-of: auth-platform
|
||||
app.kubernetes.io/managed-by: kustomize
|
||||
spec:
|
||||
serviceAccountName: migration-flyway-sa
|
||||
serviceAccountName: auth-server-migrate-sa
|
||||
automountServiceAccountToken: false
|
||||
restartPolicy: Never
|
||||
securityContext:
|
||||
@@ -108,7 +108,7 @@ spec:
|
||||
volumes:
|
||||
- name: sql
|
||||
configMap:
|
||||
name: migration-flyway-sql
|
||||
name: auth-server-migrate-sql
|
||||
- name: db-secret
|
||||
secret:
|
||||
secretName: auth-server-db
|
||||
+2
-2
@@ -6,8 +6,8 @@ resources:
|
||||
- job.yaml
|
||||
labels:
|
||||
- pairs:
|
||||
app.kubernetes.io/name: migration-flyway
|
||||
app.kubernetes.io/instance: migration-flyway
|
||||
app.kubernetes.io/name: auth-server
|
||||
app.kubernetes.io/instance: auth-server-migrate-0-1-0
|
||||
app.kubernetes.io/version: "0.1.0"
|
||||
app.kubernetes.io/component: migration
|
||||
app.kubernetes.io/part-of: auth-platform
|
||||
+3
-3
@@ -1,10 +1,10 @@
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: migration-flyway-sa
|
||||
name: auth-server-migrate-sa
|
||||
labels:
|
||||
app.kubernetes.io/name: migration-flyway
|
||||
app.kubernetes.io/instance: migration-flyway
|
||||
app.kubernetes.io/name: auth-server
|
||||
app.kubernetes.io/instance: auth-server-migrate-0-1-0
|
||||
app.kubernetes.io/version: "0.1.0"
|
||||
app.kubernetes.io/component: migration
|
||||
app.kubernetes.io/part-of: auth-platform
|
||||
+1
-4
@@ -4,8 +4,5 @@ kind: Kustomization
|
||||
namespace: mnt
|
||||
|
||||
resources:
|
||||
- ../../../base/plugins/docker-registry
|
||||
- vault-secrets.yaml
|
||||
- middleware.yaml
|
||||
- ingress-public.yaml
|
||||
- ../../base
|
||||
- networkpolicy.yaml
|
||||
@@ -0,0 +1,26 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: auth-server-migrate-egress-postgres
|
||||
labels:
|
||||
app.kubernetes.io/name: auth-server
|
||||
app.kubernetes.io/instance: auth-server-migrate-0-1-0
|
||||
app.kubernetes.io/component: migration
|
||||
app.kubernetes.io/part-of: auth-platform
|
||||
app.kubernetes.io/managed-by: kustomize
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: auth-server
|
||||
app.kubernetes.io/instance: auth-server-migrate-0-1-0
|
||||
policyTypes:
|
||||
- Egress
|
||||
egress:
|
||||
- to:
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: identity-postgres
|
||||
app.kubernetes.io/instance: identity-postgres
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 5432
|
||||
+1
-11
@@ -4,8 +4,7 @@ kind: Kustomization
|
||||
namespace: mnt
|
||||
|
||||
resources:
|
||||
- ../../../base/app/identity/auth/stateless/auth-server
|
||||
- ../../../base/managing/migration-flyway
|
||||
- ../../base
|
||||
- ingress.yaml
|
||||
- networkpolicy.yaml
|
||||
|
||||
@@ -39,12 +38,3 @@ patches:
|
||||
path: /imagePullSecrets
|
||||
value:
|
||||
- name: docker-registry-pull-credentials
|
||||
- target:
|
||||
kind: Job
|
||||
name: migration-flyway
|
||||
patch: |-
|
||||
- op: add
|
||||
path: /metadata/annotations
|
||||
value:
|
||||
argocd.argoproj.io/sync-wave: "-1"
|
||||
argocd.argoproj.io/hook: PreSync
|
||||
-27
@@ -61,30 +61,3 @@ spec:
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 8080
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: migration-flyway-egress
|
||||
labels:
|
||||
app.kubernetes.io/name: migration-flyway
|
||||
app.kubernetes.io/instance: migration-flyway
|
||||
app.kubernetes.io/component: migration
|
||||
app.kubernetes.io/part-of: auth-platform
|
||||
app.kubernetes.io/managed-by: kustomize
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: migration-flyway
|
||||
app.kubernetes.io/instance: migration-flyway
|
||||
policyTypes:
|
||||
- Egress
|
||||
egress:
|
||||
- to:
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: identity-postgres
|
||||
app.kubernetes.io/instance: identity-postgres
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 5432
|
||||
@@ -0,0 +1,5 @@
|
||||
# gitops/apps/identity-postgres AGENTS
|
||||
|
||||
Stateful data services live here. Read the storage, backup, security, and
|
||||
component standard before changing a StatefulSet, Tenant, PVC, or retention
|
||||
policy. Environment storage classes and deletion behavior belong in overlays.
|
||||
+1
-1
@@ -167,7 +167,7 @@ spec:
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
storageClassName: local-path
|
||||
storageClassName: replace-in-overlay
|
||||
volumeMode: Filesystem
|
||||
resources:
|
||||
requests:
|
||||
+4
-2
@@ -4,8 +4,7 @@ kind: Kustomization
|
||||
namespace: mnt
|
||||
|
||||
resources:
|
||||
- ../../../base/app/identity/auth/stateful/identity-postgres
|
||||
- vault-secrets.yaml
|
||||
- ../../base
|
||||
- networkpolicy.yaml
|
||||
|
||||
patches:
|
||||
@@ -19,3 +18,6 @@ patches:
|
||||
- op: replace
|
||||
path: /spec/persistentVolumeClaimRetentionPolicy/whenScaled
|
||||
value: Delete
|
||||
- op: replace
|
||||
path: /spec/volumeClaimTemplates/0/spec/storageClassName
|
||||
value: local-path
|
||||
+2
-2
@@ -27,8 +27,8 @@ spec:
|
||||
app.kubernetes.io/instance: auth-server
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: migration-flyway
|
||||
app.kubernetes.io/instance: migration-flyway
|
||||
app.kubernetes.io/name: auth-server
|
||||
app.kubernetes.io/instance: auth-server-migrate-0-1-0
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 5432
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
apiVersion: k8s.keycloak.org/v2beta1
|
||||
kind: KeycloakRealmImport
|
||||
metadata:
|
||||
name: platform-realm
|
||||
name: platform-realm-v1
|
||||
labels:
|
||||
app.kubernetes.io/name: keycloak
|
||||
app.kubernetes.io/instance: keycloak
|
||||
@@ -1,4 +1,4 @@
|
||||
# k8s/overlays AGENTS
|
||||
# gitops/clusters AGENTS
|
||||
|
||||
Role:
|
||||
- own environment-specific Kustomize composition
|
||||
@@ -6,9 +6,8 @@ Role:
|
||||
- make environment-level render, diff, apply, audit, and GitOps sync straightforward
|
||||
|
||||
Scope:
|
||||
- `dev/`
|
||||
- `staging/`
|
||||
- `prod/`
|
||||
- `<environment>/<region-or-cluster>/stages/`
|
||||
- `<environment>/<region-or-cluster>/all/`
|
||||
|
||||
Allowed:
|
||||
- namespace selection
|
||||
@@ -37,8 +36,10 @@ Read first:
|
||||
- `/docs/standards/infra/operations-runbook-upgrade-rollback.md`
|
||||
|
||||
Rules:
|
||||
- overlays are environment-first by design
|
||||
- cluster entrypoints are environment-first by design
|
||||
- `stages/*` are deployable entrypoints; `all/` is validation-only
|
||||
- controller readiness and one-shot completion belong between stage applies
|
||||
- keep patches small and named by target and intent
|
||||
- render and diff the target environment before apply
|
||||
- prod overlays must be the most conservative environment
|
||||
- if an overlay starts re-declaring most of a resource, move common shape back into `k8s/base`
|
||||
- if an overlay starts re-declaring most of a resource, move common shape back into its catalog `base`
|
||||
@@ -0,0 +1,37 @@
|
||||
# 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`을 실제 경로로 복사해 시작합니다.
|
||||
|
||||
## Current lab exception
|
||||
|
||||
`lab/main`은 CRD, Vault, Secret과 one-shot operation의 readiness 경계 때문에
|
||||
단일 direct leaf 대신 `namespaces`와 ordered `stages/*` leaf를 사용합니다.
|
||||
현재는 `scripts/bin/bootstrap.sh`가 순서를 보장합니다. `all/`은 감사
|
||||
aggregate이므로 controller root로 지정하거나 apply하지 않습니다.
|
||||
@@ -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,22 @@
|
||||
# lab / main cluster entrypoints
|
||||
|
||||
현재 `lab`은 readiness 경계를 가진 push-based staged deployment를 사용합니다.
|
||||
|
||||
```text
|
||||
namespaces
|
||||
stages/
|
||||
├── 00-platform
|
||||
├── 10-vault
|
||||
├── 20-secrets
|
||||
├── 30-data
|
||||
├── 35-registry
|
||||
├── 40-operations
|
||||
└── 50-apps
|
||||
```
|
||||
|
||||
- `namespaces`와 각 stage는 독립적으로 렌더 가능한 cluster 배포 entrypoint입니다.
|
||||
- `scripts/bin/bootstrap.sh lab`만 준비 상태를 확인하며 순차 적용합니다.
|
||||
- `all/`은 모든 stage를 합친 감사용 Kustomization입니다. apply하거나 GitOps
|
||||
controller root로 연결하지 않습니다.
|
||||
- Argo CD를 채택하면 stage별 Application/sync-wave/health gate를, Flux를
|
||||
채택하면 Kustomization `dependsOn`/`healthChecks`를 먼저 설계한 뒤 전환합니다.
|
||||
@@ -0,0 +1,13 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
# Audit/render entrypoint only. Do not apply this aggregate: controllers and
|
||||
# one-shot operations have readiness boundaries that Kustomize cannot express.
|
||||
resources:
|
||||
- ../stages/00-platform
|
||||
- ../stages/10-vault
|
||||
- ../stages/20-secrets
|
||||
- ../stages/30-data
|
||||
- ../stages/35-registry
|
||||
- ../stages/40-operations
|
||||
- ../stages/50-apps
|
||||
@@ -0,0 +1,15 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
# Namespace creation is a separate cluster entrypoint because server-side
|
||||
# dry-run cannot create a Namespace and validate namespaced resources against
|
||||
# it in the same request.
|
||||
resources:
|
||||
- ../../../../tenants/mnt/base
|
||||
|
||||
labels:
|
||||
- pairs:
|
||||
example.com/environment: lab
|
||||
example.com/owner-team: platform
|
||||
includeSelectors: false
|
||||
includeTemplates: true
|
||||
@@ -0,0 +1,18 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
# Cluster prerequisites and the deny-by-default policy are applied before any
|
||||
# workload. This stage is intentionally free of application CRs.
|
||||
resources:
|
||||
- ../../../../../tenants/mnt/base
|
||||
- ../../../../../platform/cert-manager/overlays/lab
|
||||
- ../../../../../platform/keycloak-operator/overlays/lab
|
||||
- ../../../../../platform/traefik/overlays/lab
|
||||
- ../../../../../policies/baseline/overlays/lab
|
||||
|
||||
labels:
|
||||
- pairs:
|
||||
example.com/environment: lab
|
||||
example.com/owner-team: platform
|
||||
includeSelectors: false
|
||||
includeTemplates: true
|
||||
@@ -0,0 +1,12 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
resources:
|
||||
- ../../../../../platform/vault/overlays/lab
|
||||
|
||||
labels:
|
||||
- pairs:
|
||||
example.com/environment: lab
|
||||
example.com/owner-team: platform
|
||||
includeSelectors: false
|
||||
includeTemplates: true
|
||||
@@ -0,0 +1,16 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
# The VSO controller is installed by the bootstrap task before this stage.
|
||||
resources:
|
||||
- ../../../../../platform/secret-delivery/overlays/lab/operator
|
||||
- ../../../../../platform/secret-delivery/overlays/lab/identity
|
||||
- ../../../../../platform/secret-delivery/overlays/lab/storage
|
||||
- ../../../../../platform/secret-delivery/overlays/lab/apps
|
||||
|
||||
labels:
|
||||
- pairs:
|
||||
example.com/environment: lab
|
||||
example.com/owner-team: platform
|
||||
includeSelectors: false
|
||||
includeTemplates: true
|
||||
@@ -0,0 +1,14 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
resources:
|
||||
- ../../../../../apps/identity-postgres/overlays/lab
|
||||
- ../../../../../platform/minio/overlays/lab
|
||||
- ../../../../../platform/keycloak/overlays/lab
|
||||
|
||||
labels:
|
||||
- pairs:
|
||||
example.com/environment: lab
|
||||
example.com/owner-team: platform
|
||||
includeSelectors: false
|
||||
includeTemplates: true
|
||||
@@ -0,0 +1,13 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
resources:
|
||||
- ../../../../../platform/secret-delivery/overlays/lab/storage/registry-minio
|
||||
- ../../../../../platform/registry/overlays/lab
|
||||
|
||||
labels:
|
||||
- pairs:
|
||||
example.com/environment: lab
|
||||
example.com/owner-team: platform
|
||||
includeSelectors: false
|
||||
includeTemplates: true
|
||||
@@ -0,0 +1,14 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
# One-shot resources. bootstrap.sh waits for their prerequisites before apply.
|
||||
resources:
|
||||
- ../../../../../apps/auth-migration/overlays/lab
|
||||
- ../../../../../apps/keycloak-realm-import/overlays/lab
|
||||
|
||||
labels:
|
||||
- pairs:
|
||||
example.com/environment: lab
|
||||
example.com/owner-team: platform
|
||||
includeSelectors: false
|
||||
includeTemplates: true
|
||||
@@ -0,0 +1,13 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
resources:
|
||||
- ../../../../../apps/auth-server/overlays/lab
|
||||
- ../../../../../platform/forward-auth/overlays/lab
|
||||
|
||||
labels:
|
||||
- pairs:
|
||||
example.com/environment: lab
|
||||
example.com/owner-team: platform
|
||||
includeSelectors: false
|
||||
includeTemplates: true
|
||||
@@ -0,0 +1,6 @@
|
||||
# gitops/platform AGENTS
|
||||
|
||||
Shared platform services and environment-neutral operator sources live in
|
||||
unit-level `base` directories. Cluster-specific CIDRs, namespaces, ingress,
|
||||
service mesh, load balancer, and K3s packaged-component configuration belong
|
||||
in focused overlays selected by a cluster stage.
|
||||
@@ -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`을 복사해 시작합니다.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user