init: 폴더구조 설계 및 인프라 설계

This commit is contained in:
DongHyeonka
2026-07-24 14:31:53 +09:00
parent 34ad612281
commit f9c463f87a
1839 changed files with 323096 additions and 1 deletions
+275
View File
@@ -0,0 +1,275 @@
# STYLE.md — 인프라 문서 공용 규약 (Single Source of Truth)
이 문서는 `docs/standards/infra/**``docs/examples/infra/**` 에 등장하는 모든 라벨, 네이밍, 포트, 이미지, 리소스 관례의 **정규(normative)** 정의다. 다른 모든 문서의 YAML 조각은 예시이며, 여기 규약과 충돌할 경우 **이 문서가 우선한다.** AI 에이전트가 매니페스트를 생성할 때 관례가 문서 간 표류하는 것을 방지하려는 목적이다.
---
## 1. 라벨 (Labels)
### 1.1 Kubernetes well-known labels (`app.kubernetes.io/*`)
공식 well-known set. 이 namespace 아래에는 아래 6개 외에 임의 키를 **추가하지 않는다.**
| 키 | 의미 | 예시 |
| --- | --- | --- |
| `app.kubernetes.io/name` | 애플리케이션 이름 | `auth-server` |
| `app.kubernetes.io/instance` | 인스턴스 (환경/리전 포함 가능) | `auth-server-prod`, `auth-server` |
| `app.kubernetes.io/version` | semver 또는 release tag | `1.24.0` |
| `app.kubernetes.io/component` | 역할 | `api`, `worker`, `migration`, `database` |
| `app.kubernetes.io/part-of` | 상위 시스템 | `auth-platform` |
| `app.kubernetes.io/managed-by` | 배포 도구 | `kustomize`, `argocd`, `helm` |
### 1.2 조직 커스텀 라벨 (`example.com/*`)
`example.com/` namespace 는 문서 전용 플레이스홀더다. 실제 조직은 자사 도메인(e.g., `acme.corp/`)으로 치환한다.
| 키 | 허용 값 |
| --- | --- |
| `example.com/environment` | `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` |
| `example.com/tier` | `0` (critical) \| `1` \| `2` \| `3` (best-effort) |
### 1.3 규칙
1. 모든 워크로드(Deployment / StatefulSet / DaemonSet / Job / CronJob)에는 위 6개 `app.kubernetes.io/*` 라벨 + `example.com/environment` + `example.com/owner-team` 이 **필수**다.
2. `app.kubernetes.io/environment` 라벨은 **사용 금지**. 공식 well-known set 에 없으며, 환경 라벨은 조직 namespace 아래에 둔다.
3. Selector (`spec.selector.matchLabels`)에는 **`app.kubernetes.io/name``app.kubernetes.io/instance` 만** 사용한다. 이유: selector 는 immutable 이고, `version` / `component` 이외 라벨은 릴리즈마다 바뀌기 때문에 selector 에 포함하면 rollout 이 막힌다.
4. 라벨 값은 DNS-1123 subdomain 또는 label 규칙을 따른다: 소문자 알파벳, 숫자, `-`, `.`, 최대 63자. 공백/대문자/언더스코어 금지.
5. 라벨은 metadata 의 최상위 `labels:` 와 Pod template 의 `spec.template.metadata.labels:`**동일하게** 복제한다(선택자 일치 보장).
```yaml
metadata:
name: auth-server
labels:
app.kubernetes.io/name: auth-server
app.kubernetes.io/instance: auth-server-prod
app.kubernetes.io/version: "1.24.0"
app.kubernetes.io/component: api
app.kubernetes.io/part-of: auth-platform
app.kubernetes.io/managed-by: kustomize
example.com/environment: prod
example.com/owner-team: auth-platform
spec:
selector:
matchLabels:
app.kubernetes.io/name: auth-server
app.kubernetes.io/instance: auth-server-prod
```
---
## 2. 네이밍 (Naming)
### 2.1 Namespace
1. 기본 스키마: `<env>-<domain>-<service>` — 예: `prod-auth-keycloak`, `staging-billing-api`.
2. 단일 서비스 네임스페이스에 여러 컴포넌트가 있으면 서비스 이름까지만 사용한다: `prod-auth` 네임스페이스 안에 Keycloak, PostgreSQL, Flyway Job 이 공존.
3. `default` 네임스페이스는 **금지**. `kube-*` 는 Kubernetes 예약.
4. 클러스터 공통 플랫폼 컴포넌트는 별도 접두어: `platform-vault`, `platform-cert-manager`, `platform-monitoring`.
### 2.2 리소스 이름
케밥-케이스, 소문자. Service / ServiceAccount / Secret / ConfigMap 이름은 관련 워크로드 이름을 접두어로 공유한다.
| 리소스 | 규약 | 예시 |
| --- | --- | --- |
| Deployment / StatefulSet | `<app>` | `auth-server` |
| Service (ClusterIP) | `<app>` (Deployment와 동일) | `auth-server` |
| Headless Service (StatefulSet peer 통신용) | `<app>-headless` (옆에 일반 ClusterIP `<app>` 병행) | `keycloak-headless`, `keycloak` |
| ServiceAccount | `<app>-sa` | `auth-server-sa` |
| Secret (앱 소유) | `<app>-<purpose>` | `auth-server-db`, `auth-server-oidc` |
| ConfigMap | `<app>-<purpose>` | `auth-server-config`, `auth-server-runtime` |
| PDB | `<app>-pdb` | `auth-server-pdb` |
| HPA | `<app>-hpa` | `auth-server-hpa` |
| NetworkPolicy | `<app>-<direction>-<peer>` | `auth-server-egress-db`, `auth-server-ingress-traefik` |
| Job (일회성) | `<app>-<action>-<timestamp-or-version>` | `auth-server-migrate-1-24-0` |
| CronJob | `<app>-<action>` | `auth-server-session-cleanup` |
---
## 3. 포트 (Ports)
### 3.1 이름 규약
모든 containerPort / servicePort 에는 `name` 필드가 **필수**다. 아래 이름은 예약어로 취급한다.
| name | 용도 | 관행 포트 |
| --- | --- | --- |
| `http` | HTTP 앱 트래픽 | 8080 |
| `https` | HTTPS 직접 종료 | 8443 |
| `grpc` | gRPC | 9090 또는 앱별 지정 |
| `metrics` | Prometheus scrape | 9090 (kube-prometheus 관행). 컴포넌트가 이미 9090 을 쓰면 9100 |
| `health` | 별도 헬스/관리 포트 | Keycloak Quarkus 관리 포트 9000 등 |
| `admin` | 관리 UI | 컴포넌트별 |
| `cluster` | 내부 peer / 레플리케이션 | Vault 8201, Postgres 5432, etcd 2380 |
### 3.2 규칙
1. `targetPort` 는 number 대신 **이름 참조**를 권장: `targetPort: http`. 이유: 컨테이너가 바인드 포트를 바꿔도 Service 쪽 조정이 필요 없다.
2. `metrics` 포트는 **외부 노출 금지**. ClusterIP 만 쓰며 NetworkPolicy 로 Prometheus 네임스페이스에서만 ingress 허용.
3. `health`, `admin` 포트는 Ingress 에 붙이지 않는다. NetworkPolicy 로 접근 대역을 제한한다.
Container ports 스탠자 (Deployment/Pod spec 내부):
```
ports:
- name: http
containerPort: 8080
protocol: TCP
- name: metrics
containerPort: 9090
protocol: TCP
- name: health
containerPort: 9000
protocol: TCP
```
Service 정의 (targetPort 는 이름 참조):
```yaml
apiVersion: v1
kind: Service
metadata:
name: auth-server
spec:
selector:
app.kubernetes.io/name: auth-server
app.kubernetes.io/instance: auth-server-prod
ports:
- name: http
port: 80
targetPort: http
```
---
## 4. 이미지 (Images)
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 이슈).
4. `imagePullPolicy`:
- digest 사용 시 `IfNotPresent` (이미지 콘텐츠는 immutable)
- 뮤터블 태그 사용 시 `Always`
5. 레지스트리: 조직 내부 미러가 우선한다. 예: `registry.example.com/<ns>/<app>`. Docker Hub 직접 pull 금지 (rate limit + 공급망 리스크).
6. SHA256 digest 로 pin 한 이미지는 CI 파이프라인에서 cosign 서명 검증(선택)과 `imagePullSecrets` digest 검증에 연결한다.
```yaml
containers:
- name: app
image: registry.example.com/auth-platform/auth-server@sha256:9f0b2c4d8e7a1b3c5d7e9f1a3b5c7d9e1f3a5b7c9d1e3f5a7b9c1d3e5f7a9b1c
imagePullPolicy: IfNotPresent
```
---
## 5. 리소스 (Resources)
### 5.1 필수 필드
1. 모든 컨테이너는 `resources.requests.cpu`, `resources.requests.memory`, `resources.limits.memory`**반드시** 설정한다.
2. `resources.limits.cpu` 는 **선택**이다. 레이턴시 민감 워크로드에만 설정한다. 이유: CFS throttling 으로 인한 p99 tail-latency 악화를 회피하려는 Tim Hockin / Google SRE 가이던스.
### 5.2 QoS 클래스
1. `Guaranteed` — latency-critical (Keycloak, Vault, Postgres 등): `requests == limits`, CPU limit 도 설정.
2. `Burstable` — 일반 stateless 앱: `requests < limits` 또는 CPU limit 생략.
3. `BestEffort`**금지**. requests/limits 를 생략한 워크로드는 PR 에서 블록.
### 5.3 기본 가이드라인 (1000-서비스 스케일 기준 출발점)
| 워크로드 | CPU req | Memory req / limit |
| --- | --- | --- |
| 일반 stateless API | 100m | 128256Mi |
| 무거운 JVM (Keycloak, Elasticsearch) | 500m1 | 12Gi (req == limit) |
| 배경 worker | 250m | 512Mi1Gi |
| 전환성(transient) Job (Flyway) | 100m | 128Mi |
실제 값은 부하 테스트 / VPA 권고 결과로 조정한다.
---
## 6. 보안 기준선 (Security baselines — 모든 Pod)
아래 블록은 **모든** Pod 의 최소 baseline 이다. 이걸 내린 설정은 security-hardening.md 의 예외 절차를 거쳐야 한다.
```yaml
spec:
securityContext:
runAsNonRoot: true
runAsUser: 10001 # 앱별 고정 UID, 루트(0) 금지
runAsGroup: 10001
fsGroup: 10001
seccompProfile:
type: RuntimeDefault
containers:
- name: app
image: registry.example.com/auth-platform/auth-server@sha256:...
imagePullPolicy: IfNotPresent
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: [ALL]
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
memory: 256Mi
```
---
## 7. PDB, Job, Deployment 기타
1. **PDB**: 1.27+ 에서 `spec.unhealthyPodEvictionPolicy: AlwaysAllow` **필수**. 기본값 `IfHealthyBudget` 은 노드 drain 중 복구 불가능한 Pod 가 evict 되지 못해 업그레이드가 멈추는 원인이 된다.
2. **Job / CronJob**:
- `spec.ttlSecondsAfterFinished: 86400` (24h) 기본. 민감 로그가 남는 경우 `3600` (1h).
- `spec.backoffLimit` 명시 (기본 6). 크리티컬 마이그레이션(Flyway)은 `0` 또는 `1` 로 줄여 재시도 폭주 방지.
- CronJob 은 `spec.concurrencyPolicy: Forbid` 를 기본값으로 둔다(중복 실행 금지).
3. **Deployment**:
- `spec.revisionHistoryLimit: 5` (기본 10 은 너무 많음 — etcd 부하).
- `spec.progressDeadlineSeconds: 600` 명시.
- `spec.strategy.rollingUpdate.maxUnavailable: 0` + `maxSurge: 25%` 가 안전한 기본.
```yaml
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: auth-server-pdb
spec:
minAvailable: 2
unhealthyPodEvictionPolicy: AlwaysAllow
selector:
matchLabels:
app.kubernetes.io/name: auth-server
app.kubernetes.io/instance: auth-server-prod
```
---
## 8. 문서 내 예시 규약
1. 모든 YAML 예시는 ```` ```yaml ```` 펜스로 감싼다. 다른 언어 펜스 금지.
2. 한 파일에 여러 리소스가 등장하면 `---` separator 를 **명시적으로** 추가한다.
3. 예시는 원칙적으로 `kubectl apply -f` 로 바로 적용 가능한 완전체여야 한다. 지면상 생략할 때는 주석으로 표기: `# ... (full spec omitted for brevity)`.
4. 나쁜 예시(안티패턴)는 반드시 `## 나쁜 예시`, `## ❌`, 또는 `## bad example` 헤더 아래에 둔다. CI 검증 스크립트가 이 헤더 규약으로 나쁜 예시 블록을 제외한다. 헤더 없이 안티패턴을 노출하면 검증기가 정당한 예시로 오인해 lint 규칙 위반을 일으킨다.
5. 네임스페이스, 이미지 레지스트리, 도메인 이름은 `example.com`, `registry.example.com` 플레이스홀더를 사용한다. 실제 조직 도메인은 overlays 에서만 등장한다.
---
## 검증 (Validation)
이 문서의 규약은 CI 에서 기계 검증된다.
- 실행: `k8s/scripts/ci/validate-docs.sh`
- Lint 설정 위치: `.kube-linter.yaml` (repo root)
- 목표 스코어:
- syntax 에러: **0**
- schema 에러: **0**
- lint warning: **≤ 5**
syntax 또는 schema 에러가 있으면 PR 은 머지 불가. lint warning 이 임계치를 넘으면 리뷰어가 수정 또는 예외 주석을 요구한다.
@@ -0,0 +1,297 @@
# infra architecture / environments 기준
## 목적
이 문서는 1000+ 서비스 규모의 K3s 기반 production 클러스터에서
- 환경을 어떻게 나눌지
- namespace / label / selector를 어떻게 고정할지
- K3s 기본 컴포넌트와 GitOps source of truth를 어떻게 구분할지
- cross-cluster / multi-region / DR(RPO·RTO)을 어떻게 문서화할지
를 먼저 고정한다.
이 문서의 목표:
- dev / staging / prod 환경 분리를 **label·namespace·selector 레벨에서** 일관되게 만든다
- 서비스별 리소스 소유권(팀·도메인·컴포넌트)을 label로 쿼리 가능하게 한다
- K3s packaged component와 사용자 AddOn을 혼동하지 않는다
- 멀티 서버에서 `manifests/` 디렉터리를 source-of-truth로 쓰는 사고를 원천 차단한다
- 이후 storage / secrets / ingress / workload / observability 표준의 전제 조건을 고정한다
## 공식 의미 (근거)
- Kubernetes well-known label set (공식, SIG-Apps 공인): `app.kubernetes.io/{name,instance,version,component,part-of,managed-by}` — 총 6개. `environment`는 포함되지 **않는다** (`https://kubernetes.io/docs/concepts/overview/working-with-objects/common-labels/`).
- `app.kubernetes.io/*` 외의 운영 차원(environment, team, tier, region 등)은 **자체 도메인 네임스페이스**(`example.com/*`)를 붙여 선언해야 한다.
- K3s는 `coredns`, `traefik`, `local-storage`, `metrics-server`를 packaged component로 제공한다.
- `/var/lib/rancher/k3s/server/manifests` 아래 파일은 서버 시작 시와 파일 변경 시 자동 적용된다(AddOn auto-deploy).
- packaged component manifest는 K3s가 재기록하므로 직접 수정 금지.
- 멀티 서버 K3s는 AddOn 파일을 자동 동기화하지 않는다.
- Kustomize v5+부터는 `labels:` 필드(기본 `includeSelectors: false`)가 `commonLabels`보다 안전한 기본이다. `commonLabels`는 항상 `selector.matchLabels`에 주입되며, Deployment/StatefulSet의 selector는 **immutable**이므로 운영 중 label 추가만으로 apply가 실패한다.
- GitOps 기본 apply 방식은 **Server-Side Apply** (`kubectl apply --server-side --field-manager=...`)다. CI/ArgoCD/Flux 모두 SSA 기본.
## 기본 규칙
### 1. 환경은 명시적으로 분리하고 **label + namespace 양쪽에** 박는다
기본 환경:
- `dev`
- `staging`
- `prod`
필요 시 `sandbox` / `canary` / `dr`을 추가할 수 있으나 dev/staging/prod 의미를 흐리지 않는다.
각 리소스는 두 곳에 동시에 환경이 드러나야 한다.
- `metadata.namespace` — 물리적 격리
- `metadata.labels["example.com/environment"]` — 쿼리·정책용 (well-known label에는 환경이 없으므로 **자체 도메인 label** 사용)
### 2. 환경 간 혼합 배포 전면 금지
하나의 namespace / hostname / PVC / Secret / TLS cert scope 안에서 서로 다른 환경 리소스가 섞이지 않는다.
금지:
- `auth-dev`, `auth-prod`가 같은 namespace 공유
- dev와 prod가 같은 ingress host (`auth.example.com`) 공유
- staging과 prod가 같은 PostgreSQL schema / S3 bucket / Vault mount 공유
- NetworkPolicy / ResourceQuota / LimitRange가 환경 경계를 걸치지 않음
### 3. namespace 전략은 “환경 prefix + 서비스 이름” 고정
1000+ 서비스 스케일에서 초기에 하나의 포맷을 박는다. 본 표준 권장은:
```
<env>-<domain>-<service>
```
예:
- `prod-identity-auth`
- `prod-identity-keycloak`
- `staging-identity-auth`
- `dev-identity-auth`
- `prod-platform-ingress-nginx`
- `prod-data-postgres-identity`
이유:
- `kubectl -n prod-*` 와일드카드 RBAC / 모니터링 쿼리가 쉬움
- `prod-` prefix로 PodSecurity admission (`pod-security.kubernetes.io/enforce=restricted`)을 one-shot으로 강제 가능
- `default` namespace는 production workload 배포 전면 금지
### 4. `app.kubernetes.io/*` 6종은 전 리소스 필수
모든 워크로드·서비스·ingress·PVC·ConfigMap·Secret에 아래 6개가 반드시 붙는다.
- `app.kubernetes.io/name` — 애플리케이션 이름 (예: `auth`)
- `app.kubernetes.io/instance` — 인스턴스 (예: `auth-prod`)
- `app.kubernetes.io/version` — semver 또는 image tag
- `app.kubernetes.io/component` — 역할 (예: `api`, `worker`, `database`)
- `app.kubernetes.io/part-of` — 상위 도메인 (예: `identity-platform`)
- `app.kubernetes.io/managed-by` — 관리 도구 (예: `kustomize`, `argocd`, `flux`)
### 5. 운영 차원 label은 **자체 도메인**으로 선언
well-known label 6종으로 표현되지 않는 축은 다음 키로 고정한다.
- `example.com/environment``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`
- `example.com/cost-center` — FinOps tag
- `example.com/slo-tier``tier-1|tier-2|tier-3`
금지:
- `app.kubernetes.io/environment` 사용 (well-known set에 없음)
- 도메인 없는 커스텀 키 (`environment: prod` 같은 top-level key)
### 6. selector에 들어가는 label은 **불변 3종만**
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/managed-by` (툴 교체 시 drift)
- `example.com/environment` (overlay에서 주입되면 selector immutable 위반)
### 7. K3s packaged component는 “기본 제공”일 뿐 “무조건 사용”이 아니다
다음 컴포넌트는 클러스터 bootstrap 초기에 유지/비활성 결정을 박는다.
- `traefik`
- `servicelb`
- `local-storage`
- `metrics-server`
- `coredns` (교체는 특수 케이스)
기본:
- 무엇을 끄는지 Git에 기록
- packaged manifest 직접 수정 금지 — `--disable` 플래그 또는 `HelmChartConfig`
- prod 1000-서비스 스케일에서는 traefik / servicelb 모두 disable 후 **ingress-nginx DaemonSet + MetalLB/외부 LB** 조합이 일반적
### 8. `/var/lib/rancher/k3s/server/manifests`는 source of truth 아님
이 디렉터리는 AddOn auto-deploy 경로다.
멀티 서버 환경에서 자동 동기화가 **안 되므로**, Git이 source of truth고 이 디렉터리는 apply sink에 지나지 않는다.
기본:
- Git repo의 `k8s/` 디렉터리가 SoT
- CI/ArgoCD/Flux가 `kubectl apply --server-side`로 push
- 서버별 scp / vim 절대 금지
- 멀티 서버 bootstrap AddOn도 Git 관리(예: `k8s/bootstrap/*`를 첫 서버에만 배치)
### 9. GitOps apply는 Server-Side Apply가 기본
```
kubectl apply --server-side --field-manager=<ci-id> -k <overlay>
kubectl diff --server-side -k <overlay>
```
이유:
- multi-controller 환경(ArgoCD + HPA + VPA + operator)에서 ownership 충돌을 `managedFields`로 명시적 해결
- `last-applied-configuration` annotation 2MB 한계 회피
- 3-way merge 실패로 인한 silent drift 제거
### 10. base는 환경 중립, overlay는 환경 차이만
이후 `kustomize.md`에서 상세히 다룬다. 이 문서에서는 원칙만 박는다.
- `k8s/base/` — 공통 shape, 환경-agnostic
- `k8s/overlays/{dev,staging,prod}/` — patches / images / replicas / resources / labels
overlay는 base를 재작성하지 않는다. overlay diff가 100줄을 넘으면 base 설계 실패 신호다.
### 11. `app/managing/plugins` 책임 분리
`k8s/base/` 하위는 다음 3축으로 고정한다.
- `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)
이 축은 **소유 팀이 다르다**는 가정 위에 있다. 각 축은 독립된 Git owner (CODEOWNERS)를 가진다.
### 12. 상태 저장 / 외부 공개 범위를 architecture 단계에서 분류
모든 서비스는 아래 2축으로 초기 분류한다.
| 축 | 값 |
|-----------------|-----------------------------------------------------------------|
| workload 성격 | `stateless` / `stateful` / `job` / `cronjob` / `daemonset` |
| 공개 범위 | `public` / `internal-only` / `operator-only` / `cluster-only` |
예:
- `auth-server` — stateless / public
- `keycloak` — stateless(앱) + stateful(외부 DB) / public (관리 포트는 internal-only)
- `vault` — stateful / operator-only (+ cluster-only service endpoint)
- `minio-tenant` — stateful / internal-only
- `postgres-identity` — stateful / cluster-only
- `fluent-bit` — daemonset / cluster-only
- `flyway-migrate` — job / cluster-only
### 13. SLO·RPO·RTO를 환경 문서에서 먼저 박는다
환경 분리가 의미 있으려면 각 환경의 목표를 숫자로 고정해야 한다. 서비스 tier별로 아래 항목을 환경 문서에서 표로 둔다.
| tier | availability SLO | RPO | RTO | backup 주기 | multi-AZ | PDB minAvailable |
|--------|------------------|------|------|-------------|----------|------------------|
| tier-1 | 99.95% | 5m | 15m | 15m | required | 50% |
| tier-2 | 99.9% | 1h | 1h | 1h | required | 1 |
| tier-3 | 99.5% | 24h | 4h | 24h | optional | 0 |
tier는 `example.com/slo-tier` label로 리소스마다 붙는다.
### 14. 멀티 서버 K3s는 critical config를 Git에서 통일
K3s multi-server에서는 아래가 모든 서버에서 동일해야 한다(불일치 시 `critical configuration value mismatch`로 join 실패).
- `cluster-cidr`, `service-cidr`, `cluster-dns`, `cluster-domain`
- `disable` 플래그 세트
- `flannel-backend` / CNI 관련
- `embedded-registry` 활성화 여부
기본:
- `/etc/rancher/k3s/config.yaml` Git 관리
- 서버별 ad-hoc 수정 금지
- 신규 서버 조인 전 `config.yaml` diff 확인
### 15. 공개 범위별 ingress host 패턴 고정
- public: `<service>.example.com`
- internal: `<service>.internal.example.com`
- operator: `<service>.ops.example.com` (mTLS + SSO 필수)
- cluster-only: ingress 없음, ClusterIP + NetworkPolicy로만 접근
### 16. 네이밍 규칙 정리 (요약)
- namespace: `<env>-<domain>-<service>`
- Deployment/StatefulSet 이름: `<service>` (namespace로 환경 구분, 이름에 env 중복 금지)
- Service 이름: Deployment 이름과 동일 (headless면 `-headless` suffix)
- PVC 이름: `<service>-<purpose>-<ordinal>` (StatefulSet volumeClaimTemplate은 자동)
- Kustomize overlay 디렉터리: `overlays/<env>/<region>/` (멀티 region 시)
## 추천 디렉터리 구조
```text
k8s/
base/
app/
shared/
units/
identity/
auth/
kustomization.yaml
keycloak/
kustomization.yaml
data/
postgres-identity/
kustomization.yaml
managing/
flyway-migrate-identity/
backup-postgres/
plugins/
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/
scripts/
render.sh
diff.sh
apply.sh
```
## 프로젝트 기준 요약
- 환경 3종(`dev`/`staging`/`prod`) + namespace prefix 고정
- well-known `app.kubernetes.io/*` 6개 + 자체 도메인 운영 label 필수
- `app.kubernetes.io/environment` 사용 금지, `example.com/environment`로 대체
- selector에는 불변 3종만
- K3s packaged component는 초기에 disable 여부 결정, 직접 수정 금지
- `manifests/`는 apply sink, Git이 SoT
- `kubectl apply --server-side` GitOps 기본
- SLO / RPO / RTO 표가 환경 문서의 일부
+241
View File
@@ -0,0 +1,241 @@
# backup / restore 기준
## 목적
이 문서는 K3s 및 일반 Kubernetes 인프라에서
- 무엇을 백업해야 하는지
- 어떤 도구/방식으로 백업할지 (Velero / CSI snapshot / pgBackRest / WAL-G / CNPG Barman)
- RPO / RTO를 어떻게 선언할지
- 복구 단위와 절차를 어떻게 표준화할지
- restore drill을 어떻게 운영할지
를 먼저 고정한다.
이 문서의 목표는 다음과 같다.
- "PVC가 있으니 백업도 된 것"이라는 착각을 제거한다
- "K3s etcd snapshot이 있으니 DB/PVC도 복구된다"는 오해를 제거한다
- 선언형 원본, 제어 평면, 상태 저장소를 서로 다른 백업 대상으로 구분한다
- 컴포넌트별 복구 전략과 도구를 먼저 정하고 YAML을 쓰게 한다
- 실제 장애 시 복구 절차를 재현 가능하게 만든다
## 공식 의미
- K3s etcd snapshot은 **클러스터 API 상태**(namespaces, secrets encryption key, RBAC, CRD instance 등)만 백업한다. **PVC의 데이터 내용은 백업하지 않는다.**
- K3s snapshot에는 cluster CA 인증서/개인키와 secrets encryption 관련 데이터가 포함될 수 있다.
- 새 호스트로 K3s snapshot을 복구할 때는 snapshot 당시 사용한 server token이 필요하다.
- Velero는 CNCF 표준 K8s 백업 도구로 `Backup` / `Schedule` / `Restore` CRD와 object storage 백엔드(S3 / MinIO / GCS / Azure Blob)를 사용한다.
- Velero file-level backup: File System Backup (FSB, kopia/restic) — 모든 CSI/비-CSI 볼륨의 파일 내용을 복제.
- Velero volume-level backup: CSI snapshot — CSI driver가 지원하는 경우 블록 수준 snapshot.
- VolumeSnapshotClass의 `deletionPolicy: Retain`이면 VolumeSnapshot 삭제 후에도 VolumeSnapshotContent(클라우드 snapshot)는 남는다.
- PostgreSQL의 기본 physical backup 도구로는 `pg_basebackup`(standalone) 외에 **pgBackRest** 또는 **WAL-G**가 사실상 표준이다. CloudNativePG operator는 내장으로 **Barman Cloud**를 사용한다.
- `pg_dump`는 logical export이며 정기 production 전체 백업의 기본 도구로는 보통 적합하지 않다.
- MinIO `mc mirror`는 현재 객체만 동기화하며 버전 이력/전체 메타데이터 보존에는 적합하지 않다.
- MinIO bucket replication은 versioning을 전제로 하고, DR 상황에서 `resync`를 지원한다.
## RPO / RTO 선언
모든 백업 대상은 아래 세 줄을 runbook에 먼저 적는다.
- **RPO (Recovery Point Objective)**: 허용 가능한 데이터 손실 시간
- **RTO (Recovery Time Objective)**: 허용 가능한 복구 시간
- **Retention**: 백업 보존 기간
이 세 값이 비어 있으면 도구/스케줄을 선택할 수 없다.
기본 tier 예시:
| Tier | RPO | RTO | Retention | 대표 도구 |
|---|---|---|---|---|
| gold | 5분 | 30분 | 30일 | CNPG continuous WAL + CSI snap 매일 |
| silver | 1시간 | 2시간 | 14일 | Velero hourly + CSI snap |
| bronze | 24시간 | 24시간 | 90일 | Velero daily FSB |
| archive | 24시간 | 72시간 | 7년 | Velero weekly → Glacier / cold bucket |
## 기본 규칙
### 1. 백업 대상은 세 층으로 분리
1. **선언형 원본 (Git)**: Kustomize base/overlay, Helm values, Argo CD Application, 운영 문서/runbook, 스크립트
2. **제어 평면 (K8s API state)**: K3s etcd snapshot / Velero API object backup
3. **상태 저장 데이터 (data plane)**: PVC / VolumeSnapshot, PostgreSQL 물리 백업, Vault raft snapshot, MinIO object data
이 셋을 하나의 방식으로 뭉뚱그리지 않는다. 특히 **K3s etcd snapshot은 (3)을 커버하지 않는다.**
### 2. Git은 배포 원본 백업이지 런타임 상태 백업이 아니다
Git/Kustomize는 source of truth지만 runtime DB state, Vault secret state, MinIO object data, K3s cluster membership state를 복원해 주지 않는다. Git 백업만으로 운영 복구가 된다고 판단하지 않는다.
### 3. K3s etcd snapshot은 제어 평면 전용 백업
K3s etcd snapshot은 API server의 선언 상태만 백업한다. PVC 안의 파일 내용은 포함하지 않는다.
기본:
- scheduled etcd snapshot 사용 (예: 6시간마다)
- local retention + S3/off-node retention 병행
- snapshot에는 secrets encryption key와 CA private key가 포함될 수 있으므로 민감 정보로 취급
- 저장 위치 암호화, 접근 통제, 보존 기간 통제, chain of custody 확인
- 새 호스트 복구용 server token을 별도 위치에 보관 (같은 곳에 두면 동시 유출 위험)
### 4. 상태 저장 데이터 백업은 Velero가 표준
Kubernetes 수준에서 PVC / 네임스페이스 / CRD를 함께 백업/복구하려면 **Velero**를 기본 도구로 둔다.
기본 구성:
- `BackupStorageLocation`: off-cluster S3 또는 cluster 외부 MinIO (같은 cluster 안 MinIO에 백업하지 않는다)
- `VolumeSnapshotLocation`: CSI driver 대응
- `Schedule` CRD로 cron 기반 정기 백업
- selector(`labelSelector`, `includedNamespaces`)로 tier별 스케줄 분리
- TTL로 retention 관리
### 5. Velero FSB(kopia/restic) vs CSI snapshot 선택
- **CSI snapshot**: 볼륨 수준 crash-consistent, 빠름, CSI driver 지원 필요. DB처럼 큰 볼륨에 적합. 클라우드 snapshot cost 고려.
- **File System Backup (FSB, kopia/restic)**: 파일 수준, 모든 볼륨에서 동작, 암호화/중복제거, 느림. local-path / hostPath / 비-CSI 볼륨에 적합.
운영 기본:
- CSI snapshot이 가능한 볼륨은 CSI snapshot 우선
- 크지 않은 설정/아카이브 볼륨은 FSB 허용
- DB 볼륨은 CSI snapshot이어도 app-consistent hook(pre/post backup) 필요
### 6. 오프-클러스터 백업 저장소 필수
백업을 **같은 K8s 클러스터 안**의 MinIO/S3에 두지 않는다. cluster 장애 = 백업 동시 소실이다.
기본:
- 별도 리전/별도 account의 S3-호환 object storage
- bucket versioning 활성화
- object-lock / WORM (규제 필요 시)
- 접근은 IRSA / Workload Identity / 최소권한 IAM
### 7. VolumeSnapshotClass `deletionPolicy`는 정책에 맞춘다
운영 gold tier 데이터의 VolumeSnapshotClass는 `deletionPolicy: Retain`을 기본으로 둔다.
이렇게 하면 K8s에서 VolumeSnapshot object가 지워져도 CSI driver의 실제 snapshot(VolumeSnapshotContent)은 남아서 사고 복구 여지를 준다.
### 8. PostgreSQL은 logical / physical / continuous를 구분
- `pg_dump` — logical export. 선택적 export, schema 비교, 마이그레이션 준비용. 프로덕션 전체 복구 기본값으로 두지 않는다.
- `pg_basebackup` — standalone base backup. 소규모/단순 케이스에 적합하지만 WAL archiving을 직접 구성해야 한다.
- **pgBackRest / WAL-G** — 프로덕션 표준. incremental / differential backup, parallel restore, retention, PITR, S3 업로드를 내장.
- **CloudNativePG (CNPG)** — K8s-native Postgres operator. 내장 **Barman Cloud**로 object storage에 WAL + base backup을 지속 업로드. `Backup` / `ScheduledBackup` CRD 제공.
### 9. K8s 위 Postgres 운영 기본은 CloudNativePG
2026 기준 K8s 상에서 Postgres를 운영한다면 **CloudNativePG (CNPG)**를 기본 후보로 둔다 (CNCF sandbox).
이유:
- `Cluster` CRD로 primary + standby 자동 관리, failover, rolling upgrade
- `backup` 섹션에서 Barman Cloud 기반 continuous archiving을 선언만 하면 동작
- `Backup` (on-demand), `ScheduledBackup` (cron), PITR restore가 `Cluster.spec.bootstrap.recovery`로 표준화
- Prometheus `PodMonitor` 내장
대안:
- **Zalando postgres-operator** — 오래된 생태계, Spilo 기반
- **Crunchy PGO** — 상용 지원 강점, pgBackRest 내장
manual StatefulSet + sidecar는 1000개 서비스 규모에서는 권장하지 않는다.
### 10. Keycloak DB는 애플리케이션과 분리된 DB 전략을 따른다
Keycloak이 외부 PostgreSQL을 사용하면 Keycloak 복구는 애플리케이션 Pod 복구보다 DB 백업 전략에 크게 의존한다. Keycloak server manifest만 백업해서는 충분하지 않다.
### 11. Vault는 storage mode에 따라 백업 방식을 다르게 본다
- integrated storage (raft) → `vault operator raft snapshot save` 기본
- external storage (Consul 등) → 해당 백엔드 백업 전략
- dev mode → 운영 대상 아님
Vault snapshot 복구 테스트는 격리된 네트워크/환경에서 수행한다 (live credential revoke, 원치 않는 cluster 간 통신, 데이터 일관성 훼손 방지).
### 12. MinIO는 PVC snapshot만으로 충분하다고 보지 않는다
object store는 단순 PV 파일 복사 관점보다 object versioning / replication / resync 포함 전략으로 본다.
기본:
- bucket versioning enabled
- 소스/대상 cluster replication configured
- DR 시 `mc replicate resync` 절차 문서화
- `mc mirror`는 현재 객체 동기화 용도로만 제한 (버전 이력 보존 안 됨)
- 스토리지 layer snapshot은 보조 수단
### 13. stateless workload는 데이터보다 재현성을 백업
다음은 기본적으로 런타임 파일 백업 대상이 아니다.
- auth-server, ingress-controller, stateless test-server
- 외부 DB 사용 Keycloak 서버 자체
복구 핵심:
- Git / Kustomize / Helm values
- Config / Secret source (Vault Secrets Operator 기준)
- 이미지 digest
- 운영 문서
### 14. migration-flyway는 산출물이 아닌 migration source를 백업
Flyway Job 자체나 container 파일시스템/PVC는 backup 대상이 아니다. 중요한 것은:
- migration script (Git)
- migration ordering + schema history table 상태 (DB 백업으로 포함)
- Flyway 실행 이력 (CI/CD 로그, Argo Rollout 기록)
### 15. 모든 백업은 "주기 + 보존기간 + 저장 위치 + 암호화 + 무결성 검증 + 복구 테스트"를 갖춘다
파일만 남기고 정책이 없는 것을 백업 전략으로 보지 않는다. 최소 메타데이터:
- Schedule cron 또는 RPO
- Retention TTL
- 저장 위치 (버킷, prefix, 리전)
- 암호화 방식 (SSE-S3 / SSE-KMS / client-side)
- 무결성 검증 (checksum, Velero `backup describe`의 errors)
- 복구 테스트 cadence + 마지막 성공 일자
### 16. restore drill은 표준 운영 절차
복구 가능한지 확인하지 않은 백업은 신뢰하지 않는다.
기본 cadence:
- 제어 평면 (K3s etcd / Velero): 분기별 1회
- DB physical restore + PITR: 월 1회
- Vault raft restore: 분기별 1회
- MinIO replication resync: 반기별 1회
기록 항목:
- 실행 일자
- 실행자
- 대상 snapshot/backup ID
- 실제 RTO / 확인된 RPO
- 발견된 issue
- 다음 drill 예정일
최근 90일 내 성공 기록이 없는 백업은 "신뢰할 수 있는 백업"으로 보지 않는다.
### 17. 복구 단위는 컴포넌트별로 다르게 정의
| 컴포넌트 | 복구 단위 | 기본 도구 |
|---|---|---|
| K3s control plane | cluster snapshot | k3s etcd-snapshot |
| K8s API object (namespace 단위) | Velero Backup | Velero |
| PostgreSQL cluster | DB cluster 전체 + PITR | CNPG Backup / pgBackRest |
| PostgreSQL single database | logical dump | `pg_dump` (보조) |
| Vault | raft snapshot | `vault operator raft snapshot` |
| MinIO | bucket / object / site | mc replication + resync |
| stateless apps | namespace/service redeploy | Argo CD + Git |
| PVC 일반 | VolumeSnapshot / Velero FSB | Velero + CSI |
모든 것을 "서비스 단위" 또는 "PVC 단위" 하나로만 보지 않는다.
### 18. 백업 구성은 Git으로 관리되고 GitOps sync된다
Velero `Schedule`, `BackupStorageLocation`, `VolumeSnapshotClass`, CNPG `ScheduledBackup`은 Argo CD / Flux로 동기화한다. kubectl 수동 편집 금지.
### 19. 백업과 복구는 다른 문서와 연결
다음 문서와 항상 연결한다.
- `storage-pvc.md` (PVC tier ↔ snapshot class)
- `db-and-migration.md` (DB 복구 전략)
- `operations-runbook-upgrade-rollback.md` (장애 시 절차)
- `config-and-secrets.md` (Vault 백업)
## 현재 스택 기본 권장안
- **K3s control plane**: etcd scheduled snapshot (6h) + S3 off-node 보관, server token 별도 안전 보관
- **PostgreSQL**: CloudNativePG + Barman Cloud (continuous WAL + daily base backup), RPO 5분
- **Vault**: integrated storage → raft snapshot 매일, 격리 환경에서 분기 1회 drill
- **MinIO**: bucket versioning + 별도 region으로 replication, mc resync runbook
- **PVC 일반**: Velero Schedule (tier별 분리) + CSI VolumeSnapshot
- **auth-server / ingress-controller / stateless**: Git + Argo CD 재현
- **migration-flyway**: migration source는 Git, DB 상태는 CNPG 백업에 포함
## 프로젝트 기준 요약
- 백업 대상은 선언형 원본 / 제어 평면 / 상태 저장 데이터로 분리
- K3s etcd snapshot은 PVC 데이터를 포함하지 않음 — 별도 Velero 필수
- Velero를 Kubernetes 백업 표준으로, off-cluster 저장소에 보관
- PostgreSQL은 CNPG + Barman Cloud (또는 pgBackRest / WAL-G) — `pg_dump`는 보조
- VolumeSnapshotClass `deletionPolicy`를 tier에 맞게 (운영은 Retain)
- RPO/RTO/Retention을 tier로 선언
- restore drill은 분기/월 단위 표준 cadence + 최근 성공 일자 기록
- 백업 구성은 GitOps로 관리
+223
View File
@@ -0,0 +1,223 @@
# config / secrets 기준
## 목적
이 문서는 1000+ 서비스 운영 클러스터에서
- 무엇을 ConfigMap에 두고 무엇을 Secret/Vault에 두는지
- 민감정보를 어떤 경로로 Pod에 주입하는지 (VSO / ESO / CSI / SealedSecrets / SOPS)
- Kubernetes Secret at-rest encryption을 어떻게 구성하는지
- Image registry credential은 어떻게 다루는지
를 단일 ground truth로 고정한다.
이 문서의 목표는 다음과 같다.
- 민감정보가 manifest/Git/image/log 어디에도 새지 않는다
- Vault를 single source of truth로 두고 K8s Secret은 **파생 산출물**로만 존재
- 주입 방식(envFrom/volume/CSI)과 source(VSO/ESO/Vault Injector)를 표준화
- GitOps와 비밀 관리를 구조적으로 분리
## 공식 의미 (근거)
- ConfigMap은 **비기밀** 데이터 저장용 API object. 최대 1MiB.
- Secret은 민감정보용 object. **data는 base64 encoded(암호화 아님)**. stringData는 생성 시 자동 base64.
- Secret은 기본적으로 etcd에 평문 저장(base64 decode가 암호화가 아님). Kubernetes는 **at-rest encryption을 운영에서 필수**로 권장.
- Secret 타입: `Opaque`, `kubernetes.io/tls`, `kubernetes.io/dockerconfigjson`, `kubernetes.io/service-account-token`, `bootstrap.kubernetes.io/token`, `kubernetes.io/basic-auth`, `kubernetes.io/ssh-auth`.
- **Vault Secrets Operator(VSO)**: Vault의 secret(KV v2, dynamic DB, PKI, AWS 등)을 Kubernetes Secret으로 sync하는 controller. CRD: `VaultConnection`, `VaultAuth`, `VaultStaticSecret`, `VaultDynamicSecret`, `VaultPKISecret`, `HCPVaultSecretsApp`. 앱은 그냥 K8s Secret을 `envFrom`/`volumeMounts`로 소비.
- **Vault Agent Injector**: Mutating webhook이 Pod에 sidecar/init container를 주입해 tmpfs에 비밀을 렌더링. K8s Secret을 **만들지 않는다**(Vault → file).
- **External Secrets Operator(ESO)**: AWS Secrets Manager, GCP Secret Manager, Azure Key Vault, Vault 등 **외부 provider → K8s Secret sync**. VSO와 유사하지만 멀티 provider.
- **CSI Secret Store Driver**: volume으로만 마운트(K8s Secret 미생성, optionally mirror). Azure Key Vault, AWS Secrets Manager, GCP Secret Manager, Vault provider 존재.
- **Sealed Secrets (Bitnami)**: public key로 암호화된 `SealedSecret` CRD를 Git에 커밋 → controller가 cluster private key로 복호화해 K8s Secret 생성. GitOps 친화적.
- **SOPS**: 파일 수준 암호화(age/GPG/KMS) + kustomize/Helm/Flux plugin. Git 커밋 가능.
- `EncryptionConfiguration`은 API Server `--encryption-provider-config` 플래그로 지정. providers: `identity`(평문), `aescbc`, `aesgcm`, `secretbox`, `kms` v1/v2.
- K3s는 `--secrets-encryption` 플래그로 aescbc provider 활성화.
## 기본 규칙
### 1. 분류: ConfigMap vs Secret vs Vault
#### ConfigMap
- host/port/base path
- feature flag
- timeout/retry/batch size
- 공개 가능한 application config (`application.yaml` 비기밀 부분)
- log level
- probe 관련 non-secret 설정
#### Kubernetes Secret (하지만 **Vault 파생**이 기본)
- DB password, OAuth client secret, signing key, API token
- TLS 인증서 (cert-manager가 자동 생성)
- imagePullSecret(`kubernetes.io/dockerconfigjson`)
- VSO/ESO가 sync한 Secret
#### Vault (source of truth)
- 모든 운영 credential의 1차 저장소
- DB dynamic credentials, PKI, transit encryption keys
- OIDC client secret, SMTP credential
- KV v2 path로 서비스별 격리
**원칙:** "조금이라도 민감하면 Vault/Secret 쪽". ConfigMap에는 절대 비밀 넣지 않는다. base64는 암호화가 아니다.
### 2. Kubernetes Secret at-rest encryption 필수
운영 클러스터는 API Server `--encryption-provider-config`로 Secret 자원을 암호화한다. 권장 순서: **KMS v2 > KMS v1 > aescbc > identity(금지)**.
```yaml
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources: ["secrets"]
providers:
- kms:
apiVersion: v2
name: platform-kms
endpoint: unix:///var/run/kmsplugin/socket.sock
cachesize: 1000
timeout: 3s
- aescbc:
keys:
- name: fallback-2026-q1
secret: <32-byte base64 key>
- identity: {}
```
- KMS 소켓/플러그인은 노드 hardening 대상.
- K3s는 `curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="server --secrets-encryption" sh -` 또는 config.yaml `secrets-encryption: true`.
- 기존 Secret은 `kubectl get secrets --all-namespaces -o json | kubectl replace -f -`로 강제 재암호화.
- 키 회전은 `kube-apiserver` restart + `replace` 절차를 ADR로 고정.
### 3. Secret delivery 경로 우선순위
1. **VSO** — 운영 기본. Vault KV v2/dynamic credential → K8s Secret → envFrom/volume. 앱 코드 변경 0.
2. **ESO** — 멀티 클라우드 provider 필요 시. API는 VSO와 유사하지만 `SecretStore`/`ClusterSecretStore` + `ExternalSecret`.
3. **CSI Secret Store Driver** — K8s Secret object를 아예 만들고 싶지 않을 때(volume only). SA별 scope가 필요한 sensitive mount.
4. **Vault Agent Injector** — 앱이 template engine을 필요로 할 때(JSON/XML 포맷 렌더링). K8s Secret 없음.
5. **SealedSecrets / SOPS** — GitOps 전용 + 소규모 클러스터 + VSO 미도입 환경. Git에 encrypted blob 커밋.
6. **Plain Secret manifest** — 운영 금지. 로컬/부트스트랩 한정.
선택 기준:
- "Vault가 SoT이고 K8s Secret을 앱이 envFrom으로 소비" → VSO
- "멀티 클라우드/비-Vault provider" → ESO
- "K8s Secret 자체를 만들고 싶지 않음(audit/scope)" → CSI
- "앱이 Vault template로 renderng 필요" → Vault Agent Injector
- "Vault 없음 + Git에 커밋해야 함" → SealedSecrets/SOPS
### 4. VSO CRD 사용 표준
VSO는 Helm으로 `vault-secrets-operator` namespace에 설치되어 있다고 가정한다.
- `VaultConnection` (namespace or cluster) — Vault address, CA bundle, TLS skipVerify=false
- `VaultAuth` — auth method(kubernetes, jwt, approle). kubernetes auth 기본.
- `VaultStaticSecret` — KV v2 secret → K8s Secret
- `VaultDynamicSecret` — Postgres/MySQL/AWS dynamic credentials
- `VaultPKISecret` — PKI engine → `kubernetes.io/tls` Secret
- `HCPVaultSecretsApp` — HCP Vault Secrets 소비
모든 CRD는 같은 namespace 안에서 선언하고, 결과 Secret의 이름은 서비스명 규칙을 따른다.
### 5. Vault path 규칙 + auth policy
- KV v2 path: `kv/data/<team>/<service>/<env>/<component>` (예: `kv/data/identity/auth-server/prod/db`)
- Vault role은 namespace + service account로 제한:
```
bound_service_account_names=auth-server
bound_service_account_namespaces=auth-prod
```
- Vault policy는 `path "kv/data/identity/auth-server/prod/*" { capabilities = ["read"] }` 수준으로 scope.
- dynamic credential TTL은 pod 수명과 맞춘다(예: Postgres role 24h, auto-renew).
### 6. 주입 방식: envFrom vs volume
- **envFrom** — 전체 Secret의 key를 env로 투사. 간단, 12-factor 친화. 하지만 프로세스 env는 sub-process 상속, `/proc/<pid>/environ` 노출 위험.
- **volume** — 파일로 마운트(`/var/run/secrets/<name>`). 권장 in-memory(`readOnly: true`). 민감 key는 volume 우선.
- **envFrom + volume 혼합** 허용(db env는 env, signing key는 volume).
- **subPath**는 사용 금지(Secret 업데이트가 자동 반영 안 됨).
### 7. 한 Pod 내에서도 필요한 컨테이너에만 주입
- sidecar(metrics, proxy)에는 secret 전달 금지.
- Pod `volumes`로 선언하더라도 각 컨테이너 `volumeMounts`는 필요한 컨테이너에만.
### 8. Secret/ConfigMap naming
- 패턴: `<service>-<purpose>` (`auth-server-db`, `auth-server-oidc-client`, `keycloak-db`).
- 금지: `common-*`, `shared-*`, `global-*` (스코프가 불분명하고 권한 팽창 원인).
### 9. immutable Secret/ConfigMap
- 변경 빈도 낮은 `kubernetes.io/tls`, 앱 release-tied config는 `immutable: true` 검토.
- immutable이면 수정 불가 → 삭제 후 재생성 + rollout 필요. VSO가 갱신하는 Secret은 immutable 금지.
### 10. Kustomize generator 사용 기준
- `configMapGenerator` — 비기밀 설정에 허용. hash suffix로 rollout 트리거.
- `secretGenerator` — **운영 금지**. 로컬/테스트/부트스트랩 한정.
- 운영은 VSO/ESO/SealedSecrets 경로.
### 11. Image pull secret
- 타입: `kubernetes.io/dockerconfigjson`.
- 구조:
```json
{
"auths": {
"registry.example.com": {
"username": "ci-bot",
"password": "<token>",
"auth": "<base64(username:password)>"
}
}
}
```
- SA의 `imagePullSecrets`에 연결 → Deployment마다 반복 선언 불필요.
- Registry credential 자체도 VSO로 Vault → `kubernetes.io/dockerconfigjson` Secret sync(VSO `VaultStaticSecret.destination.type: kubernetes.io/dockerconfigjson`).
### 12. image 지정: digest pin 기본
- mutable tag(`latest`, `main`, `dev`)는 `imagePullPolicy: Always` + staging 환경에만.
- 운영은 `image: registry.example.com/auth-server@sha256:<digest>` 고정. `imagePullPolicy: IfNotPresent` 충분.
- digest는 CI가 release 시 생성하고 GitOps manifest(ArgoCD)에 커밋.
- Kyverno/Gatekeeper로 namespace `auth-prod`의 Pod image가 `@sha256:`를 포함하도록 enforce.
### 13. Secret 접근 RBAC
- `secrets` 리소스의 `list`/`watch`는 controller(VSO, cert-manager, argo-cd)에만 허용.
- 일반 workload는 `get` + `resourceNames` 배열로 제한.
- 같은 namespace에서 Pod 생성 권한은 Secret 간접 접근이 될 수 있음을 전제로 RBAC 설계(namespace 분리).
### 14. 민감정보 로깅/에러 보호
- 앱은 비밀을 평문 로그, 예외 메시지, telemetry attribute, debug endpoint에 포함 금지.
- Exception handler는 `password`, `token`, `secret`, `authorization` 포함 필드 자동 redact.
- APM/Logging pipeline에도 scrub rule 추가.
### 15. Secret 회전
- dynamic credential: VSO `VaultDynamicSecret`이 TTL 전에 자동 renew/rotate + Pod rollout trigger(`rolloutRestartTargets`).
- static credential: VSO `refreshAfter` + Vault rotate cron + `rolloutRestartTargets`로 Deployment 자동 rolling.
- TLS cert: cert-manager가 `renewBefore`에 맞춰 회전. Pod는 `reloader` annotation 또는 webhook으로 rollout.
### 16. 설정 타입과 도메인 타입 분리
- `@ConfigurationProperties` / `application.yaml`은 설정 계약.
- 도메인 Value Object는 config에서 복사하되 config 타입을 도메인에 노출하지 않는다.
- 테스트에서는 config를 직접 주입할 수 있어야 한다(포트 바인딩, spring profile).
### 17. 환경별 overlay
- `base/` — 공통 ConfigMap/Service/Deployment/RBAC
- `overlays/{dev,staging,prod}/` — 환경별 patch(`replicas`, `resources`, `image digest`, `ingress host`)
- Secret은 **overlay에 plain 저장 금지**. VSO CRD도 prod overlay에서 Vault mount path만 override.
### 18. 현재 스택 기본 권장안
#### auth-server / test-server / keycloak
- ConfigMap: `application.yaml` 비기밀
- Secret 경로: VSO `VaultStaticSecret`(OIDC client) + `VaultDynamicSecret`(Postgres role)
- 주입: envFrom(DB creds) + volume(signing key 파일)
#### migration-flyway
- short-lived Job
- SA token automount false
- VSO `VaultDynamicSecret`이 migration 전용 Postgres role을 짧은 TTL로 발급
#### vault
- Vault server 자체의 unseal key는 cluster 밖(HSM/KMS/cloud KMS auto-unseal)
- bootstrap token은 `vault-bootstrap` namespace에 at-rest encrypted Secret으로 저장, 사용 후 삭제
#### registry
- `kubernetes.io/dockerconfigjson` Secret은 VSO로 Vault KV에서 sync
- namespace SA `imagePullSecrets`에 연결
## 프로젝트 기준 요약
- ConfigMap = 비기밀, Secret = 민감정보, Vault = source of truth
- Kubernetes Secret at-rest encryption(KMS 우선, aescbc 최소) 필수
- Secret delivery 우선순위: VSO > ESO > CSI > Vault Agent Injector > SealedSecrets/SOPS
- 운영 Secret generator/plain Secret manifest 금지
- image는 digest pin + private registry, `kubernetes.io/dockerconfigjson` Secret은 SA imagePullSecrets 연결
- Secret 주입은 필요한 컨테이너/필요한 key만, env보다 volume 우선
- RBAC은 namespace Role + `resourceNames` + list/watch controller 전용
- 회전은 VSO/cert-manager + rolloutRestart 자동화
+305
View File
@@ -0,0 +1,305 @@
# db / migration 기준
## 목적
이 문서는 Kubernetes 상의 PostgreSQL과 Flyway를 기준으로
- 데이터베이스를 어떻게 나눌지
- 어떤 operator / 도구로 운영할지 (CNPG, Zalando, Crunchy, self-managed StatefulSet)
- migration을 어디서 어떻게 실행할지
- migration과 배포(Helm / Argo CD)의 순서를 어떻게 보장할지
- validate / migrate / rollback / backup / PITR을 어떤 순서로 볼지
- zero-downtime을 위한 expand-migrate-contract를 어떻게 적용할지
를 먼저 고정한다.
이 문서의 목표는 다음과 같다.
- app rollout과 schema 변경을 분리한다
- Keycloak DB와 auth-server DB 경계를 먼저 고정한다
- Flyway를 앱 시작 로직에 숨기지 않는다
- PostgreSQL backup/restore 전략과 migration 전략을 함께 본다
- 1000+ 서비스 규모에서 일관된 migration Job 표준을 만든다
## 공식 의미
- `pg_dump`는 logical export다. 정기 production 전체 백업 기본값으로는 보통 적합하지 않다.
- `pg_basebackup`은 실행 중인 PostgreSQL cluster의 base backup을 만들며 PITR/standby 시작점으로 쓴다.
- PostgreSQL PITR은 base backup + WAL archiving 결합이다.
- 운영 표준 물리 백업 도구: **pgBackRest**, **WAL-G**. 또는 operator-native (CNPG Barman Cloud, Crunchy PGO).
- PostgreSQL의 **대부분 DDL은 트랜잭션 내에서 실행 가능**하지만, `CREATE INDEX CONCURRENTLY`, `REINDEX CONCURRENTLY`, `ALTER TYPE ... ADD VALUE`, `VACUUM`은 트랜잭션 밖에서만 실행된다.
- CloudNativePG operator는 CNCF Sandbox 프로젝트로 K8s-native Postgres 운영 표준 후보다.
- Flyway `validate`는 적용된 migration과 로컬 migration 사이의 이름/타입/checksum 차이, 로컬에 없는 적용 버전, 아직 적용되지 않은 로컬 버전을 검증한다.
- Flyway `validateOnMigrate` 기본값은 `true`, `cleanDisabled` 기본값은 `true` (Flyway 9+).
- Flyway `migrate`는 schema history table을 자동 생성하고 최신 migration까지 적용한다.
- Flyway Community(OSS)는 **undo(U__) migration을 지원하지 않는다.** Undo는 Teams/Enterprise 전용이다.
- Flyway는 migration 실행 시 schema history table에 advisory lock을 걸어 동시 실행을 방지한다.
## RPO / RTO
모든 DB는 다음을 runbook에 먼저 적는다.
- RPO / RTO / Retention
- 복구 목표 (cluster restore / PITR / standby seed)
- 운영 tier (gold / silver / bronze)
이 값들이 없으면 backup 도구 선택이 되지 않는다. `backup-restore.md` 참조.
## 기본 규칙
### 1. DB 경계는 애플리케이션 경계보다 먼저 고정
다음을 명시적으로 정한다.
- Keycloak DB와 auth-server DB를 **물리적 cluster**로 분리할지, 같은 cluster 내 **logical DB / schema**로 분리할지
- test-server가 DB를 가지는지
- migration 소유권이 누구에게 있는지 (보통 서비스 팀)
기본:
- 인증 critical data (Keycloak)와 앱 data (auth-server)는 **cluster 분리 권장**
- 같은 cluster를 쓰더라도 database / role / schema ownership을 섞지 않음
- 한 migration tool/job이 여러 서비스 schema를 동시에 소유하지 않음
### 2. K8s 위 Postgres 운영 기본은 operator
1000+ 서비스 규모에서 self-managed StatefulSet은 운영 부담이 너무 크다. Operator를 기본 후보로 둔다.
우선순위 (2026 기준):
1. **CloudNativePG (CNPG)** — CNCF Sandbox, K8s-native, Barman Cloud 내장, `Cluster` / `Backup` / `ScheduledBackup` CRD
2. **Crunchy PGO** — 상용 지원, pgBackRest 내장
3. **Zalando postgres-operator** — Spilo 기반, 레거시 환경
operator를 쓰면 자동으로 얻는 것:
- primary/standby 구성 + failover
- rolling minor upgrade
- WAL archiving + continuous backup
- pg_basebackup, PITR, replica re-clone
- PodMonitor 연동
### 3. migration은 앱 startup에 숨기지 않는다
Flyway migration은 **독립 실행 단계**다.
기본:
- `validate` → (필요시 `info`) → `migrate` → app rollout
기본 금지:
- app container 시작 시 자동 migration (Spring Boot `spring.flyway.enabled=true` + `@SpringBootApplication` 부팅 시 migrate)
- readiness/liveness와 migration 실패를 섞는 구조
- "서버가 뜨면 알아서 schema를 맞춘다" 방식
### 4. Flyway 실행 기본값은 Kubernetes Job
운영 환경에서 Flyway는 다음 중 하나로만 실행한다.
- Kubernetes Job (권장)
- CI/CD 명시 단계
- 운영자 명시 실행 절차
장기 실행 Deployment에 넣지 않는다. Flyway 예시는 `examples/infra/flyway.md` 참조.
### 5. migration Job은 배포 흐름 안에서 app보다 먼저 실행
migration을 app보다 **선행**시키는 것은 manifest 메타데이터로 선언한다.
패턴 A — Helm hook:
```yaml
metadata:
annotations:
"helm.sh/hook": "pre-upgrade,pre-install"
"helm.sh/hook-weight": "-10"
"helm.sh/hook-delete-policy": "before-hook-creation,hook-succeeded"
```
패턴 B — Argo CD sync wave + hook:
```yaml
metadata:
annotations:
argocd.argoproj.io/sync-wave: "-1"
argocd.argoproj.io/hook: Sync
argocd.argoproj.io/hook-delete-policy: BeforeHookCreation
```
기본:
- migration은 sync wave가 app보다 **작은** 값 (먼저 실행)
- app Deployment는 wave `0` 또는 그 이상
- Helm과 Argo CD를 혼용하는 경우 **한 쪽으로 통일** (둘 다 hook을 걸면 순서가 꼬인다)
### 6. migration Job 안전 설정
모든 migration Job은 다음을 명시한다.
- `parallelism: 1` — 병렬 실행 금지 (Flyway advisory lock이 막아주지만, Job 수준에서도 명시)
- `completions: 1`
- `backoffLimit: 0` 또는 작은 값 (1~2) — 실패 시 무한 재시도 금지
- `activeDeadlineSeconds` — 타임아웃 (예: 1800)
- `ttlSecondsAfterFinished` — 완료 후 자동 정리 (예: 86400)
- `restartPolicy: Never`
- 이미지는 **digest pinning** (`flyway/flyway@sha256:...`)
- `resources.requests/limits` 명시
- `securityContext` restricted PSA 준수
### 7. validate를 먼저, migrate를 나중에
운영 절차 기본 순서:
1. `flyway info` (pending migration 확인)
2. `flyway validate`
3. `flyway migrate`
4. `flyway info` (결과 확인)
5. app rollout
`validateOnMigrate=true` 기본값이 있더라도, 운영 runbook에서는 validate 단계를 **분리 Job** 또는 **initContainer**로 분리한다. `examples/infra/flyway.md` 참조.
### 8. migration source는 Git이 source of truth
중요한 것은 아래다.
- versioned migration script (`V__`)
- repeatable migration script (`R__`)
- migration ordering
- schema history table 상태
기본 금지:
- 운영 서버에서 migration 파일 수동 수정
- 적용된 migration 파일을 사후 편집 (checksum mismatch)
- Flyway schema history table을 사람이 직접 UPDATE/DELETE
### 9. Flyway undo(U__)는 쓰지 않는다
Flyway Community(OSS)는 **U__ 파일을 지원하지 않는다**. Teams/Enterprise에서만 `undo` 명령이 동작한다.
기본:
- undo migration 파일을 만들지 않음
- rollback은 forward-only migration + PITR로 수행
- 운영 기본은 "다음 migration으로 앞으로 수정"
### 10. DB backup 전략과 migration 전략을 같이 본다
schema 변경이 production에 들어간다면, 같은 변경 계획 안에 아래가 같이 있어야 한다.
- rollback 가능 여부
- 변경 직전 backup 시점 (예: on-demand CNPG `Backup` 실행)
- restore 단위 (전체 cluster / logical DB)
- PITR 필요 여부 + targetTime 후보
- migration 실패 시 중단 지점 (어느 V__에서 멈췄는지)
### 11. PostgreSQL 운영 기본 백업은 continuous physical backup
운영 기본 복구 목표가 cluster-level restore / PITR / standby seed 중 하나면 continuous WAL archiving + base backup이 기본이다.
도구 선택:
- K8s + CNPG → Barman Cloud (내장)
- K8s + Crunchy → pgBackRest (내장)
- 자체 운영 → pgBackRest 또는 WAL-G
`pg_dump`는 다음 용도로 제한:
- 선택적 logical export
- 로컬/테스트 seed
- 일부 schema/table 보존
- migration 검증용 비교 데이터
### 12. PITR 필요 여부를 초기에 결정
다음 질문에 "예"면 PITR을 우선 검토한다.
- 잘못된 migration/DDL을 특정 시점 직전으로 되돌려야 하는가
- 운영 데이터 손실 허용 시간이 짧은가 (RPO < 1h)
- 인증 관련 데이터 정합성이 중요한가
### 13. schema ownership은 서비스별로 분리
기본:
- auth-server schema는 auth-server 팀이 소유
- keycloak schema는 keycloak이 소유
- 공용 schema 남발 금지
- "편해서" 하나의 migration 프로젝트로 통합 관리 금지
### 14. Flyway history table 전략을 먼저 고정
초기에 결정:
- `flyway.table` (기본 `flyway_schema_history`)
- `flyway.defaultSchema`
- `flyway.schemas`
- `flyway.createSchemas`
- 필요 시 `flyway.initSql`
기본:
- history table을 service별 schema에 배치 (예: `auth_server.flyway_schema_history`)
- 여러 서비스의 history table을 하나의 schema에 몰지 않음
### 15. baseline / repair는 예외 절차
baseline과 repair는 정상 운영 흐름이 아니다.
허용 예:
- legacy DB를 처음 Flyway 관리로 편입 (baseline)
- 의도적 migration 수정 후 공식 절차로 checksum 회복 (repair)
- history corruption 복구 (repair)
기본 금지:
- CI/CD에서 습관적 baseline/repair
- validate 오류를 없애기 위해 무분별하게 repair
### 16. migration은 forward-only를 기본값으로
운영 기본값:
- 새 migration으로 앞으로 수정
- rollback용 SQL을 미리 기대하지 않음
- 실패 시 restore/PITR 또는 다음 migration으로 교정
### 17. Keycloak DB와 auth-server DB는 따로 본다
둘 다 PostgreSQL을 써도 운영 기준은 별도로 둔다.
- migration 파이프라인 분리
- backup/restore 영향도 분리
- schema/table ownership 분리
- 버전 업그레이드 절차 분리
- Keycloak은 자체 migration을 내장하므로 **Flyway로 관리하지 않는다**
### 18. test-server는 DB를 기본 전제로 두지 않는다
test-server가 DB 연결이 없으면 migration 대상 아님, DB secret 불필요, rollout 절차도 DB 의존 없이 단순화된다.
### 19. destructive migration은 expand → migrate → contract
Zero-downtime을 위한 3단계 릴리즈:
1. **Expand** — 새 컬럼/테이블 추가 (NULL 허용 또는 default 값 있음). 기존 앱 호환.
2. **Migrate** — 앱을 새 스키마 기준으로 배포 + 데이터 backfill.
3. **Contract** — 기존 컬럼/테이블/제약 제거. 한 릴리즈 이상 뒤.
각 단계는 **별도 릴리즈**로 나간다. 같은 릴리즈에서 expand와 contract를 같이 하지 않는다.
인증/권한/토큰 관련 테이블은 특히 보수적으로.
### 20. DB 변경은 애플리케이션 호환성 윈도우를 고려
migration 문서는 다음을 포함한다.
- 이전 앱 버전과 호환 여부
- 새 앱 버전과 호환 여부
- 중간 배포 구간에서 허용되는 상태 (N-1 ↔ N 동시 운영 가능 여부)
- 롤백 시 DB가 이미 바뀐 상태일 때의 대응
### 21. 대용량 / long-running DDL은 트랜잭션 밖에서
Postgres에서 다음은 트랜잭션 밖에서 실행해야 한다.
- `CREATE INDEX CONCURRENTLY`
- `REINDEX CONCURRENTLY`
- `ALTER TYPE ... ADD VALUE` (Postgres 12+에서는 트랜잭션 내에서도 제한적으로 가능)
- `VACUUM`
Flyway에서는 해당 migration 파일 상단에 다음을 적는다:
```sql
-- flyway:executeInTransaction=false
CREATE INDEX CONCURRENTLY idx_users_email ON users(email);
```
### 22. restore 테스트 없는 backup/migration 전략 금지
다음은 반드시 drill이 있어야 한다.
- PostgreSQL base backup 복구
- WAL/PITR 절차
- Flyway 적용 후 실패 시 중단 및 복구 절차
- Keycloak/auth-server 개별 DB restore 절차
## 현재 스택 기본 권장안
- **auth-server DB**: CNPG `Cluster` 3 instances, Barman Cloud, RPO 5분, Flyway Job으로 migration
- **keycloak DB**: CNPG `Cluster` 별도, Keycloak 자체 migration (Flyway 밖)
- **test-server**: DB 없음
- **migration-flyway**: Kubernetes Job (Helm/Argo hook), 앱 Deployment보다 먼저 실행
- **backup**: CNPG Barman continuous WAL + daily base backup, `pg_dump`는 보조
## 프로젝트 기준 요약
- app rollout과 migration을 분리 (app startup migration 금지)
- Postgres on K8s는 CNPG operator를 기본 후보로
- migration Job은 Helm hook 또는 Argo CD sync wave로 app보다 먼저 실행
- migration Job은 `parallelism: 1`, `backoffLimit: 0`, digest pinning, restricted PSA
- Flyway undo(U__) 파일 만들지 않음 (OSS 미지원)
- validate → migrate → app rollout 순서
- CNPG Barman Cloud (또는 pgBackRest / WAL-G) 물리 백업 + PITR, `pg_dump`는 보조
- schema ownership은 서비스별 분리
- destructive migration은 expand → migrate → contract, 여러 릴리즈에 걸쳐
- non-transactional DDL은 `-- flyway:executeInTransaction=false`
+304
View File
@@ -0,0 +1,304 @@
# Flyway 기준
## 목적
이 문서는 PostgreSQL 기반 서비스에서 Flyway를
- 어디서 실행할지 (Kubernetes Job)
- 어떤 순서로 실행할지 (validate / info / migrate / app rollout)
- 어떤 배포 흐름과 맞물릴지 (Helm hook / Argo CD sync-wave)
- config를 어떻게 공급할지 (env var + Secret via Vault Secrets Operator)
- schema history table을 어떻게 둘지
- baseline / repair / out-of-order / undo를 어떻게 다룰지
- non-transactional DDL을 어떻게 처리할지
를 먼저 고정한다.
이 문서의 목표는 다음과 같다.
- Flyway를 앱 startup 내부 로직처럼 숨기지 않는다
- validate / migrate / repair / baseline의 역할을 분리한다
- schema history table을 운영 감사 추적의 일부로 본다
- migration Job을 1000+ 서비스 규모에서 재현 가능하게 표준화한다
- DB 변경을 애플리케이션 rollout과 분리해 운영한다
## 공식 의미
- Flyway `validate`는 적용된 migration과 로컬 migration 사이의 이름/타입/checksum 차이, 로컬에 없는 적용 버전, 아직 적용되지 않은 로컬 버전을 검증한다.
- `migrate`는 schema history table이 없으면 자동 생성하고 최신 migration까지 적용한다.
- schema history table은 migration 실행 내역, checksum, 성공/실패 상태를 기록하는 audit trail이다.
- `repair`는 schema history table을 수정하는 명령이며, 실패한 migration 엔트리 제거, checksum/description/type 재정렬, missing migration 삭제 표시를 수행한다. user object는 정리하지 않는다.
- schema history table 기본 이름은 `flyway_schema_history`다.
- schema history table 위치는 `table`, `defaultSchema`, `schemas`로 제어할 수 있다.
- `createSchemas=false`일 때 history table이 들어갈 schema가 미리 준비되지 않으면 migrate가 실패할 수 있다.
- 기존 non-empty schema에 Flyway를 도입할 때 history table이 없으면 `baseline` 또는 `baselineOnMigrate`가 필요할 수 있다.
- schema history에는 `Pending`, `Success`, `Missing`, `Out of Order`, `Outdated`, `Superseded`, `Deleted` 등 상태가 기록될 수 있다.
- Flyway는 migration 실행 중 schema history table에 **advisory lock**을 걸어 동시 실행을 직렬화한다. 다중 replica Job 수준의 race를 방지한다.
- `cleanDisabled`는 Flyway 9 이후 기본 `true`. production에서는 반드시 `true`를 명시한다.
- Flyway 8.2+ 에서 `-- flyway:executeInTransaction=false` directive로 migration 파일 단위 트랜잭션 비활성화가 가능하다.
- Flyway Community(OSS)는 **undo(U__) migration을 지원하지 않는다.** Undo는 Teams/Enterprise 상용 기능이다.
- 환경변수 config 지원: `FLYWAY_URL`, `FLYWAY_USER`, `FLYWAY_PASSWORD`, `FLYWAY_LOCATIONS`, `FLYWAY_SCHEMAS`, `FLYWAY_DEFAULT_SCHEMA`, `FLYWAY_TABLE`, `FLYWAY_BASELINE_ON_MIGRATE`, `FLYWAY_VALIDATE_ON_MIGRATE`, `FLYWAY_CLEAN_DISABLED`, `FLYWAY_OUT_OF_ORDER`, 그 외 `FLYWAY_*`.
## 기본 규칙
### 1. Flyway는 앱 startup이 아니라 독립 실행 단계
운영 환경에서 Flyway는 다음 중 하나로만 실행한다.
- Kubernetes Job (권장)
- CI/CD 명시 단계
- 운영자 명시 실행 절차
기본 금지:
- 애플리케이션 startup 시 자동 migration
- Spring Boot `spring.flyway.enabled=true`로 앱 부팅 경로에 포함
- readiness/liveness와 migration 실패를 섞는 구조
### 2. 기본 순서는 info → validate → migrate → info → app rollout
운영 기본 순서:
1. `flyway info` (pending 확인)
2. `flyway validate`
3. `flyway migrate`
4. `flyway info` (결과 확인)
5. 애플리케이션 rollout
`validateOnMigrate=true`가 기본값이지만, 운영 절차상 validate를 **분리 initContainer** 또는 **사전 단계**로 둔다.
### 3. 배포 흐름 안에서 app보다 먼저 실행 — 두 가지 패턴
**패턴 A: Helm hook**
```yaml
annotations:
"helm.sh/hook": "pre-upgrade,pre-install"
"helm.sh/hook-weight": "-10"
"helm.sh/hook-delete-policy": "before-hook-creation,hook-succeeded"
```
**패턴 B: Argo CD sync-wave**
```yaml
annotations:
argocd.argoproj.io/sync-wave: "-1"
argocd.argoproj.io/hook: Sync
argocd.argoproj.io/hook-delete-policy: BeforeHookCreation
```
기본:
- 두 패턴을 혼용하지 않는다 (Argo CD가 Helm chart를 렌더링할 때 Helm hook을 일반 리소스로 취급해 순서가 꼬임)
- 배포 도구에 맞춰 한쪽만 사용
### 4. migration Job 안전 설정 체크리스트
Job manifest에 반드시 다음이 있어야 한다.
- `parallelism: 1`, `completions: 1`
- `backoffLimit: 0` 또는 작은 값 (1~2)
- `activeDeadlineSeconds` (권장 1800 = 30분, 대형 migration은 더 길게)
- `ttlSecondsAfterFinished` (권장 86400 = 1일)
- `restartPolicy: Never`
- 이미지 digest pinning (`flyway/flyway@sha256:...`)
- `imagePullPolicy: IfNotPresent`
- `resources.requests/limits`
- `securityContext`: `runAsNonRoot: true`, `readOnlyRootFilesystem: true`, `capabilities: drop: [ALL]`
- Pod-level `seccompProfile: RuntimeDefault`
- `fsGroup` 명시 (필요 시)
### 5. config는 환경변수 + Secret
Flyway CLI는 `FLYWAY_*` 환경변수를 읽는다. Secret은 Vault Secrets Operator(VSO) 또는 External Secrets Operator를 통해 클러스터에 동기화된 `Secret`에서 주입한다.
필수 env var:
- `FLYWAY_URL``jdbc:postgresql://host:5432/db`
- `FLYWAY_USER`
- `FLYWAY_PASSWORD` — Secret에서 주입
- `FLYWAY_LOCATIONS``filesystem:/flyway/sql`
운영 권장 env var:
- `FLYWAY_SCHEMAS` — 대상 schema
- `FLYWAY_DEFAULT_SCHEMA` — history table 위치
- `FLYWAY_TABLE` — 기본 `flyway_schema_history`
- `FLYWAY_VALIDATE_ON_MIGRATE=true`
- `FLYWAY_BASELINE_ON_MIGRATE=false` (운영 기본값)
- `FLYWAY_CLEAN_DISABLED=true` (production 필수)
- `FLYWAY_OUT_OF_ORDER=false`
- `FLYWAY_MIXED=false`
### 6. `cleanDisabled=true`는 production 필수
`flyway clean`은 모든 object를 drop 하는 파괴적 명령이다.
- production: `FLYWAY_CLEAN_DISABLED=true` 반드시 명시 (Flyway 9+ 기본값이지만 명시적으로 선언)
- dev/test: 필요 시 `false` 허용, 단 접근 권한 분리
### 7. migration SQL은 ConfigMap 또는 이미지 레이어로
옵션:
- **ConfigMap**: 서비스 manifest와 함께 Argo CD로 관리. small/medium migration set에 적합. ConfigMap 1MiB 제한 주의.
- **이미지 레이어**: 서비스 repo에서 migration SQL을 Docker image로 빌드하고 Flyway image와 합쳐 사용. 대규모 migration set에 적합.
기본:
- 두 방식 모두 Git이 source of truth
- 운영 서버에서 `kubectl edit configmap`으로 migration 편집 금지
### 8. schema history table은 운영 감사 추적의 일부
수동 UPDATE / DELETE 금지. 위치는 명시적으로 결정.
기본:
- service별 schema를 `FLYWAY_DEFAULT_SCHEMA`로 지정 (예: `auth_server`)
- history table 이름은 기본값 `flyway_schema_history` 유지
- 여러 서비스의 history table을 하나의 schema에 몰지 않음
### 9. `createSchemas=false`면 history schema를 사전 준비
`createSchemas=false`를 쓰면 history table이 들어갈 schema를 별도 준비해야 한다.
기본:
- `FLYWAY_INIT_SQL``CREATE SCHEMA IF NOT EXISTS` 지시 가능
- 또는 CNPG `Cluster.bootstrap.initdb.postInitSQL`에서 schema 사전 생성
- 생성 책임이 누구인지 문서화
### 10. baseline은 예외 절차
허용 예:
- legacy DB를 처음 Flyway 관리로 편입
- 기존 non-empty schema를 Flyway에 편입할 때
기본 금지:
- 새 프로젝트인데 baseline부터 쓰기
- 운영 배포 파이프라인에서 습관적으로 baseline 사용
### 11. `baselineOnMigrate`는 기본값 아님
`baselineOnMigrate=true`는 도입/전환 시 편의를 줄 수 있지만, 운영 기본값으로 두지 않는다.
이유:
- 예상치 못한 기존 schema를 "정상 상태"처럼 받아들일 수 있다
- 실수 탐지력이 떨어진다
`FLYWAY_BASELINE_ON_MIGRATE=false`로 명시.
### 12. `repair`는 예외 절차
허용 예:
- 의도적으로 migration 파일을 수정했고 checksum 정렬이 필요
- missing migration을 문서화된 절차로 정리
- failed repeatable migration 이후 history 정리
기본 금지:
- validate 오류가 나면 원인 분석 없이 바로 repair
- CI/CD에서 습관적으로 repair 실행
### 13. `repair`는 user object를 고쳐주지 않는다
repair는 schema history table만 정리한다. 실패한 migration이 남긴 DB object 정리, 불완전한 DDL/DML 정리는 별도 절차로 수행해야 한다.
### 14. 적용된 migration 파일은 수정 금지
이유:
- checksum mismatch
- 재현 불가
- 환경 간 drift
대응:
- 새 migration으로 교정
- 정말 예외적인 수정만 공식 repair 절차와 함께 수행
### 15. out-of-order는 기본 금지
Out-of-order migration은 전체 migration history를 다시 실행할 때 다른 결과를 만들 수 있다.
기본:
- `FLYWAY_OUT_OF_ORDER=false`
- 뒤늦게 빠진 migration을 넣는 방식을 기본값으로 두지 않음
- 예외 허용 시 영향 범위 검토 문서 필수
### 16. repeatable migration(R__)은 목적 제한
Repeatable migration은 다음 용도에 제한한다.
- view 정의
- function / procedure
- trigger 재생성
- reference / static data refresh
기본 금지:
- 순서가 중요한 핵심 schema change를 repeatable로 남발
- versioned migration 대신 repeatable로 대체
### 17. Undo(U__) migration은 만들지 않는다
Flyway Community(OSS)는 undo를 지원하지 않는다.
- U__ 파일을 repo에 두지 않음 (오해 유발)
- rollback은 forward-only 새 migration + PITR로 대응
### 18. locations는 environment별로 흔들지 않는다
`migrate``repair`는 같은 `locations` 전제를 가져야 한다.
기본:
- env마다 location 구조가 달라지지 않게 유지
- 운영과 개발에서 전혀 다른 migration set을 쓰지 않음
- env별 변수는 `placeholders`(`FLYWAY_PLACEHOLDERS_*`)로 분리
### 19. migration은 서비스 소유권 단위로 분리
기본:
- auth-server는 auth-server migration set
- keycloak은 keycloak 고유 migration (사실 Keycloak은 내부 migration을 사용하므로 Flyway 대상이 아님)
- 공용 migration 프로젝트 금지
### 20. migration naming / versioning
기본:
- versioned: `V<N>__<snake_case>.sql`, N은 증가하는 정수 또는 점표기(예: `V12__`, `V1.2.3__`)
- repeatable: `R__<snake_case>.sql`
- 이름은 변경 의도를 드러나게 작성
예:
- `V42__add_refresh_token_audit_columns.sql`
- `R__refresh_user_views.sql`
### 21. destructive change는 expand → migrate → contract
`db-and-migration.md` #19 참조. Flyway 입장에서 각 단계는 **별도 릴리즈**의 versioned migration으로 나간다.
### 22. non-transactional DDL은 `executeInTransaction=false`
Postgres에서 트랜잭션 밖 실행이 필요한 DDL:
- `CREATE INDEX CONCURRENTLY`
- `REINDEX CONCURRENTLY`
- `ALTER TYPE ... ADD VALUE`
- `VACUUM`
migration 파일 상단:
```sql
-- flyway:executeInTransaction=false
CREATE INDEX CONCURRENTLY idx_users_email ON users(email);
```
기본:
- 이런 DDL은 **전용 migration 파일**로 분리 (다른 statement와 섞지 않음)
- runtime 추정치 주석
- low-traffic window로 배포 일정 조정
### 23. rollback은 Flyway 명령에 기대지 않는다
운영 기본 rollback:
- 새 migration으로 수정
- PostgreSQL PITR (CNPG bootstrap.recovery)
- 애플리케이션 버전 rollback + DB 호환 윈도우 유지 (expand-contract의 효과)
rollout undo가 DB schema rollback을 대신하지 않는다.
### 24. 현재 스택 기준 기본 권장안
- **auth-server**
- Flyway Job (Helm hook 또는 Argo sync-wave)
- `FLYWAY_DEFAULT_SCHEMA=auth_server`
- validate → migrate → app rollout
- digest pinning
- **keycloak**
- Keycloak 자체 migration 사용, Flyway 대상 아님
- **test-server**
- DB가 없으면 Flyway 대상 아님
- **운영 절차**
- repair/baseline은 예외 승인 절차
- applied migration 수정 금지
- CLEAN_DISABLED=true 필수
## 프로젝트 기준 요약
- Flyway는 독립 실행 단계 (Kubernetes Job)
- info → validate → migrate → info → app rollout
- 배포 흐름 내 순서는 Helm hook 또는 Argo CD sync-wave 중 하나로 통일
- Job: `parallelism: 1`, `backoffLimit: 0`, `ttlSecondsAfterFinished`, digest pinning, restricted PSA
- config는 `FLYWAY_*` env var + Secret (VSO / ESO)
- `FLYWAY_CLEAN_DISABLED=true` 필수, `FLYWAY_BASELINE_ON_MIGRATE=false`, `FLYWAY_OUT_OF_ORDER=false`
- schema history table 위치를 `FLYWAY_DEFAULT_SCHEMA`로 명시
- baseline / repair / out-of-order는 예외 절차
- applied migration 수정 금지
- Undo(U__) 파일 만들지 않음 (OSS 미지원)
- non-transactional DDL은 `-- flyway:executeInTransaction=false`로 파일 단위 분리
- service별 migration ownership 분리
- destructive migration은 expand → migrate → contract
+224
View File
@@ -0,0 +1,224 @@
# K3s-specific 기준
## 목적
이 문서는 일반 Kubernetes 표준과 **분리**해서, K3s 운영에서만 발생하는 특수성을 고정한다.
목표:
- K3s packaged component (`coredns`, `traefik`, `local-storage`, `metrics-server`, `servicelb`)를 일반 manifest처럼 관리하는 실수를 막는다
- `/var/lib/rancher/k3s/server/manifests`를 source-of-truth로 쓰는 실수를 막는다
- 멀티 server HA 환경에서 `critical configuration value mismatch` join 실패를 예방한다
- embedded registry mirror(Spegel)의 네트워크·버전 게이트를 정확히 이해한다
- 1000+ 서비스 prod 스케일에서 K3s의 어떤 기능을 켜고 어떤 기능을 외부로 뺄지 기준을 박는다
## 공식 의미 (근거 URL 포함)
- K3s packaged component: `coredns`, `traefik`, `local-storage`, `metrics-server` (매니페스트 파일 기반) + `servicelb`(매니페스트 없이 `--disable`만 가능).
- AddOn auto-deploy: `/var/lib/rancher/k3s/server/manifests` 하위 파일은 server 시작 시 + 파일 변경 시 자동 apply. packaged component는 K3s가 재기록하므로 직접 수정 금지.
- multi-server 유저 AddOn은 서버 간 자동 동기화되지 **않는다**.
- K3s 설정: `/etc/rancher/k3s/config.yaml` + `/etc/rancher/k3s/config.yaml.d/*.yaml` drop-in.
- critical 값 (cluster-cidr / service-cidr / cluster-dns / cluster-domain / disable 세트 / CNI / embedded-registry 활성화)이 서버 간 불일치면 `critical configuration value mismatch` join 실패.
- packaged Helm component(`traefik` 등) 커스터마이징은 `HelmChartConfig` (apiVersion `helm.cattle.io/v1`).
- K3s 기본 local storage는 Rancher Local Path Provisioner (`local-path` StorageClass, node-local, not replicated).
- **embedded registry mirror (Spegel)**: 기본 비활성. 활성화 시 노드 간 TCP 5001 (p2p gossip) + TCP 6443 (registry + supervisor)이 reachable해야 한다. 출처: `https://docs.k3s.io/installation/registry-mirror` — "all nodes must be able to reach each other via their internal IP addresses, on TCP ports 5001 and 6443".
- K3s 이미지 import: `/var/lib/rancher/k3s/agent/images/*.tar{,.zst,.gz}`.
- K3s는 기본적으로 network policy enforcer (kube-router 기반)를 포함한다. 외부 CNI(Cilium 등) 사용 시 `--disable-network-policy` + `--flannel-backend=none` 조합 필요.
## 기본 규칙
### 1. K3s 전용 규칙은 별도 문서로 유지
일반 Kubernetes 표준 문서에 K3s 특수성을 흩뿌리지 않는다. 분리 범주:
- packaged component
- AddOn auto-deploy
- config.yaml / config.yaml.d
- local-path provisioner
- embedded registry mirror
- critical server flags
- Helm component customization
### 2. packaged component는 “편의 기능”, 직접 수정 절대 금지
관리 대상:
- `coredns`
- `traefik`
- `local-storage`
- `metrics-server`
- `servicelb` (manifest 없음, flag로만 제어)
금지:
- `/var/lib/rancher/k3s/server/manifests/traefik.yaml` 직접 edit
- packaged manifest를 Git SoT로 관리
- 재시작 후 overwrite되는 파일에 운영 커스터마이징 저장
### 3. packaged component 유지/비활성은 cluster bootstrap 때 박는다
1000-서비스 prod 스케일에서 현재 기준:
| component | prod 기본 | 이유 |
|----------------|-----------|-------------------------------------------------------------|
| `traefik` | disable | ingress-nginx / Envoy Gateway로 교체. Traefik은 dev만. |
| `servicelb` | disable | MetalLB L2/BGP 또는 외부 LB. klipper는 노드 80/443 점유. |
| `local-storage`| disable | Longhorn / Ceph RBD / CSI. node-local은 DR 불가. |
| `metrics-server`| keep | HPA + `kubectl top` 전제. 대체 pipeline 준비되면 교체 가능. |
| `coredns` | keep | 교체는 특수 케이스. node-local dns cache는 별도로 추가. |
| network policy | 상황별 | Cilium 도입 시 disable. 기본 kube-router 유지도 가능. |
### 4. server critical config는 Git에서 단일 파일로 관리
`/etc/rancher/k3s/config.yaml`이 Git의 inventory repo (Ansible / Fleet / CI)에서 push된다.
서버별 ad-hoc 수정 금지. critical 값 mismatch는 **join 실패**로 직결된다.
일치해야 하는 값:
- `cluster-cidr`, `service-cidr`, `cluster-dns`, `cluster-domain`
- `disable` 세트
- `flannel-backend` / `disable-network-policy`
- `embedded-registry` 활성화 여부
- `datastore-endpoint` (etcd / external DB)
### 5. CLI argument보다 config file 우선
재현성 / diff / multi-node 동기화를 위해 server/agent 플래그는 모두 `config.yaml`로.
`/etc/rancher/k3s/config.yaml.d/*.yaml` drop-in은 역할별 파일 분리(예: `10-networking.yaml`, `20-audit.yaml`)에 사용.
### 6. `/var/lib/rancher/k3s/server/manifests`는 apply sink, SoT 아님
- 운영 SoT = Git (+ Kustomize / ArgoCD / Flux)
- 이 디렉터리는 bootstrap addon에만 한정 (예: `k3s-addons-disabled.yaml` placeholder)
- 서버별로 다른 파일을 두고 "알아서 맞겠지"는 금지
- `.skip` 파일은 **임시** 비활성화 용. 장기 disable은 `--disable` 플래그로.
### 7. multi-server user AddOn은 Git push, 로컬 scp 금지
K3s는 user AddOn을 서버 간 동기화하지 않는다. 멀티 server 환경에서 AddOn을 쓰려면:
- GitOps 컨트롤러(ArgoCD/Flux)가 apply
- 또는 Ansible/Fleet이 단일 server 노드에만 drop
- 또는 완전히 포기하고 `kubectl apply`로만 관리 (권장)
### 8. packaged Helm component 커스터마이징은 `HelmChartConfig`
traefik 유지가 불가피할 때:
```yaml
apiVersion: helm.cattle.io/v1
kind: HelmChartConfig
metadata:
name: traefik
namespace: kube-system
spec:
valuesContent: |-
<override values>
```
- `metadata.name` / `namespace`는 대응 `HelmChart`와 반드시 일치
- 민감 값은 `valuesSecrets`로 Secret 참조 (valuesContent에 하드코딩 금지)
- HelmChartConfig 자체는 Git 관리
### 9. local-path provisioner는 dev/test 한정
Rancher Local Path Provisioner = node-local hostPath. 특성:
- ReadWriteOnce only
- 노드 장애 시 데이터 접근 불가
- 백업/DR 불가 (StorageClass 레벨 스냅샷 없음)
- binding mode = WaitForFirstConsumer (Pod가 뜰 때 PV 생성)
기준:
- dev/test StatefulSet의 PVC 기본값으로만 허용
- prod의 DB / Vault / MinIO / Kafka / etcd backup target에 절대 사용 금지
- prod storage는 **Longhorn (K3s 권장) / Ceph RBD / 외부 CSI** 중 택1
### 10. metrics-server는 유지 기본값
HPA v2 metrics, `kubectl top`, VPA, kube-state-metrics 연동 모두가 전제. disable 시 Prometheus Adapter 등 대체 pipeline을 먼저 준비한 뒤에만 꺼야 한다.
### 11. traefik / servicelb는 포트 점유 + 노드 노출 전략을 같이 본다
- `servicelb` (klipper) = 모든 노드가 80/443 HostPort로 열림. prod에서는 거의 항상 disable + MetalLB 또는 외부 LB.
- `traefik` 유지 시 IngressClass / Middleware / EntryPoint 세 레이어가 전부 K3s 관리. prod에서는 disable + `ingress-nginx` DaemonSet 또는 Envoy Gateway Deployment.
### 12. network policy controller 충돌
- 기본: K3s 내장 kube-router 기반 enforcer
- Cilium / Calico 도입 시: `--flannel-backend=none` + `--disable-network-policy` + `--disable=servicelb`
- 도입 계획은 클러스터 bootstrap 결정 사항 (리빌드 없이 swap 불가에 가까움)
### 13. embedded registry mirror (Spegel): 명시적 opt-in + 네트워크 요구사항
- 기본 **비활성**
- 활성화 방법: `/etc/rancher/k3s/config.yaml``embedded-registry: true` + `registries.yaml`에 mirror 설정
- **네트워크 요구사항** (공식): 모든 노드가 서로 **TCP 5001 (p2p gossip) + TCP 6443 (local registry + supervisor)**에 도달 가능해야 한다. firewall / security group에서 해당 포트 오픈 필수.
- 활성화 대상:
- airgap / 반-airgap 환경
- 이미지 pull bottleneck이 심한 대규모 배포
- external registry 의존을 낮춰야 하는 환경
- 클러스터 범위 기능이므로 **모든 server/agent에 동일 적용**
### 14. 이미지 import / airgap 전략
- 평상시: registry pull (internal mirror 선호)
- airgap: `/var/lib/rancher/k3s/agent/images/*.tar{,.zst,.gz}` 사용, import 절차를 runbook에 명시
- 이미지 import는 agent startup 때만 로드됨 → 런타임 교체는 re-push 필요
### 15. K3s version gating을 항상 확인
다음 기능은 버전에 따라 동작/옵션이 바뀌므로, 업그레이드 전 CHANGELOG 확인 필수:
- embedded registry mirror (Spegel)
- image pre-import
- `HelmChartConfig` schema
- `disable-helm-controller` 동작
- etcd snapshot / S3 backup 옵션
### 16. K3s-specific 예외는 component 문서보다 먼저 확정
이 문서에서 박고 내려가야 하는 결정:
- traefik 유지/비활성
- servicelb 유지/비활성
- local-storage 유지 범위 (env별)
- metrics-server 유지
- network policy controller 선택
- embedded registry mirror 사용 여부
그 다음에 keycloak / vault / minio / ingress / storage 문서로 내려간다.
### 17. `kubectl apply --server-side` 기본 사용
K3s도 SSA 지원. ArgoCD / Flux / CI 모두 `--server-side --field-manager=<id>` 기본. last-applied-configuration annotation 2MB 한계 회피 + multi-controller ownership 명시.
### 18. etcd snapshot은 K3s 고유 메커니즘 사용
- embedded etcd면 `k3s etcd-snapshot` CLI 또는 `--etcd-snapshot-*` config
- S3 업로드 설정은 `/etc/rancher/k3s/config.yaml`에 선언
- 외부 datastore(PostgreSQL/MySQL) 사용 시 backup은 해당 DB 레이어에서 따로
## 현재 스택 기본 권장안 (prod)
- `traefik`: disable, ingress-nginx + cert-manager로 교체
- `servicelb`: disable, MetalLB (L2 또는 BGP)로 교체
- `local-storage`: disable (prod), dev/staging 만 유지. Longhorn으로 교체
- `metrics-server`: keep (HPA 전제)
- `network policy`: 현 단계 kube-router 유지, Cilium 도입은 별 RFC
- `embedded registry mirror`: off (현재 airgap 아님), 옵션으로 남김
- `etcd snapshot`: S3 업로드 활성, 6시간 주기, 72시간 retention
- `HelmChartConfig`: traefik 유지 경로를 쓰지 않으므로 현재 미사용
- apply 방식: `kubectl apply --server-side --field-manager=argocd`
## 프로젝트 기준 요약
- K3s 전용 규칙은 별도 문서
- packaged component 직접 수정 금지 (HelmChartConfig / disable만)
- `manifests/`는 SoT 아님
- critical config는 Git 단일 파일, 서버 간 동일
- local-path는 dev/test만
- embedded registry mirror는 TCP 5001 + 6443 reachability가 전제
- prod에서 traefik/servicelb/local-storage 전부 disable이 기본
- Server-Side Apply가 GitOps 기본
+227
View File
@@ -0,0 +1,227 @@
# Keycloak 기준
## 목적
이 문서는 Kubernetes 환경에서 Keycloak 26+ (Quarkus distribution)을 1000+ 서비스의 ID 브로커로 운영하기 위한 기준을 고정한다.
- 빌드/실행 두 단계(`kc.sh build``kc.sh start --optimized`)를 전제한다
- Hostname v2, proxy-headers, management port 9000, Infinispan 캐시를 명시한다
- 단일 Deployment 수제 배포 대신 **Keycloak Operator**를 1차 권장 경로로 둔다
- DB / 캐시 / probe / Ingress / RealmImport 를 YAML이 아닌 "설계 결정"으로 먼저 고정한다
- auth-server(도메인 위임)와 Keycloak(IdP)의 ownership 경계를 분리한다
## 공식 의미 (Keycloak 26+ 기준)
- 운영 실행 방식은 **두 단계**다. `kc.sh build`가 Quarkus augmentation을 수행해 optimized 이미지를 만들고, `kc.sh start --optimized`가 그 이미지를 기동한다. 빌드 시 configuration은 런타임에 변경 불가능하다.
- **`--proxy` 옵션은 v24에서 deprecated, v26에서 제거되었다.** 대체는 `--proxy-headers=xforwarded` 또는 `--proxy-headers=forwarded`다.
- **Hostname v2**가 기본값이며 `--hostname`은 full URL을 받는다. v24+ 이후 `hostname-url`, `hostname-path`, `hostname-port`는 제거되었다. admin 전용 주소는 `--hostname-admin`으로 지정한다.
- `--hostname-strict`의 production 기본값은 `true`다. `--hostname-backchannel-dynamic`은 기본 `false`다.
- HTTPS 종료를 Ingress/LB가 하면 Keycloak은 `KC_HTTP_ENABLED=true`로 HTTP를 수신한다.
- DB는 `KC_DB=postgres`, `KC_DB_URL`**JDBC URL**(`jdbc:postgresql://host:5432/db`) 형식이다.
- **Management interface는 기본 포트 `9000`**에서 제공되고, `/health`, `/health/started`, `/health/ready`, `/health/live`, `/metrics`를 호스팅한다. Pod probe와 Prometheus scrape는 모두 9000 대상이다.
- Production cache type 기본은 `ispn`(Infinispan distributed). **cache-stack 기본값이 `kubernetes`(DNS_PING)에서 v25부터 `jdbc-ping`으로 바뀌었다.** Operator가 관리하는 StatefulSet은 Raft-less 클러스터링을 jdbc-ping으로 수행한다.
- Operator가 생성하는 워크로드는 **StatefulSet**이다 (pod ordering이 Infinispan discovery와 맞물린다). 사용자 수제 YAML에서도 Operator 경로가 1차 권장이다.
- `KeycloakRealmImport` CR은 Keycloak server가 준비된 후 realm JSON을 server side로 import하는 1회성 Job을 생성한다.
## 기본 규칙
### 1. 운영 실행은 `start --optimized` 두 단계
빌드 단계에서 feature/db/health/metrics를 굽고, 실행 단계에서 runtime config만 주입한다.
기본:
- Dockerfile에서 `RUN /opt/keycloak/bin/kc.sh build`로 optimized 이미지 생성
- 컨테이너 CMD는 `kc.sh start --optimized`
- runtime-only config: hostname, DB URL/credential, log level
기본 금지:
- `start-dev` 운영 사용
- `start` 단독 실행(build 없이 매 기동마다 augmentation)
### 2. `--proxy-headers` 사용, `--proxy` 금지
Keycloak 26에서 `--proxy`는 제거되었다.
기본:
- HTTPS 종료 proxy 뒤: `KC_PROXY_HEADERS=xforwarded` (nginx, Traefik, ingress-nginx 등)
- RFC 7239 지원 proxy: `KC_PROXY_HEADERS=forwarded`
- proxy가 Host / X-Forwarded-* 를 **덮어쓰도록** 고정
기본 금지:
- `KC_PROXY=edge|reencrypt|passthrough` 등 legacy 옵션
### 3. Hostname v2: full URL로 고정
기본:
- `KC_HOSTNAME=https://auth.example.com` (full URL)
- Admin Console 분리: `KC_HOSTNAME_ADMIN=https://admin-auth.example.com`
- `KC_HOSTNAME_STRICT=true` (production 기본 유지)
- `KC_HOSTNAME_BACKCHANNEL_DYNAMIC=false` (기본값; 다중 cluster federation일 때만 true 검토)
기본 금지:
- 제거된 옵션 사용: `KC_HOSTNAME_URL`, `KC_HOSTNAME_PATH`, `KC_HOSTNAME_PORT`
- hostname 없이 요청 헤더에서 해석되도록 방치
### 4. HTTPS는 Ingress/LB에서 종료, Pod는 HTTP
Pod 내부에서 TLS 재암호화가 필요 없으면 Pod는 HTTP로 수신한다.
기본:
- `KC_HTTP_ENABLED=true`, `KC_HTTP_PORT=8080`
- Ingress가 TLS 종료 + proxy-header 주입
- passthrough TLS가 필요한 보안 요구가 있을 때만 `KC_HTTPS_*` 경로 채택
### 5. DB는 외부 PostgreSQL + JDBC URL
기본:
- `KC_DB=postgres`
- `KC_DB_URL=jdbc:postgresql://keycloak-db-rw:5432/keycloak` (CloudNativePG `-rw` RW endpoint 권장)
- `KC_DB_USERNAME`, `KC_DB_PASSWORD` → Secret `secretKeyRef`
- Keycloak schema와 auth-server schema는 **다른 DB 또는 다른 database**로 분리
기본 금지:
- 내장 H2 (`dev-file`, `dev-mem`) 운영
- root/superuser credential 사용
- Keycloak DB에 auth-server migration 수행
### 6. Management port 9000은 외부 비공개
기본:
- Pod containerPort 9000 (`KC_HTTP_MANAGEMENT_PORT=9000`)
- Service에 9000 expose하되 Ingress 대상 제외
- probe는 9000 대상: `/health/started`, `/health/ready`, `/health/live`
- Prometheus scrape는 내부 scraper가 9000/`/metrics`에 직접 접근
### 7. Probe timing은 Keycloak 기동 특성에 맞춘다
Keycloak은 JVM + Quarkus + Infinispan + DB migration으로 cold start가 30~120초다.
기본:
- `startupProbe`: `/health/started`, `periodSeconds: 5`, `failureThreshold: 60` → 최대 5분 유예
- `readinessProbe`: `/health/ready`, `periodSeconds: 10`, `failureThreshold: 3`
- `livenessProbe`: `/health/live`, `periodSeconds: 30`, `failureThreshold: 3`, `initialDelaySeconds: 60`
### 8. Cache: Infinispan + 버전별 stack 기본값 인지
v25+ 기본 stack은 **`jdbc-ping`**이다. DB를 discovery 매체로 쓰므로 headless service / ServiceAccount RBAC가 필요 없다.
기본:
- Operator 관리 클러스터: `KC_CACHE=ispn`, `KC_CACHE_STACK=jdbc-ping` (명시)
- 수제 StatefulSet에서 headless service 경유 discovery를 쓰려면 `KC_CACHE_STACK=kubernetes` (DNS_PING) 선택
- local mode 운영 금지 (`KC_CACHE=local`은 single replica 테스트 전용)
### 9. Operator 경로를 1차 권장으로
1000+ 서비스 규모에서 realm import, CR 기반 롤아웃, cache stack 자동 설정, StatefulSet 관리를 Operator가 담당한다.
기본:
- `Keycloak` CR + `KeycloakRealmImport` CR 조합
- OLM(OperatorHub) 또는 공식 manifest 설치
- 수제 StatefulSet 유지보수는 Operator 기능이 부족할 때만 허용
### 10. 공개 경로 최소화
Ingress에 허용하는 기본 경로:
- `/realms/` — OIDC / SAML endpoint
- `/resources/` — Keycloak theme / JS
- `/.well-known/` — OIDC discovery, JWKS
- `/js/` — Keycloak adapter JS (필요 시)
기본 금지:
- `/admin/` 외부 공개 (별도 admin host 경유)
- `/metrics`, `/health*` 외부 공개
- `/` 전체 wildcard 공개
### 11. Admin Console은 별도 host로 분리
Admin 접근은 일반 SSO host와 다른 경로로 둔다.
기본:
- `KC_HOSTNAME_ADMIN=https://admin-auth.example.com`
- Admin host는 사내 IP 화이트리스트 / VPN / OIDC forward-auth로 추가 보호
- production에서 `/admin/` 을 SSO 공용 host에 노출 금지
### 12. Realm은 `KeycloakRealmImport` CR로 선언적 관리
기본:
- realm JSON은 Git에 보관
- `KeycloakRealmImport` CR이 Job을 생성해 server-side import
- secret이 들어가는 identity provider client secret은 Vault에서 주입
기본 금지:
- Admin REST / kcadm.sh를 CI/CD pipeline이 직접 호출해 상태 변경
- realm export 파일을 Pod 내부 파일로 배포
### 13. High Availability: replicas ≥ 2 + PDB + topologySpread
Operator는 `instances` 필드로 replica를 제어한다.
기본:
- `instances: 3` (odd quorum 아님 — cache replication 안정성)
- `PodDisruptionBudget minAvailable: 2`
- `topologySpreadConstraints`로 node/zone 분산
### 14. Sticky session은 성능 최적화 옵션
Infinispan이 session을 복제하므로 필수는 아니지만, login flow 중간 redirect 지연을 줄인다.
기본:
- Ingress controller에서 `AUTH_SESSION_ID` cookie affinity
- Service `sessionAffinity: ClientIP`는 2차 선택지
### 15. Security context: Restricted PSS 준수
기본:
- `runAsNonRoot: true`, `runAsUser: 1000`
- `readOnlyRootFilesystem: true` (Keycloak은 `/opt/keycloak/data` 만 writable 요구; emptyDir 마운트)
- `allowPrivilegeEscalation: false`, `capabilities.drop: [ALL]`
- `seccompProfile: RuntimeDefault`
### 16. Resource 요청은 JVM 특성 반영
기본 단일 replica:
- requests: `cpu: 500m`, `memory: 1Gi`
- limits: `cpu: 2`, `memory: 2Gi`
- JVM: `JAVA_OPTS_APPEND=-XX:MaxRAMPercentage=70 -XX:InitialRAMPercentage=50`
login throughput 요구가 높으면 replica 수평 확장 우선 (JVM heap 수직 확장 2차).
### 17. Observability
기본:
- `KC_METRICS_ENABLED=true`, `KC_HEALTH_ENABLED=true`
- `ServiceMonitor` 또는 `PodMonitor`로 9000/`/metrics` scrape
- event metric (login failure, token issuance)은 필요한 것만 활성화 (high cardinality 방지)
### 18. DB credential / admin credential은 Vault 경유
기본:
- `KC_DB_PASSWORD`: VSO `VaultDynamicSecret`(postgres dynamic role) 또는 `VaultStaticSecret` → K8s Secret 동기화
- Bootstrap admin (`KEYCLOAK_ADMIN`, `KEYCLOAK_ADMIN_PASSWORD`): 최초 기동 후 제거, 실 운영 admin은 realm-managed
기본 금지:
- Secret을 Git에 평문 저장
- 환경변수 default value로 credential 하드코딩
### 19. 현재 스택 기본 권장안
- 배포: Keycloak Operator + `Keycloak` CR + `KeycloakRealmImport` CR
- 워크로드: StatefulSet (Operator 생성)
- Service: ClusterIP (9000, 8080)
- Ingress: SSO host + Admin host 분리
- DB: CloudNativePG PostgreSQL cluster + Vault dynamic secret
- Cache: `ispn` + `jdbc-ping`
- Probe: 9000 management port
- Replicas: 3 + PDB + topologySpread
## 프로젝트 기준 요약
- Keycloak 26+ Quarkus distribution, `start --optimized` 두 단계
- `--proxy-headers` 사용, `--proxy` 금지
- Hostname v2 full URL, admin host 분리, strict=true 유지
- DB: 외부 PostgreSQL, JDBC URL, Vault credential
- Management port 9000 내부 전용, probe / metrics 대상
- Infinispan `ispn` + `jdbc-ping` (v25+)
- Operator 경로 1차 권장 (CR로 realm import 포함)
- Admin Console 별도 host, 공개 경로는 `/realms/`, `/resources/`, `/.well-known/`
- Replicas ≥ 2 + PDB + topologySpread + Restricted PSS
+325
View File
@@ -0,0 +1,325 @@
# Kustomize 기준
## 목적
Kustomize는 Kubernetes 리소스를 **template-free**로 조합하고 환경별 차이를 overlay로 표현하는 도구다.
1000+ 서비스 prod 스케일에서 기본 배포 도구로 사용하며, Helm 차트는 특정 플랫폼 컴포넌트(Prometheus Operator, cert-manager 등)에만 제한적으로 쓴다.
목표:
- base / overlay / component 세 축을 명확히 구분한다
- `commonLabels`의 selector immutability 함정을 피한다
- `kubectl apply --server-side`를 전제로 field manager ownership을 관리한다
- GitOps (ArgoCD/Flux) 또는 CI `kubectl apply -k` 어느 쪽이든 같은 원본을 쓴다
## 공식 의미 (근거)
- 공식 문서: `https://kubernetes.io/docs/tasks/manage-kubernetes-objects/kustomization/`, `https://kubectl.docs.kubernetes.io/references/kustomize/`
- `kubectl kustomize <dir>` 렌더, `kubectl apply -k <dir>` apply, `kubectl diff -k <dir>` diff.
- **Kustomize v5+ `labels:` 필드**: label을 리소스에 추가하되 **기본적으로 selector에 주입하지 않는다** (`includeSelectors: false`). 공식 문서 인용: *"A field that allows adding labels without also automatically injecting corresponding selectors. This can be used instead of the `commonLabels` field, which always adds selectors."*
- **`commonLabels`**: 모든 리소스의 `metadata.labels` + `spec.selector.matchLabels` + Pod template labels에 주입된다. Deployment/StatefulSet의 `selector.matchLabels`**immutable** 이므로, 이미 apply된 리소스에 `commonLabels`로 label을 추가하면 `field is immutable` 에러로 apply 실패.
- **`components:`** (v4+): 재사용 가능한 cross-cutting overlay 단위. `kind: Component`. resource 집합 + patch 집합을 하나의 단위로 묶어 여러 overlay에서 `components:` 키로 참조.
- `configMapGenerator` / `secretGenerator`: 이름 끝에 hash suffix가 자동으로 붙어 rollout trigger. `generatorOptions.disableNameSuffixHash: true`로 비활성 가능.
- `patches:` (v5 권장): `target:` 선택 + `patch:` inline 또는 `path:` 파일. strategic merge / JSON patch 양쪽 지원.
- `images:`: image name/tag/digest 교체.
- `replicas:`: resource별 replica 수 override.
- `namespace:` / `namePrefix:` / `nameSuffix:`: overlay에서 공통 변환.
- Server-Side Apply (`kubectl apply --server-side --field-manager=<id> -k`)가 GitOps 기본.
## 기본 규칙
### 1. Kustomize 디렉터리가 선언형 source of truth
- 렌더: `kubectl kustomize <dir>`
- diff: `kubectl diff --server-side -k <dir>`
- apply: `kubectl apply --server-side --field-manager=<ci-id> -k <dir>`
`kubectl apply -f` 단일 파일 apply는 금지 (bootstrap 예외 제외).
### 2. base는 환경 중립
허용:
- Deployment/StatefulSet/DaemonSet/Job/CronJob 기본 shape
- `app.kubernetes.io/{name,instance,component,part-of,managed-by}` (`version`은 overlay에서 image tag와 함께 주입)
- 공통 container spec (resources, probes, securityContext)
- 공통 volume mount / ConfigMap reference
금지:
- replicas 고정값 (overlay `replicas:`에서 결정)
- 환경별 host / domain / issuer 이름
- 환경별 secret / ConfigMap 이름
- 환경별 resources requests/limits
- `example.com/environment` label (overlay에서 `labels:`로 주입)
### 3. overlay는 환경 차이만, patches는 파일로 분리
overlay 한 디렉터리의 `kustomization.yaml`은 짧아야 한다. diff가 몇 백 줄을 넘으면 base 설계 실패 신호.
권장 구조:
```
overlays/prod/
kustomization.yaml
patches/
auth-replicas.yaml
auth-resources.yaml
auth-topology-spread.yaml
ingress-host.yaml
postgres-storage.yaml
```
### 4. 디렉터리 구조는 base / components / overlays 3축
```
k8s/
base/
app/units/<domain>/<service>/
managing/<job>/
plugins/<platform>/
components/
<reusable-cross-cutting>/
overlays/
<env>/[region/]
```
`components/`는 "Kustomize Components"로, 여러 overlay에서 재사용.
### 5. `commonLabels` 금지, `labels:` 사용
신규 코드에서는 `commonLabels` 사용을 금지한다.
```yaml
# DO
labels:
- pairs:
example.com/environment: prod
example.com/region: kr-main
includeSelectors: false
includeTemplates: true
```
이유:
- `commonLabels``selector.matchLabels`에 자동 주입 → live Deployment/StatefulSet apply 시 `field is immutable` 실패
- `labels:``includeSelectors: false`가 기본 → safe
- `includeTemplates: true`로 Pod template labels에는 전파되므로 관찰성은 유지
기존 `commonLabels` 사용 코드는 migration plan을 세워 교체. selector에 이미 들어간 label이 있다면 해당 리소스를 **재배포** (delete + recreate) 없이는 변경 불가.
### 6. selector에는 불변 3종만
overlay에서 selector를 건드리지 않는다. selector에 허용되는 label은:
- `app.kubernetes.io/name`
- `app.kubernetes.io/instance`
- `app.kubernetes.io/component`
이 3종은 base에서 고정. overlay가 `labels:`로 추가하는 label은 반드시 `includeSelectors: false`.
### 7. `patches:` (v5 스타일) 사용, `patchesStrategicMerge` / `patchesJson6902` 금지
```yaml
patches:
- target:
kind: Deployment
name: auth
path: patches/auth-resources.yaml
- target:
kind: Ingress
name: auth-public
patch: |-
- op: replace
path: /spec/rules/0/host
value: auth.example.com
```
이유:
- 단일 키로 strategic merge + JSON patch 양쪽 지원
- `target:` selector로 여러 리소스에 적용 가능
- 레거시 `patchesStrategicMerge` / `patchesJson6902`는 v5에서 deprecated (여전히 작동하지만 신규 사용 금지)
### 8. `components:`로 cross-cutting 재사용
multiple overlay에서 공통으로 끼워야 하는 변경(예: mTLS 활성화, sidecar 주입, monitoring label 추가)은 component로.
```
components/
with-istio-sidecar/
kustomization.yaml # kind: Component
patches/
inject-sidecar.yaml
with-service-monitor/
kustomization.yaml
service-monitor.yaml
with-pdb-tier1/
kustomization.yaml
pdb-patch.yaml
```
overlay에서:
```yaml
components:
- ../../components/with-service-monitor
- ../../components/with-pdb-tier1
```
### 9. `namePrefix` / `nameSuffix`는 꼭 필요할 때만
리소스 이름이 바뀌면 ConfigMap/Secret 참조 (`envFrom`, `volumes.configMap.name`)도 모두 바뀐다. namespace 격리가 기본이고, 같은 cluster 안에서 같은 이름 리소스를 여러 번 생성할 때만 prefix/suffix를 쓴다.
### 10. generator 기준
- `configMapGenerator`: 비민감 설정만. 기본 hash suffix로 rollout 자동 트리거.
- `secretGenerator`: 로컬/테스트/bootstrap 에만. prod secret은 External Secrets Operator / Vault Secrets Operator / SealedSecrets로 관리.
- `generatorOptions.disableNameSuffixHash: true`는 GitOps 외부 컨슈머가 이름을 하드코딩해야 할 때만 (예외).
### 11. `images:`로 image tag/digest 고정
```yaml
images:
- name: registry.example.com/auth
newTag: "1.24.3"
- name: registry.example.com/keycloak
digest: "sha256:abcd1234..."
```
- prod에서는 digest 권장 (tag는 mutable)
- CI가 overlay의 `images:` 섹션을 빌드 후 새 digest로 patch (kustomize edit set image)
### 12. `replicas:`는 overlay에서 resource별 값 주입
```yaml
replicas:
- name: auth
count: 6
- name: keycloak
count: 3
```
HPA 주도 rollout 환경에서는 `replicas:` override가 HPA와 충돌할 수 있다. HPA 활성 리소스는 base `replicas`를 HPA `minReplicas`와 일치시키고 overlay에서는 건드리지 않는다.
### 13. `kubectl apply --server-side --field-manager=<id>` 기본
- ArgoCD: field manager `argocd-controller`
- Flux: field manager `kustomize-controller`
- CI manual: field manager `ci-<pipeline-id>`
field manager 이름을 환경별로 통일해야 `managedFields` 충돌이 예측 가능해진다.
### 14. render 전 검증
CI가 아래를 순서대로 실행:
```bash
kubectl kustomize overlays/prod > /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
```
- kubeconform / kubeval: schema validation
- kyverno / OPA Gatekeeper: policy validation (post-render)
- conftest: opa policy bundle 실행
### 15. base는 overlay를 모른다
공식 원칙. base `kustomization.yaml`은 overlay에서만 의미 있는 설정(환경 host / issuer / region label)을 전제하지 않는다. 위반 시 base가 더 이상 재사용 가능한 unit이 아니다.
### 16. Kustomize를 템플릿 엔진으로 남용하지 않는다
분기 / 조건 / 반복이 필요하면:
1. 리소스 분리
2. component 도입
3. overlay 추가
4. (마지막 수단) Helm / jsonnet / cdk8s
Kustomize는 patch/overlay 도구다. Go template이 아니다.
### 17. scripts는 Kustomize 보조, 대체 아님
`scripts/render.sh`, `scripts/diff.sh`, `scripts/apply.sh`는 Kustomize 명령의 wrapper에 그치고 overlay 구조를 우회하지 않는다.
### 18. `resources:` vs `bases:` — v5에서는 `resources:` 통일
v2.1에서 `bases:``resources:`로 통합됨. 신규 파일에서 `bases:` 금지.
### 19. overlay에서 StatefulSet PVC retention 변경 주의
`persistentVolumeClaimRetentionPolicy`는 StatefulSet spec 필드 (GA 1.27). 환경별로 값이 다르면 overlay patch로 조정하되 prod는 기본 `{whenDeleted: Retain, whenScaled: Retain}` 유지.
## 추천 폴더 구조
```text
k8s/
base/
app/
kustomization.yaml
units/
identity/
auth/
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/
scripts/
render.sh
diff.sh
apply.sh
validate.sh
```
## 프로젝트 기준 요약
- Kustomize v5 문법 기준, `commonLabels` 금지, `labels:` 사용
- `patches:` 단일 키, `target:` + `path:` 또는 `patch:` inline
- `components:`로 cross-cutting 재사용
- selector에는 불변 3종만 (name / instance / component)
- generator는 configMap만 기본, secret은 External Secrets
- `kubectl apply --server-side --field-manager=<id>` 전제
- render + schema + policy 검증을 CI에서 강제
- base / components / overlays 3축 디렉터리
- overlay diff는 짧아야 한다 (base 재작성 금지)
+238
View File
@@ -0,0 +1,238 @@
# MinIO 기준
## 목적
이 문서는 Kubernetes 환경에서 MinIO를 1000+ 서비스의 S3 호환 object storage로 운영하기 위한 기준을 고정한다.
- MinIO Operator + **Tenant CRD** (`minio.min.io/v2`)를 기본 배포 모델로 둔다
- Erasure coding 최소 요건 (`servers × volumesPerServer ≥ 4`)을 명시한다
- KES sidecar + Vault transit backend로 SSE-KMS를 구성한다
- STS + OIDC (Keycloak)로 서비스 인증을 수행한다
- 버전 관리 / object lock / replication / lifecycle rule을 운영 필수 요소로 둔다
## 공식 의미 (MinIO Operator + Tenant CRD 기준)
- MinIO Operator는 `minio.min.io/v2` API group의 **Tenant** CR을 watch하여 StatefulSet, Service, PVC, 인증서를 자동 생성한다.
- Tenant는 **namespace당 1개**를 권장한다 (namespace = 소유/정책/쿼터 경계).
- MinIO는 erasure coding을 사용한다. **`servers × volumesPerServer`는 최소 4이어야** 기동한다. EC:N parity (기본 EC:4 ~ EC:8)는 parity drive 수를 결정하며, 장애 허용 drive 수 = parity 수.
- MinIO pool은 불변이다(immutable). pool 내 servers / volumesPerServer는 Tenant 생성 후 변경 불가. 용량 확장은 **새 pool 추가**로 수행.
- **Health endpoints**:
- `/minio/health/live` — 프로세스 liveness (인증 없음)
- `/minio/health/cluster`**write quorum** 기준 (rolling update 중 false 가능, readiness 비권장)
- `/minio/health/cluster/read`**read quorum** 기준 (rolling update 허용, readiness 권장)
- **Metrics endpoints**:
- `/minio/v2/metrics/cluster` — cluster-wide (기본 Bearer token 필요)
- `/minio/v2/metrics/node` — per-node
- `/minio/v2/metrics/bucket/api/<bucket>` — bucket API metrics
- `mc admin prometheus generate` 로 scrape config + token 생성. 또는 `prometheusAuthType: public` 설정으로 unauth scrape 허용.
- **KES** (Key Encryption Service)는 별도 sidecar/Deployment로 Vault transit backend와 통신해 SSE-KMS / SSE-S3 per-object key를 발급한다.
- MinIO **service account**는 root access key의 하위 derived credential이다(IAM role 개념 아님). 앱은 service account만 사용하고 root는 bootstrap 전용.
- **Object lock**은 bucket 생성 시점에 활성화해야 하며, `GOVERNANCE` (bypass 권한자 우회 가능) vs `COMPLIANCE` (root도 우회 불가) 두 모드.
- **Site replication**은 최대 16개 MinIO 클러스터를 동기화한다 (IAM, bucket config, object 전체). **Bucket replication**은 특정 bucket만 대상.
- Console은 9090 port, API는 9000 port.
## 기본 규칙
### 1. 배포는 MinIO Operator + Tenant CR
기본:
- `kubectl apply -k "https://github.com/minio/operator?ref=v6.0.4"` 또는 Helm `minio-operator` + `tenant` chart
- Tenant CR로 pool, credential, TLS, KES, logging, monitoring 선언
- 수제 StatefulSet 운영 금지
### 2. Tenant는 namespace당 1개
기본:
- `minio-prod` namespace에 Tenant 1개
- 테넌트 간 격리가 필요하면 namespace를 복수 생성
- 동일 namespace에 다른 워크로드와 공존 금지
### 3. Erasure coding 요건: `servers × volumesPerServer ≥ 4`
기본 topology 후보:
| servers | volumesPerServer | 총 drive | 기본 EC parity | 장애 허용 drive |
|---------|------------------|----------|----------------|-----------------|
| 4 | 4 | 16 | EC:4 | 4 |
| 4 | 8 | 32 | EC:4 | 4 |
| 8 | 4 | 32 | EC:4 | 4 |
| 8 | 8 | 64 | EC:4 ~ EC:8 | 4 ~ 8 |
기본:
- 최소 `4 × 4 = 16` drive 시작 (prod)
- `MINIO_STORAGE_CLASS_STANDARD=EC:4` 이상, critical data는 `EC:8`
- parity 증가 = 용량 감소 + 신뢰성 증가
기본 금지:
- `servers × volumesPerServer < 4` → Tenant가 기동 실패
### 4. Pool은 immutable — 확장은 새 pool 추가
기본:
- 초기 pool의 `servers`, `volumesPerServer`, `volumeClaimTemplate.size`는 평생 고정
- 용량 부족 시 `spec.pools[]``pool-1`, `pool-2` 추가
- pool 간 데이터 rebalance는 `mc admin rebalance start`
### 5. StorageClass 명시 (local-path 금지)
기본:
- prod: `volumeClaimTemplate.spec.storageClassName: ceph-rbd-retain` / `ebs-gp3` / `local-volume-xfs` (명시적)
- 파일시스템은 `xfs` 권장 (MinIO는 ext4보다 xfs에 최적화)
- `reclaimPolicy: Retain` + PVC 삭제 가드 (Tenant 삭제 시 데이터 소실 방어)
기본 금지:
- k3s local-path prod 사용
- default StorageClass fallback
### 6. Credential: root는 bootstrap 전용, 앱은 service account
기본:
- `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 발급
- service account는 최소 권한 policy 바인딩
### 7. TLS는 기본 활성화
기본:
- `spec.requestAutoCert: true` → Operator가 Kubernetes CSR로 인증서 자동 발급 (MinIO 자체 CA)
- 사내 PKI 사용 시 `spec.externalCertSecret` + cert-manager Certificate
- API (9000), Console (9090), KES 전부 TLS
### 8. KES + Vault transit으로 SSE-KMS
기본:
- `spec.kes` 필드에 KES 사이드카 spec
- KES는 Vault transit engine을 key store로 사용
- bucket 생성 시 `mc encrypt set sse-kms minio-backup/critical key-id=my-app-key`
- per-object DEK를 KES에서 받아 암호화
기본 금지:
- KES 없이 SSE-S3만 사용 (master key가 MinIO 내부에만 존재 → 분실 위험)
- KES가 local filesystem key store 사용 (prod)
### 9. Versioning + Object Lock은 critical bucket 기본값
기본:
- 금융/감사 데이터: `mc version enable` + Object Lock `COMPLIANCE` 모드
- 백업 bucket: Object Lock `GOVERNANCE` + retention 30일
- 일반 app bucket: versioning만 (실수 복구)
- lifecycle rule로 오래된 버전 자동 정리 (`mc ilm add --expire-noncurrent-days 90`)
### 10. Replication: site vs bucket
기본:
- 전체 IAM/config 동기화 필요: **site replication** (`mc admin replicate add`)
- 특정 bucket만 cross-region 복제: **bucket replication** (`mc replicate add`)
- async replication 특성 인지 (RPO > 0)
- `mc mirror`는 DR 전략 아님 — 일회성 migration/sync 용도
### 11. STS + OIDC (Keycloak) 통합
기본:
- Keycloak에 `minio` client 생성 (confidential)
- MinIO 설정:
```
mc admin config set ALIAS identity_openid \
config_url="https://auth.example.com/realms/platform/.well-known/openid-configuration" \
client_id="minio" \
client_secret="..." \
claim_name="policy" \
scopes="openid,profile,email"
```
- 앱은 `AssumeRoleWithWebIdentity`로 JWT → 임시 STS credential 교환
- MinIO policy에 JWT `policy` claim으로 매핑
### 12. Health probe: read quorum을 readiness로
기본:
- `livenessProbe`: `/minio/health/live` (프로세스 생존)
- `readinessProbe`: `/minio/health/cluster/read` (read quorum) — rolling update 허용
- `startupProbe`: `/minio/health/live` + `failureThreshold` 넉넉하게
기본 금지:
- `readinessProbe`로 `/minio/health/cluster` (write quorum) 사용 → rolling update 시 전체 pod unready
### 13. Metrics: 내부 scrape 전용
기본:
- `spec.prometheus` 또는 `prometheusAuthType: public` (내부 network만)
- 또는 `mc admin prometheus generate` 로 scrape token 발급 후 `bearerTokenSecret`
- ServiceMonitor는 `/minio/v2/metrics/cluster` 대상
- 외부 Ingress 공개 금지
### 14. API Ingress — Console은 내부 전용
기본:
- API (9000): 필요한 경우 Ingress로 공개 (S3 API host: `s3.example.com`)
- Console (9090): 내부/운영자 전용, 외부 공개 금지 (별도 host + IP whitelist + OIDC forward-auth)
- Console을 공개하면 root credential UI 로그인 표면 확장
### 15. Lifecycle rule로 용량 관리
기본:
- 로그 bucket: 30~90일 expire
- tmp / cache bucket: 7일 expire
- versioning enabled bucket: noncurrent version 90일 expire
- incomplete multipart upload: 7일 abort (`mc ilm add --expire-incomplete-upload-days 7`)
### 16. 로깅
기본:
- `spec.log.audit` → bucket에 audit log 저장 또는 webhook으로 외부 전송
- stdout으로 console log → fluent-bit / Loki 수집
- audit log는 Object Lock bucket에 저장해 변조 방지
### 17. SecurityContext + Resource
기본:
- `spec.securityContext`: `runAsNonRoot: true`, `runAsUser: 1000`, `fsGroup: 1000`, `runAsGroup: 1000`
- Restricted PSS 준수
- 단일 pod resource (4 server cluster 기준):
- requests: `cpu: 500m`, `memory: 2Gi`
- limits: `cpu: 4`, `memory: 8Gi`
- 데이터 규모/동시 요청 수에 따라 조정
### 18. Anti-affinity + topologySpread
기본:
- `podAntiAffinity`: hostname 기준 required (같은 node에 MinIO pod 복수 금지)
- `topologySpreadConstraints`: zone 분산
- EC:4 + 4-zone = 1 zone 장애 허용
### 19. Console은 분리, auth는 OIDC
기본:
- Console endpoint에 `MINIO_IDENTITY_OPENID_*` OIDC 설정
- root credential UI 로그인은 break-glass 전용
- 일반 운영자는 OIDC 로그인 + group → policy 매핑
### 20. 현재 스택 기본 권장안
- 배포: MinIO Operator + Tenant CR (`minio.min.io/v2`)
- Topology: 최소 `4 × 4 = 16` drive, prod는 `8 × 4 = 32` 이상
- EC: `EC:4` 기본, critical data `EC:8`
- StorageClass: 명시적 (xfs, Retain)
- TLS: `requestAutoCert: true`
- KMS: KES sidecar + Vault transit
- 인증: root는 VSO 주입, 앱은 service account, 사용자는 Keycloak OIDC
- Health: live / cluster-read (readiness)
- Metrics: Prometheus bearer-token scrape
- Versioning + Object Lock: critical bucket 기본값
- Replication: site (전체) / bucket (부분) 구분
- Console: 내부 전용
## 프로젝트 기준 요약
- MinIO Operator + Tenant CR 기본 배포
- namespace당 Tenant 1개
- `servers × volumesPerServer ≥ 4` erasure coding 요건
- Pool immutable — 확장은 새 pool
- StorageClass 명시 + xfs 권장 + Retain
- KES + Vault transit으로 SSE-KMS
- Root credential은 Vault → VSO → Secret 경로
- 앱 접근은 service account, 사용자는 Keycloak OIDC STS
- Health: `/minio/health/live` + `/minio/health/cluster/read`
- Metrics: Bearer token scrape
- Console 외부 비공개, API만 필요 시 Ingress
- Versioning + Object Lock + Lifecycle rule로 데이터 보호
- site/bucket replication으로 DR (`mc mirror`는 DR 아님)
+170
View File
@@ -0,0 +1,170 @@
# network / ingress / TLS 기준
## 목적
이 문서는 K3s/Kubernetes(1000+ 서비스) 환경에서
- 어떤 Service 타입을 언제 쓸지
- 외부 공개는 Ingress/Gateway 어디로 할지
- TLS를 어디서 종료할지
- 인증서는 누가 발급·회전할지
- NetworkPolicy로 L3/L4 경계를 어떻게 그을지
를 단일 ground truth로 고정한다.
이 문서의 목표는 다음과 같다.
- 외부 attack surface를 최소화한다
- `ClusterIP`/`NodePort`/`LoadBalancer`/`Ingress`의 역할을 섞지 않는다
- 모든 Ingress는 cert-manager 발급 TLS + HTTPS redirect + HSTS + TLS 1.2+ 기본
- K3s 기본 Traefik을 유지하되 packaged manifest는 수정하지 않는다
- Keycloak/Vault/DB 같은 민감 컴포넌트의 노출 범위를 manifest로 증명한다
## 공식 의미 (근거)
- Service 기본 타입은 `ClusterIP`. 외부 L4 노출은 `NodePort` 또는 `LoadBalancer`, 외부 L7은 Ingress 또는 Gateway API.
- Ingress v1 API는 GA이지만 spec은 frozen 상태이고, 신규 기능(L4, traffic split, header match)은 Gateway API로 이동 중이다.
- Ingress v1은 `spec.ingressClassName` 필드로 컨트롤러를 선택한다. 이전의 `kubernetes.io/ingress.class` annotation은 deprecated이며 1.22에서 공식 deprecation 고지.
- Ingress TLS Secret은 타입이 `kubernetes.io/tls`이고 data key는 `tls.crt`, `tls.key`여야 한다. `spec.tls[].hosts``rules[].host`는 일치해야 한다.
- cert-manager는 `Issuer`/`ClusterIssuer`, `Certificate`, `CertificateRequest`, `Order`, `Challenge` CRD로 구성된다. `Certificate`가 참조하는 `secretName`에 자동으로 `kubernetes.io/tls` Secret이 생성·갱신된다.
- ACME HTTP-01은 public DNS + 80 reachable 필요. DNS-01은 wildcard(`*.example.com`) 발급에 필수이며 DNS provider API credential이 요구된다.
- Traefik v2/v3는 `IngressRoute`(CRD) + `Middleware`(CRD)로 L7 정책(redirect, HSTS, rate-limit, auth)을 체계적으로 구성한다. 기본 Ingress API도 annotation으로 일부 기능을 쓸 수 있다.
- K3s는 Traefik을 packaged component로 설치한다(`/var/lib/rancher/k3s/server/manifests/traefik.yaml`). packaged manifest 직접 수정은 재설치 시 덮어쓰인다. `HelmChartConfig`로 override한다.
- NetworkPolicy는 CNI가 지원해야 enforce된다. K3s 기본 flannel + kube-router policy controller는 v1 NetworkPolicy를 지원한다.
## 기본 규칙
### 1. 기본 Service 타입은 `ClusterIP`
- 내부 통신: `ClusterIP`
- 외부 HTTP/HTTPS: Ingress
- 외부 TCP/UDP L4: `LoadBalancer`(ServiceLB/MetalLB/클라우드 LB 전제)
- `NodePort`는 개발/bootstrap 용도 외 운영 금지. namespace `ResourceQuota.services.nodeports: 0`으로 선제 차단.
### 2. 모든 Service는 named port + `appProtocol`
```yaml
ports:
- name: http
port: 80
targetPort: http
protocol: TCP
appProtocol: http
```
- `name: http|https|grpc|metrics`로 명명.
- `appProtocol` 명시는 Ingress controller/서비스 메시가 L7 처리를 최적화할 수 있게 한다.
- container `ports[].name`과 Service `targetPort`를 이름으로 연결해 포트 번호 drift를 방지.
### 3. Ingress는 `spec.ingressClassName: traefik` 필수
- `kubernetes.io/ingress.class` annotation은 **deprecated**. 어떤 Ingress에도 남기지 않는다.
- 컨트롤러가 여러 개인 클러스터(예: Traefik + internal-only NGINX)는 `IngressClass` 리소스를 만들어 class를 명시한다.
- 기본 클래스는 `ingressclass.kubernetes.io/is-default-class: "true"` annotation으로 한 개만 지정.
### 4. 외부 HTTPS는 cert-manager ClusterIssuer로 발급
- 운영 공인 도메인: `letsencrypt-prod` ClusterIssuer(ACME HTTP-01) 기본.
- Wildcard/internal CA: DNS-01(`letsencrypt-prod-dns`) 또는 Vault PKI issuer.
- Staging 검증: `letsencrypt-staging` ClusterIssuer로 선행 테스트 후 prod 전환.
- Ingress에는 annotation으로 issuer 지정: `cert-manager.io/cluster-issuer: letsencrypt-prod`. cert-manager가 Certificate + Secret을 자동 생성·갱신한다.
- Certificate CRD를 명시적으로 선언하는 방식도 허용(공유 Secret 재사용, 세밀한 `duration`/`renewBefore` 제어 필요 시).
### 5. HTTPS redirect + HSTS + TLS 1.2+ 기본
- 모든 외부 Ingress는 HTTP → HTTPS 영구 리다이렉트.
- HSTS: `max-age=31536000; includeSubDomains; preload` 기본.
- TLS minVersion: `VersionTLS12`(가능하면 1.3). 취약 cipher(RC4, 3DES) disable.
- Traefik에서는 `Middleware`(redirectScheme, headers) + `TLSOption` CRD로 정책을 선언. Ingress annotation 방식 예:
- `traefik.ingress.kubernetes.io/router.entrypoints: websecure`
- `traefik.ingress.kubernetes.io/router.middlewares: default-hsts@kubernetescrd,default-https-redirect@kubernetescrd`
- `traefik.ingress.kubernetes.io/router.tls: "true"`
### 6. Ingress host는 환경별로 분리, wildcard 남용 금지
- dev/staging/prod별 호스트 분리(`auth.dev.example.com`, `auth.staging.example.com`, `auth.example.com`).
- Wildcard 인증서는 플랫폼 수준 Certificate로 관리하고 서비스 Ingress가 `secretName` 재사용.
- `defaultBackend`(host 없음) 금지. host가 명시된 rule만 허용.
### 7. 외부 공개 범위 = "반드시 공개해야 하는 path"만
- Keycloak: `/realms/`, `/resources/`, `/.well-known/`만 노출. `/admin/`, `/metrics`, `/health`는 공개 금지.
- 관리 포트(Keycloak 9000, Vault 8201, Postgres 5432, Redis 6379)는 Ingress 경유 금지.
- 내부 도구(Argo CD, Grafana, Kibana)는 VPN/zero-trust proxy(예: Pomerium, cloudflared tunnel)로만 노출.
### 8. TLS 종료 위치와 내부 재암호화 정책
- 기본: Ingress(Traefik)에서 TLS 종료, 내부 Pod까지는 ClusterIP 경유 평문.
- 민감 backend(Vault, Keycloak token endpoint)는 **Ingress→Pod 재암호화** 검토. Traefik `serversTransport` + `insecureSkipVerify: false`로 backend TLS 사용.
- E2E mTLS가 필요하면 서비스 메시(Linkerd/Istio) 도입을 별도 ADR로 결정.
### 9. K3s 기본 Traefik은 유지·격리
- packaged manifest(`traefik.yaml`) 직접 수정 금지.
- 커스터마이징은 `HelmChartConfig`(`kind: HelmChartConfig` in `helm.cattle.io/v1`)로 override.
- Traefik은 `ingress-traefik` namespace에 격리, PSA `baseline`, NetworkPolicy는 80/443/8443 inbound + 모든 app namespace outbound 허용.
### 10. ServiceLB(klipper-lb) / MetalLB 결정
- 단일 노드 또는 on-prem 초기: K3s ServiceLB.
- 다중 노드 + BGP/ARP 정책이 필요: MetalLB(`kubectl get deploy -n kube-system | grep servicelb`가 없어야 함, `--disable=servicelb`로 off).
- 클라우드(EKS/GKE/AKS): cloud-provider LoadBalancer가 우선.
- 이 결정이 ADR로 고정되기 전에는 `LoadBalancer` Service를 새로 만들지 않는다.
### 11. NetworkPolicy는 namespace default-deny 기본
모든 운영 namespace는 다음 3종 + 서비스별 allow가 기본 세트다.
1. `default-deny-all` (ingress+egress)
2. `allow-dns-egress` (to `kube-system` `k8s-app=kube-dns`, 53/UDP+TCP)
3. `allow-from-ingress-traefik` (특정 app Pod만 허용)
### 12. NetworkPolicy `from`/`to` 엔트리 AND/OR 규칙
- **동일 엔트리 내 `namespaceSelector`+`podSelector`** → AND(교집합). 권장 패턴.
- **별도 엔트리로 분리** → OR(합집합). 거의 항상 버그.
- `ipBlock`은 같은 엔트리 내 `namespaceSelector`/`podSelector`와 함께 쓸 수 없다. 외부 CIDR allow는 별도 엔트리.
### 13. egress NetworkPolicy는 DNS 먼저, 서비스별 allow 나중
- `default-deny-all`만 적용하면 DNS 해석 실패로 앱이 기동 불가.
- kube-dns 53/UDP+TCP가 첫 번째 allow.
- 외부 API(OIDC issuer, SMTP, S3)는 FQDN이 아니라 IP CIDR로 나와야 v1 NetworkPolicy로 표현 가능. FQDN 기반 egress가 필요하면 Cilium `CiliumNetworkPolicy` 또는 egress gateway 검토.
### 14. Prometheus scrape는 ingress rule로 열기
- `monitoring` namespace의 Prometheus Pod만 허용.
- `namespaceSelector: kubernetes.io/metadata.name=monitoring` + `podSelector: app.kubernetes.io/name=prometheus` AND.
- 포트는 `metrics`(9090/9100 등) 전용, 앱 `http` 포트 재사용 금지.
### 15. Gateway API는 단계적 도입
- 신규 요구사항(traffic split, header routing, gRPC filter)이 Ingress v1으로 표현 불가하면 Gateway API 검토.
- 전환은 서비스 단위로 Ingress → `HTTPRoute`로 마이그레이션. `GatewayClass`/`Gateway`는 platform-team 소유.
### 16. health/metrics/admin endpoint 외부 공개 금지
- `/actuator/*`, `/debug/pprof/*`, `/admin/*`, `/metrics`는 Ingress path에 포함하지 않는다.
- 별도 Service 포트(`name: metrics`)를 만들고 NetworkPolicy로 Prometheus만 허용.
### 17. Ingress 경로 설계는 prefix + 명시 + 최소
- `pathType: Prefix` 명시(`ImplementationSpecific` 금지).
- `/`를 바로 노출하기 전 사용자 경로만 선언 가능한지 검토(Keycloak 패턴 참조).
- Path rewrite가 필요하면 Traefik `Middleware.stripPrefix`를 사용하고 annotation으로 명시.
### 18. ExternalName/headless Service는 용도에 맞춰
- `ExternalName`은 클러스터 외부 CNAME alias 용도. 인증/TLS 경계와 별개 고려.
- Headless(`clusterIP: None`)는 StatefulSet DNS, client-side LB 용도. Ingress 대상 아님.
### 19. 현재 스택 기본 권장안
#### auth-server / test-server
- Service: `ClusterIP` with named `http`, `metrics`
- Ingress: `ingressClassName: traefik`, cert-manager `letsencrypt-prod`, HSTS + HTTPS redirect
- NetworkPolicy: default-deny + dns + ingress-traefik + db + vault + prometheus
#### keycloak
- Service: `ClusterIP`, named `http`(8080), `management`(9000)
- Ingress: `/realms/`, `/resources/`, `/.well-known/`만 노출. 9000 포트는 Service로도 cluster 외부 비공개.
- Certificate: 전용(`sso.example.com`), 전용 TLS Secret
#### vault / db / migration-flyway
- Ingress 없음. ClusterIP only. 접근은 bastion + `kubectl port-forward` 또는 zero-trust proxy.
#### minio
- API/Console Ingress 분리. Console은 내부 전용, API는 필요 시 signed URL 중심.
#### ingress-traefik
- `ingress-traefik` namespace 격리, PSA `baseline`
- `Service type=LoadBalancer`(ServiceLB/MetalLB) 또는 `hostPort` 80/443만
## 프로젝트 기준 요약
- 기본 Service 타입은 `ClusterIP`, named port 필수
- 모든 Ingress는 `spec.ingressClassName: traefik`, annotation `kubernetes.io/ingress.class` 금지
- 모든 외부 HTTPS는 cert-manager ClusterIssuer 발급 + HSTS + HTTP→HTTPS redirect + TLS 1.2+
- Keycloak/Vault/DB 노출 범위는 path/host로 증명, 관리 포트 비공개
- K3s Traefik은 packaged manifest 직접 수정 금지, `HelmChartConfig` override
- NetworkPolicy default-deny + DNS allow + ingress-traefik allow 기본 세트
- `namespaceSelector`+`podSelector` AND/OR 차이를 정확히 사용
- Gateway API는 단계적 도입, 기존 Ingress 유지
@@ -0,0 +1,219 @@
# observability / health 기준
## 목적
이 문서는 1000+ 서비스가 공통으로 따르는 observability 기준선이다. metrics 수집 경로, golden signal 정의, 로그 포맷 / 수집 stack, trace 수집(OTel), health endpoint 외부 비공개 원칙, cardinality 가드를 한 파일에 고정한다.
## 공식 / 업계 근거
- **Google SRE Book (Ch.6)**: Four Golden Signals = **Latency, Traffic, Errors, Saturation**. 운영 대시보드의 기본 구성 원칙.
- **RED method (Tom Wilkie, Weaveworks)**: request-driven service에 대해 **Rate, Errors, Duration**.
- **USE method (Brendan Gregg)**: resource에 대해 **Utilization, Saturation, Errors**.
- **kube-prometheus-stack**: Prometheus Operator를 통한 `ServiceMonitor` / `PodMonitor` CRD가 primary scrape path.
- **Prometheus annotation fallback**: `prometheus.io/scrape: "true"` 등은 Operator가 없을 때만 사용.
- **OpenTelemetry**: OTLP protocol + OTel Collector (Deployment gateway + DaemonSet agent) 가 표준.
- **Log shipping canonical stacks**: Loki + Grafana Alloy (또는 Promtail) / Fluent Bit → OpenSearch. 한 플랫폼에서 둘 이상 섞지 않는다.
- `kubectl events` (1.27+ stable) — 기존 `kubectl get events`보다 sort/watch 기본 제공.
- metrics-server: HPA/VPA와 `kubectl top` 을 위한 최소 resource metric. full metrics와 분리.
## 기본 규칙
### 1. Four Golden Signals를 모든 서비스 대시보드의 골격으로
각 traffic-facing service는 최소 4개 signal을 노출한다.
- **Latency**: `request_duration_seconds` histogram (p50/p95/p99).
- **Traffic**: `requests_per_second` by method/status.
- **Errors**: `error_rate` (5xx / 전체).
- **Saturation**: resource utilization (CPU / memory / connection pool / queue depth).
SLO / alert / dashboard가 이 4개에서 시작한다.
### 2. RED는 request-driven, USE는 resource에 쓴다
- HTTP / gRPC 서비스 → **RED**.
- Node / disk / CPU / DB pool → **USE**.
- 두 방법론을 동시에 활용 가능 (golden signal은 양쪽 합집합).
### 3. ServiceMonitor / PodMonitor 를 primary scrape path로
kube-prometheus-stack을 운영하는 플랫폼에서는 `ServiceMonitor` CRD가 표준이다.
- `selector.matchLabels` 로 대상 Service 매칭.
- `namespaceSelector` 명시 (암묵적 전체 허용 금지).
- `endpoints[].port`**named port**, 숫자 port 금지.
- `interval` (기본 30s), `scrapeTimeout` (interval < interval) 명시.
- `scheme` (http/https) 명시.
- `bearerTokenSecret` / `tlsConfig` 로 인증 scrape.
- `relabelings` 로 label 위생 (pod_template_hash drop 등).
Pod에 직접 연결되는 경우 (Service가 없는 워크로드) `PodMonitor` 사용.
### 4. Annotation-based scrape 는 fallback
`prometheus.io/scrape: "true"` 계열 annotation은 Prometheus가 Operator 없이 kubernetes_sd_configs로 직접 discover하는 방식이다. ServiceMonitor 대비 label relabel / auth / tls 제어가 약하다.
- kube-prometheus-stack이 있는 환경: **사용 금지**, ServiceMonitor 통일.
- legacy / 교체 진행 중인 플랫폼: 전환 기간 동안만 사용.
지원 annotation:
- `prometheus.io/scrape: "true"`
- `prometheus.io/port: "8081"`
- `prometheus.io/path: "/metrics"`
- `prometheus.io/scheme: "http"`
### 5. metrics port는 외부 비공개, NetworkPolicy로 scraper만 허용
- `/metrics` 는 절대 Ingress 경로에 노출하지 않는다.
- metrics port는 별도 containerPort (ex: 8081, 9000).
- NetworkPolicy로 **monitoring namespace의 prometheus pod만** 해당 port에 ingress 허용.
### 6. Cardinality는 label 설계 단계에서 가드
Prometheus TSDB에서 **각 label value 조합 = 새 time series**. cardinality 폭발은 쿼리 OOM / storage 폭증의 가장 흔한 원인.
금지 label:
- `user_id`, `tenant_id` (높은 기수) — 대신 top-N aggregation 또는 별도 logging.
- `path` (path에 UUID / numeric ID 포함) — template된 route로 바꾼다 (`/users/:id`).
- `url` 전체, `request_id`, `trace_id`, `session_id`.
- timestamp, epoch value.
허용 label 예:
- `method` (GET/POST/…), `status_code` (bucketed 2xx/4xx/5xx가 더 안전), `route` (template).
규칙: **한 metric당 series 수 ≤ 10,000** 목표. 10만 넘어가면 review.
### 7. Histogram 을 p99 표현 기본값으로
- summary는 aggregatable 하지 않다 (서비스 간 p99 합산 불가).
- `histogram_quantile()` 를 위한 `_bucket` + `_count` + `_sum` 를 쓴다.
- bucket boundary는 SLO에 맞춰 튜닝 (`le: 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10`).
### 8. 로그는 JSON structured, stdout/stderr 로만
application log는 **JSON one-line per record**, stdout/stderr로 출력. PVC / hostPath / 컨테이너 내부 file 금지.
필수 field:
- `timestamp` (ISO 8601 RFC3339, UTC).
- `level` (`DEBUG`/`INFO`/`WARN`/`ERROR`).
- `service` (= `app.kubernetes.io/name`).
- `trace_id`, `span_id` (OTel에서 주입).
- `message`.
- `error` (object with `type`, `message`, `stacktrace` when level=ERROR).
- optional: `user_id` (hashed), `request_id`, `http_status`.
### 9. 로그 수집 stack은 한 플랫폼당 하나
canonical choice:
- **Loki + Grafana Alloy (권장)**: 낮은 storage cost, Grafana 통합.
- **Fluent Bit → OpenSearch/Elasticsearch**: full-text search 중심, 높은 storage cost.
플랫폼 하나에서 둘 다 운영하지 않는다. AI agent가 매니페스트 생성할 때 플랫폼 선택을 context에서 받아 일관되게 적용한다.
node-level: DaemonSet으로 agent 배포 → tail `/var/log/containers/*.log`.
### 10. 민감정보는 로그 금지 + 자동 masking
금지:
- access/refresh token, bearer, API key.
- DB password, connection string의 password 부분.
- Vault secret value.
- full Authorization header.
- PII (email, phone, SSN 등) 원문.
구현:
- logging framework의 structured field 에서만 쓰고 `toString()` 흐름 차단.
- 중앙 수집 파이프라인에 redaction filter 추가.
- 의심스러운 pattern은 debug 로그에서도 masking.
### 11. OpenTelemetry / OTLP를 trace / metrics 통로로
- 애플리케이션: OTel SDK로 계측, OTLP (gRPC 4317 또는 HTTP 4318) 로 export.
- 수집: **OTel Collector DaemonSet (agent)****OTel Collector Deployment (gateway)** → backend (Tempo / Jaeger / New Relic / Datadog).
- gateway에서 sampling / tail-based sampling / PII scrubbing 적용.
- app은 cluster 내부 agent endpoint만 알면 됨 (localhost:4317 → DaemonSet).
### 12. health / metrics / admin endpoint 는 외부 비공개 기본값
외부 비공개 대상:
- `/health`, `/health/*`, `/actuator/*`.
- `/metrics`.
- `/admin`, `/internal`, `/debug`.
- Keycloak management port 9000.
- Vault `/sys/*` endpoint.
외부 공개는 명시적 review 필요.
### 13. probe는 health endpoint 와 목적을 구분
- probe용 endpoint는 shallow, 빠른 응답.
- 운영자 점검용 deep health는 별도 endpoint (ex: `/ops/deep-health`), 인증 필요.
- Prometheus 가 `/metrics` 를 스크레이프하더라도 probe가 `/metrics` 를 쓰지 않는다 (cost 문제).
### 14. `kubectl events` 를 기본 event 조회 수단으로 (1.27+)
Kubernetes 1.27+ 부터 `kubectl events` 가 stable.
- `kubectl events -A --watch` — cluster-wide live view.
- `kubectl events -n <ns> --for pod/<name>` — 특정 오브젝트.
- `kubectl events --types=Warning` — 경고만.
`kubectl get events` 대비 sort-by-timestamp 기본, watch 안정적.
### 15. 알림 기준: Golden Signal 에 SLO 를 먼저 정의
- availability SLO: 99.9% / 99.95% 등.
- latency SLO: p99 < 500ms.
- error budget: (1 - SLO) × 기간.
- alert는 **burn rate** 기준 (1h/6h fast burn + 6h/3d slow burn 이중 창).
단순 "CPU > 80%" alert 는 actionable 하지 않다 (saturation은 dashboard용, 알림은 SLO 기반).
### 16. 워크로드별 기본 권장안
#### auth-server (Spring Boot)
- metrics: micrometer + prometheus registry, `/actuator/prometheus`.
- ServiceMonitor with named port `metrics` (8081).
- tracing: OTel Java agent, OTLP to DaemonSet.
- logging: logback JSON encoder → stdout.
#### keycloak
- metrics: management port 9000 `/metrics`.
- ServiceMonitor 대상, `/admin``9000` 외부 비공개.
- event metric cardinality는 `event_type` level 까지만, user / session ID 금지.
#### vault
- `/sys/metrics?format=prometheus` (token 필요) → ServiceMonitor with `bearerTokenSecret`.
- `/sys/health` 는 sealed/standby 구분해서 alert 룰 따로.
#### minio
- `/minio/v2/metrics/cluster` + `/node` + `/bucket`.
- bucket metric은 bucket 수 폭증 시 cardinality 주의.
#### db (PostgreSQL / MySQL)
- postgres_exporter / mysqld_exporter sidecar 또는 별도 Deployment.
- USE method (connection pool saturation, lock wait).
#### ingress-controller
- RED + upstream response time.
- path label은 반드시 template 화.
## 프로젝트 기준 요약
- Four Golden Signals를 dashboard 골격으로, RED/USE를 세부 방법론으로.
- ServiceMonitor / PodMonitor 를 primary scrape, annotation은 fallback.
- metrics port는 NetworkPolicy로 monitoring namespace만 허용.
- Cardinality는 label 설계에서 가드 (user_id / raw path / timestamp 금지).
- 로그는 JSON structured stdout, trace_id/span_id 포함.
- log shipping stack은 플랫폼당 하나 (Loki+Alloy 또는 Fluent Bit→OpenSearch).
- 로그에 민감정보 금지, 중앙 파이프라인 redaction.
- OpenTelemetry DaemonSet agent + Deployment gateway.
- health / metrics / admin endpoint 외부 비공개.
- `kubectl events` 를 기본 event 조회 수단으로 (1.27+).
- alert는 SLO burn rate 기반, CPU% 같은 단순 threshold 금지.
@@ -0,0 +1,301 @@
# operations / runbook / upgrade / rollback 기준
## 목적
이 문서는 1000+ 서비스를 운영하는 플랫폼에서 모든 변경이 거쳐야 하는 **runbook 규칙**을 고정한다. GitOps 원본, rolling update 파라미터 튜닝, 진보된 배포 전략 (Argo Rollouts, canary, blue/green), K3s 자동 업그레이드, node 작업(drain/cordon), rollback 의미와 경계가 대상이다.
## 공식 / 업계 근거
- Kubernetes `Deployment.spec.strategy`: `RollingUpdate` (default, maxSurge/maxUnavailable 25%/25%) 또는 `Recreate` (singleton).
- `kubectl rollout`: `status --timeout`, `history`, `undo --to-revision`, `pause`, `resume`, `restart`.
- **Argo Rollouts** (https://argoproj.github.io/argo-rollouts/): `Rollout` CRD가 Deployment의 대체제로 canary / blueGreen 지원. `AnalysisTemplate` + Prometheus metric으로 자동 승격/롤백.
- **Flagger**: Argo Rollouts의 대안, service-mesh 친화적 (Istio/Linkerd/App Mesh).
- **ArgoCD**: sync wave (`argocd.argoproj.io/sync-wave: "<int>"`), sync phase hook (`PreSync`, `Sync`, `PostSync`, `SyncFail`, `PostDelete`).
- **Flux**: `Kustomization.spec.dependsOn` 으로 순서 명시.
- `kubectl drain --ignore-daemonsets --delete-emptydir-data --grace-period=30` 가 node maintenance 표준. PDB를 존중하므로 PDB 설계가 전제.
- **K3s System Upgrade Controller** (https://docs.k3s.io/upgrades/automated): `Plan` CRD로 server-plan / agent-plan 분리, concurrency 제어, nodeSelector로 대상 제한.
- Flyway `validate`, `info`, `migrate` — application rollout 과 분리.
## 기본 규칙
### 1. Source of truth = Git 의 Kustomize / Helm overlay
운영 변경은 Git에 있는 선언형 원본에서만 시작한다.
기본 금지:
- 운영 노드에서 manifest 파일 직접 편집.
- `kubectl edit` 로 live object 수정 후 문서 없음.
- `/var/lib/rancher/k3s/server/manifests` 를 1차 원본처럼 사용.
### 2. 변경 절차는 render → diff → apply → status → post-check 로 고정
```
1. kubectl kustomize <overlay> # render
2. kubectl diff -k <overlay> # preview
3. kubectl apply -k <overlay> # apply
4. kubectl rollout status ... --timeout=10m
5. post-check (smoke test, SLO check)
```
`diff` 없는 `apply` 는 프로덕션 금지.
### 3. `rollingUpdate.maxSurge` / `maxUnavailable` 는 워크로드별 튜닝
기본값 `25% / 25%`**replica 수에 따라 틀릴 수 있다**.
- **replica 2**: default는 maxUnavailable 0, maxSurge 1 추천 → 항상 최소 2 유지 + 1 추가.
- **replica 3**: `maxSurge: 1, maxUnavailable: 0` → 가용성 우선.
- **replica 10+**: `maxSurge: 25%, maxUnavailable: 10%` → 속도와 가용성 균형.
- **latency-sensitive**: `maxUnavailable: 0` 고정.
- **cost-sensitive large fleet**: `maxSurge: 10%, maxUnavailable: 10%`.
### 4. `Recreate` 전략은 singleton / 동시성 금지 워크로드에만
- PVC ReadWriteOnce + 단일 pod 가 전제인 app (legacy MySQL single instance 등).
- Old/New 동시 실행 시 데이터 부정합이 나는 앱.
- 짧은 downtime이 허용되는 경우.
일반 stateless app은 절대 Recreate 쓰지 않는다.
### 5. `kubectl rollout` 명령 계열
- `kubectl rollout status deployment/<name> --timeout=10m`: 타임아웃 필수.
- `kubectl rollout history deployment/<name>`: revision 확인.
- `kubectl rollout undo deployment/<name> --to-revision=<N>`: 이전 revision으로 되돌림.
- `kubectl rollout pause deployment/<name>`: 롤아웃 중단 (부분 적용 뒤 관찰용).
- `kubectl rollout resume deployment/<name>`: 재개.
- `kubectl rollout restart deployment/<name>`: 이미지 변경 없이 Pod 재생성 (secret 갱신 후 등).
### 6. 진보된 배포 전략: Argo Rollouts (canary / blueGreen)
표준 `Deployment` 로는 부족한 경우 (자동화된 canary, metric-based 승격) 에는 Argo Rollouts 의 `Rollout` CRD 를 쓴다.
- **canary**: `steps:` 로 traffic %, pause, analysis 순서 기술.
- **blueGreen**: `activeService` / `previewService` 로 서비스 두 개 전환.
- **AnalysisTemplate**: Prometheus query로 success rate / p99 latency 측정 → 자동 promote or abort.
- **대안 Flagger**: Istio / Linkerd / App Mesh + Flagger `Canary` CRD. service mesh 있는 플랫폼에서 선택.
### 7. Argo Rollouts 기본 canary 스텝
```
steps:
- setWeight: 10
- pause: { duration: 2m }
- analysis: { templates: [{ templateName: success-rate }] }
- setWeight: 25
- pause: { duration: 5m }
- analysis: { templates: [...] }
- setWeight: 50
- pause: { duration: 10m }
- setWeight: 100
```
각 setWeight 사이에 pause + analysis 로 자동 abort gate.
### 8. blueGreen 은 traffic cutover 가 필요한 경우만
blueGreen은:
- schema 변경이 양립 불가해서 instant cutover가 필요.
- 외부 system 과 coordination 필요 (rollback도 instant).
일반 변경은 canary 가 우선. blueGreen 은 trade-off (리소스 2배, warm-up 부담) 때문에 default 가 아니다.
### 9. ArgoCD sync wave / hook
배포 순서는 sync wave annotation 으로 명시한다.
- `argocd.argoproj.io/sync-wave: "-2"` → CRD.
- `argocd.argoproj.io/sync-wave: "-1"` → namespace, secret store, operator.
- `argocd.argoproj.io/sync-wave: "0"` → 본 리소스 (기본).
- `argocd.argoproj.io/sync-wave: "1"` → Ingress, post-deploy job.
hook:
- `PreSync`: schema migration job.
- `Sync`: 본 리소스 (default).
- `PostSync`: smoke test Job, cache warm.
- `SyncFail`: 실패 시 알림 Job.
- `PostDelete`: 삭제 후 cleanup.
### 10. Flux Kustomization dependsOn
Flux 플랫폼에서는 `Kustomization.spec.dependsOn` 으로 순서를 명시한다.
```yaml
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: auth-server
namespace: flux-system
spec:
interval: 5m
path: ./k8s/overlays/prod
prune: true
sourceRef:
kind: GitRepository
name: platform
dependsOn:
- name: cert-manager
- name: postgres-operator
```
### 11. node 작업 (drain / cordon) 은 PDB 존중 흐름
```
1. kubectl cordon <node>
2. kubectl drain <node> \
--ignore-daemonsets \
--delete-emptydir-data \
--grace-period=30 \
--timeout=10m
3. 작업 수행
4. kubectl uncordon <node>
```
옵션 의미:
- `--ignore-daemonsets`: DaemonSet pod는 evict 대상이 아님.
- `--delete-emptydir-data`: ephemeral 데이터 수용.
- `--grace-period=30`: preStop + terminationGracePeriod 존중.
- `--timeout=10m`: PDB 로 인한 무한 대기 차단.
**PDB 없는 critical workload** 는 drain 실패 또는 downtime 유발. PDB 설계가 선결 조건.
### 12. K3s System Upgrade Controller
K3s 자동 업그레이드는 `system-upgrade-controller``Plan` CRD 를 쓴다.
구성:
- **server-plan**: control-plane 먼저 업그레이드. `concurrency: 1`, nodeSelector: `node-role.kubernetes.io/control-plane=true`.
- **agent-plan**: agent 노드. `concurrency: 1~N` (small cluster는 1), server-plan 완료 후.
- `cordon: true`, `drain.force: true, deleteEmptydirData: true, ignoreDaemonsets: true` 표준.
- `version:` 또는 `channel:` 로 target K3s version.
- `upgrade.image: rancher/k3s-upgrade` + 버전 tag.
### 13. blue/green via two Services (수동 패턴)
Argo Rollouts 없이 간단 blue/green 이 필요하면:
- Deployment A (blue), Deployment B (green) 각각.
- Service selector 의 `version` label 만 전환 (blue → green).
- rollback = selector 를 다시 blue 로.
- canary 는 이 방식으로 구현하지 않는다 (Argo Rollouts 사용).
### 14. Rollback 은 DB rollback 이 아니다
**가장 자주 오해되는 규칙**. 반드시 내재화한다.
- `kubectl rollout undo` 는 Deployment workload 만 되돌린다.
- **DB schema 변경 / migration 은 되돌아가지 않는다**.
- rollback 설계는 **schema-forward-compatible** 로 한다:
- Expand (schema 추가 → 이전 코드도 호환) → Migrate (데이터 이전) → Contract (이전 코드용 schema 제거). Expand/Contract를 별도 릴리스로 분리.
- 긴급 상황에서도 rollout undo 로 DB 를 되돌릴 수 없다. DB 는 별도 restore 절차 (PITR, snapshot).
### 15. Flyway validate → migrate 를 application rollout 과 분리
```
1. flyway validate # checksum / 순서 확인
2. flyway info # 대기 migration 확인
3. flyway migrate # 실제 적용
4. kubectl apply -k ... # app rollout (별도 단계)
5. kubectl rollout status # 앱 기동 확인
```
application startup 안에 migration 을 숨기지 않는다 (rollout 실패와 migration 실패 섞임).
### 16. restore 와 rollout 구분
**rollout** (workload 변경 되돌리기):
- `kubectl rollout undo` 또는 이전 Git revision apply.
- Deployment / StatefulSet / DaemonSet 대상.
**restore** (상태 복구):
- K3s control plane → etcd snapshot restore.
- PostgreSQL → PITR / base backup + WAL.
- Vault → raft snapshot restore.
- MinIO → replication resync 또는 DR site cutover.
서로 다른 runbook 이다. "rollback" 이라는 한 단어로 뭉치지 않는다.
### 17. 긴급 변경도 runbook 을 벗어나지 않음
장애 대응 hot-fix 라도:
- 어떤 overlay 를 바꿨는지 commit / PR.
- 어떤 명령을 실행했는지 기록 (shell history / runbook log).
- 사후 Git 반영 (live-cluster drift 제거).
- 임시 조치의 만료 / 정리 시점 기록.
### 18. destructive 작업은 명시 승인 + 증거 보존
요구 작업:
- namespace 삭제.
- PVC 삭제.
- StatefulSet 삭제 + PVC 정리.
- K3s snapshot restore.
- Vault raft snapshot restore.
- DB restore overwrite.
- MinIO bucket purge / replication cutover.
규칙:
- 2-person approval.
- 작업 전 full snapshot 확보.
- dry-run / diff 선행.
- post-mortem 작성.
## 권장 절차 템플릿
### 일반 app 변경
1. PR 생성 + review
2. `kubectl kustomize <overlay>` → 렌더 검증
3. `kubectl diff -k <overlay>` → 변경 확인
4. `kubectl apply -k <overlay>`
5. `kubectl rollout status deployment/<name> --timeout=10m`
6. smoke test + SLO dashboard 확인
7. 결과 PR comment
### DB migration 포함 변경
1. migration SQL review
2. `flyway validate``flyway info``flyway migrate`
3. app overlay apply
4. `kubectl rollout status`
5. post-check
6. 실패 시 DB runbook 과 app rollback runbook 분리 적용
### K3s control plane upgrade
1. 해당 버전 release notes / caveat 확인
2. etcd snapshot 확보
3. `Plan` CRD apply (server-plan)
4. control-plane 업그레이드 완료 확인
5. `Plan` CRD apply (agent-plan)
6. agent 업그레이드 완료 확인
7. packaged component 영향 확인
8. 실패 시 etcd restore runbook
### node maintenance
1. `kubectl cordon <node>`
2. `kubectl drain <node> --ignore-daemonsets --delete-emptydir-data --grace-period=30 --timeout=10m`
3. 작업 수행
4. `kubectl uncordon <node>`
5. `kubectl get pods -o wide` 로 재배치 확인
## 프로젝트 기준 요약
- source of truth = Git Kustomize/Helm overlay, live-cluster 수정 금지.
- render → diff → apply → rollout status → post-check 순서 고정.
- rollingUpdate 파라미터는 워크로드별 튜닝, default 25%/25% 맹신 금지.
- Argo Rollouts 로 canary + AnalysisTemplate 자동 gate, Flagger 는 mesh 환경 대안.
- ArgoCD sync wave / hook, Flux dependsOn 으로 순서 명시.
- node 작업은 PDB 존중 drain 흐름, PDB 설계가 선결.
- K3s 업그레이드는 System Upgrade Controller `Plan` CRD (server → agent).
- blue/green 은 cutover 필요 시, canary 가 default.
- **rollback 은 DB rollback 이 아니다** — schema-forward-compatible 로 설계.
- Flyway validate/migrate 는 application rollout 과 분리.
- restore 와 rollout 은 다른 runbook.
- destructive 작업은 2-person approval + snapshot.
@@ -0,0 +1,199 @@
# resources / probes / availability 기준
## 목적
이 문서는 1000+ 서비스를 운영하는 Kubernetes 플랫폼에서 AI coding agent가 생성하는 모든 워크로드 매니페스트의 ground truth다. 모든 rule은 Google SRE / Netflix / Shopify의 실제 프로덕션 합의를 기반으로 한다.
정하는 것:
- resource requests/limits를 어떤 값으로, 어떤 QoS class로 줄지
- probe (startup / readiness / liveness) 세 축을 어떻게 분리할지
- 가용성(PDB / topologySpread / HPA)을 어떤 조합으로 구성할지
- K3s 환경에서 metrics-server 전제를 어떻게 다룰지
## 공식 / 업계 근거
- Kubernetes QoS class는 resources 값에 의해 자동 결정된다 (`Guaranteed`, `Burstable`, `BestEffort`).
- CPU는 compressible resource로 limit 초과 시 throttle된다. memory는 incompressible로 OOM kill된다.
- Tim Hockin (Google, Kubernetes co-founder) 및 다수 SRE 컨퍼런스 토크: **CPU limit는 CFS throttling을 quota 미만에서도 유발하므로 대부분의 프로덕션 워크로드에서 제거한다**. CPU request만 설정하여 노드 capacity를 공정 공유한다.
- memory limit는 OOM kill의 유일한 제어 수단이므로 반드시 설정한다.
- `topologySpreadConstraints`는 1.19+ stable. zone과 host 두 축으로 skew를 제한하는 것이 표준이다.
- `podAntiAffinity`는 legacy 대안, 현대 가이드는 topologySpreadConstraints 우선.
- HPA v2 (`autoscaling/v2`) 는 `behavior` block으로 scale up/down stabilizationWindow와 policy를 분리 제어한다.
- PodDisruptionBudget은 `maxUnavailable` 또는 `minAvailable`. 대규모 fleet에서는 `maxUnavailable` 권장 (replica scale 변화 추종).
- startup probe는 성공 전까지 liveness/readiness를 차단한다. slow boot 서비스에 필수.
- Kubernetes 1.29+ native sidecar: init container에 `restartPolicy: Always` 명시.
## 기본 규칙
### 1. QoS class는 의도적으로 선택한다
QoS class는 `resources` 값의 결과물이 아니라 **선택**이다.
- **Guaranteed**: 모든 컨테이너의 request == limit. 가장 높은 eviction 우선순위 보호.
- 적용: latency-sensitive JVM (Keycloak, auth-server critical tier), stateful 단일 인스턴스 (vault active), 단일 ReplicaSet critical path.
- **Burstable**: request < limit 또는 일부만 설정. 탄력적 CPU burst 허용.
- 적용: stateless HTTP API, worker, generic service — 기본값.
- **BestEffort**: request/limit 모두 없음. 가장 먼저 evict됨.
- 적용: 일시적 debugging pod, 무영향 experiment. 프로덕션 금지.
### 2. CPU limit anti-pattern — 기본은 CPU request only
Google SRE 및 Tim Hockin의 공식 stance는 "대부분의 워크로드에서 CPU limit를 설정하지 말 것"이다. Linux CFS의 quota 회계가 sub-period burst에서도 throttle을 유발하기 때문이다.
기본:
- **CPU**: request만 설정, limit 생략.
- **Memory**: limit 반드시 설정.
- Guaranteed를 원하면: `limits.memory == requests.memory`.
- Burstable 기본값: `limits.memory = 1.1 ~ 1.5 × requests.memory`.
예외 (CPU limit를 설정해야 하는 경우):
- multi-tenant 노드에서 noisy neighbor가 측정 가능한 손해를 유발.
- batch/cron Job에서 예산 통제가 필요.
- billing-backed 측정으로 인한 compliance 요구.
### 3. requests 값은 측정 기반으로 잡는다
- p95 cpu usage × 1.2 가 request 시작점.
- p99 memory (steady state) × 1.3 이 memory request 시작점.
- 최초 배포는 **overprovision** 으로 시작 → 1~2주 관측 후 right-sizing.
- VPA recommendation을 참고하되 자동 적용은 하지 않는다 (review 필요).
### 4. `limit`만 있고 `request`가 없는 구성 금지
Kubernetes는 request 미설정 시 limit를 request로 복사한다. 이는 암묵적 Guaranteed QoS로 귀결되며 의도와 다를 수 있다. 반드시 둘 다 명시한다.
### 5. Probe는 세 축으로 분리한다
- **startup probe**: "부팅이 끝났는가". 성공 전까지 readiness/liveness는 실행되지 않는다.
- 필수: Keycloak, Vault, JVM warm-up이 긴 서비스.
- 타이밍 규칙: `failureThreshold × periodSeconds ≥ 최악의 cold start (p99)`. 예: Keycloak `periodSeconds: 10, failureThreshold: 30` = 300s.
- **readiness probe**: "지금 트래픽을 받아도 되는가". 실패 시 Service endpoint에서 제외.
- 모든 traffic-facing 서비스 필수.
- 외부 의존성 전체 가용성을 묶지 않는다 (동시 탈락 방지).
- **liveness probe**: "재시작이 치료인가" (deadlock only).
- Default = 설정하지 않거나 readiness와 다른 가벼운 self-check.
- **잘못 설정하면 cascading restart 유발**. Kubernetes 공식 문서 명시.
### 6. readiness는 shallow, liveness는 더 shallow
readiness는 "app loop이 요청을 처리 가능한가"까지만 검사한다. DB connection pool 초기화처럼 intra-pod 조건은 OK. 외부 DB `SELECT 1` 전체 가용성 체크는 금지.
liveness는 process deadlock 감지 전용. HTTP endpoint면 `/livez` 같은 매우 가벼운 200 응답.
### 7. topologySpreadConstraints를 기본 가용성 primitive로
production multi-zone cluster에서는 **zone + host 두 축** 모두 제약한다.
```yaml
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app.kubernetes.io/name: auth-server
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app.kubernetes.io/name: auth-server
```
- zone `DoNotSchedule`: 프로덕션에서 zone 장애 격리에 필수.
- host `ScheduleAnyway`: 노드 부족 시 배포 불가 방지.
### 8. podAntiAffinity는 legacy로 본다
topologySpreadConstraints가 등장한 뒤 podAntiAffinity는 대부분의 use case에서 대체되었다. 신규 매니페스트는 topologySpreadConstraints를 우선 적용한다.
예외: 단순 "한 노드에 두 개 이상 금지" 규칙만 필요하고 spread 회계가 불필요한 경우.
### 9. HPA는 autoscaling/v2, behavior block 필수
`autoscaling/v1`은 더 이상 사용하지 않는다. `autoscaling/v2`를 기본으로 한다.
- `metrics:` 유형: `Resource` (cpu/memory), `Pods`, `Object`, `External`, `ContainerResource`.
- `behavior.scaleUp.stabilizationWindowSeconds: 0` (트래픽 급증에 빠르게 반응).
- `behavior.scaleDown.stabilizationWindowSeconds: 300` (flapping 방지).
- `policies` 조합: `type: Percent` (현 replica의 X%)와 `type: Pods` (절대 수) 동시 지정, `selectPolicy: Max` 또는 `Min`.
### 10. HPA 전제 조건
- resource requests가 먼저 잡혀 있어야 한다 (utilization target이 request 기준).
- startup probe가 안정화되어 있어야 한다 (scale-up 중 flapping 방지).
- 해당 워크로드가 **horizontal scale로 효과가 있는** 성격이어야 한다. stateful / DB / quorum 기반은 HPA 비대상.
- K3s metrics-server가 packaged로 배포되어 있음을 전제로 하되, availability를 runbook에서 점검한다.
### 11. PDB는 fleet 규모에 맞춰 `maxUnavailable` 우선
- replica ≥ 3: `maxUnavailable: 1` 또는 `maxUnavailable: 25%`.
- replica 대규모 (10+): `maxUnavailable: 10%` 권장 (유연성).
- replica 2: `maxUnavailable: 1`.
- replica 1: PDB 금지 (node drain을 막는다).
- quorum 기반 (etcd, vault raft, DB cluster): `minAvailable` 로 quorum 수 명시.
### 12. PDB zero disruption 금지
`maxUnavailable: 0` 또는 `minAvailable: 100%` 는 node drain / maintenance를 완전 차단한다. Kubernetes 업그레이드 자체가 불가능해진다. 명시적 예외 승인 없이 사용 금지.
### 13. init container 와 sidecar 순서 (1.29+)
- **init container**: main 전에 실행, 완료 후 종료. schema migration, secret preparation 용.
- **native sidecar (1.29+)**: init container에 `restartPolicy: Always` 명시. main과 병렬 실행, main 종료 후 종료.
- 사용: log forwarder, metrics exporter, service mesh proxy.
- `initContainers` 배열 순서가 실행 순서다.
### 14. 워크로드별 기본 권장안
#### auth-server (stateless Spring Boot)
- QoS: **Burstable**.
- CPU: request only (`500m`). Memory: request `1Gi`, limit `1.5Gi`.
- Probes: startup `/actuator/health/started` (60s), readiness `/actuator/health/readiness`, liveness `/actuator/health/liveness`.
- HPA: CPU 70%, min 3, max 20, scale-down 300s.
- PDB: `maxUnavailable: 1`.
- topologySpread: zone `DoNotSchedule`, host `ScheduleAnyway`.
#### keycloak (JVM, slow boot, latency-sensitive)
- QoS: **Guaranteed** (request == limit, memory 2Gi 고정).
- CPU: request `1`, limit `1` (Guaranteed 요구).
- Probes: startup 5분 budget (`periodSeconds: 10, failureThreshold: 30`), readiness `/health/ready` on 9000, liveness `/health/live` on 9000.
- HPA: 보통 **비대상**. 고정 replica (3)로 시작, 측정 후 검토.
- PDB: `maxUnavailable: 1`.
#### vault (raft quorum)
- QoS: **Guaranteed**.
- Probes: readiness/liveness는 raft sealed/active 상태 구분.
- HPA: 비대상.
- PDB: `minAvailable: 2` (3-node raft 기준 quorum 보존).
#### minio (erasure coded storage)
- QoS: **Guaranteed**.
- PDB: `minAvailable: N-1` (erasure set 기준).
- HPA: 비대상.
#### migration-flyway (Job)
- probe 없음 (Job은 probe 무의미).
- requests 명시, limit는 memory만.
- activeDeadlineSeconds 설정.
- HPA/PDB 비대상.
#### ingress-controller
- QoS: **Burstable** 또는 Guaranteed (tier에 따라).
- HPA 후보 (traffic 기반).
- PDB: `maxUnavailable: 1`.
## 프로젝트 기준 요약
- QoS는 의도적으로 선택. Guaranteed는 latency-sensitive JVM, Burstable은 stateless 기본.
- CPU limit 기본 제거 (throttling 회피). Memory limit 필수.
- requests/limits 함께 명시. limit만 단독 금지.
- probe 세 축 분리. startup 타이밍은 worst-case cold start 기준.
- topologySpreadConstraints zone + host 두 축으로 기본 구성.
- HPA v2 + behavior block. resource requests / startup 안정화 후 적용.
- PDB는 `maxUnavailable` 우선, replica 전략과 함께 결정.
- 1.29+ native sidecar는 init container `restartPolicy: Always`.
- K3s metrics-server는 HPA 전제로만 신뢰, full metrics는 별도 stack.
+293
View File
@@ -0,0 +1,293 @@
# infra scripts 기준
## 목적
이 문서는 1000+ 서비스 플랫폼에서 인프라 스크립트가 지켜야 할 품질 기준선이다. 스크립트는 선언형 원본 (Kustomize / Helm / ArgoCD / Flux) 을 **대체하지 않는다**. 렌더 / diff / 적용 / 백업 / 복구 / 부트스트랩을 **orchestration** 하는 얇은 레이어로 제한한다.
## 공식 / 업계 근거
- **Google Shell Style Guide**: `#!/usr/bin/env bash`, `set -e`, `main "$@"`, function-first, `local`.
- **Unofficial Bash Strict Mode (Aaron Maxwell)**: `set -euo pipefail` + `IFS=$'\n\t'` 가 사실상 표준.
- **ShellCheck** (https://www.shellcheck.net/): 정적 분석. CI에서 mandatory.
- **shfmt** (mvdan/sh): 자동 포맷터. line-length / indent 규격 강제.
- **GitOps 원칙** (Weaveworks 정의): 선언형 원본 + auto-reconcile. 스크립트는 원본을 소유하지 않는다.
## 기본 규칙
### 1. 모든 스크립트 맨 위에 strict mode
```bash
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
```
의미:
- `set -e` : 명령 실패 시 즉시 종료.
- `set -u` : unset variable 참조 시 에러.
- `set -o pipefail` : pipeline 중 하나라도 실패하면 전체 실패.
- `IFS=$'\n\t'` : 기본 IFS에서 space 제거 → 파일명 공백 sane split.
예외 금지. CI lint 에서 검사.
### 2. 정리 작업은 `trap` 으로 보장
임시 파일 / 임시 kubeconfig / port-forward / background job 은 반드시 trap EXIT 에서 정리.
```bash
TMPDIR="$(mktemp -d)"
trap 'rm -rf "${TMPDIR}"' EXIT INT TERM
```
- `EXIT`: 정상/비정상 종료 모두 잡음.
- `INT TERM`: signal 기반 종료 시에도 실행.
- trap은 setup 직후 즉시 설치.
### 3. ShellCheck + shfmt 는 CI 에서 필수
- `shellcheck -S style scripts/**/*.sh` → CI fail 시 merge 금지.
- `shfmt -i 2 -bn -ci -d scripts/` → 자동 포맷 검증.
- suppress (`# shellcheck disable=...`) 는 **줄 단위**로만, 이유 주석 필수.
- "경고 너무 많아서 꺼둔다" 금지.
### 4. 표준 `log()` 함수 (ISO 8601 timestamp + level, stderr)
```bash
log() {
local level="$1"; shift
local ts
ts="$(date -u +'%Y-%m-%dT%H:%M:%SZ')"
printf '%s [%s] %s\n' "${ts}" "${level}" "$*" >&2
}
info() { log INFO "$@"; }
warn() { log WARN "$@"; }
error() { log ERROR "$@"; }
fatal() { log FATAL "$@"; exit 1; }
```
- stdout 은 머신 판독용 결과 전용.
- stderr 로 로그 → pipeline 안전.
- UTC ISO 8601 로 tz 모호성 제거.
### 5. 엔트리포인트 `main "$@"` 패턴
```bash
usage() {
cat <<'EOF' >&2
Usage: render-diff-apply.sh [--overlay PATH] [--context NAME] [--yes]
--overlay PATH path to kustomize overlay (required)
--context NAME kube context (required)
--yes skip confirmation for apply
EOF
}
main() {
# 인자 파싱
# 환경 검증
# 함수 호출
:
}
main "$@"
```
- 엔트리포인트 스크립트는 **얇게**. 비즈니스 로직은 `lib/` 또는 `tasks/`.
- `usage()` 함수 필수.
### 6. 변수는 `local`, command substitution 은 분리
```bash
bad_pattern() {
local ctx="$(kubectl config current-context)" # local 이 exit status 가려버림
}
good_pattern() {
local ctx
ctx="$(kubectl config current-context)" # 분리 → $? 보존
}
```
ShellCheck SC2155 가 이것을 잡음.
### 7. Idempotent 를 기본값으로
- `create` 보다 `apply` / `ensure` 성격.
- `kubectl apply -k` 는 idempotent.
- `mkdir -p`, `kubectl create namespace X --dry-run=client -o yaml | kubectl apply -f -` 패턴.
- destroy 성격은 반드시 opt-in.
### 8. `kubectl diff` → `kubectl apply` 필수 흐름
프로덕션 적용 스크립트 기본 흐름:
```
1. kubectl kustomize <overlay> > render.yaml # render
2. kubeconform / kubectl apply --dry-run=server # validate
3. kubectl diff -k <overlay> # preview
4. confirm gate (CONFIRM=yes 또는 --yes)
5. kubectl apply -k <overlay> # apply
6. kubectl rollout status ... --timeout=10m # watch
```
### 9. `--dry-run=server` 를 validation 기본값으로
client-side dry run 은 CRD schema / admission webhook 을 평가하지 않는다. **server-side dry run** 을 쓴다:
```bash
kubectl apply -k "${OVERLAY}" --dry-run=server
```
### 10. destructive 작업은 `--yes` 또는 `CONFIRM=yes` gate
delete / prune / restore overwrite 류는 명시적 opt-in 없이 실행 금지.
```bash
if [[ "${CONFIRM:-no}" != "yes" ]]; then
fatal "destructive operation requires CONFIRM=yes"
fi
```
또는:
```bash
if [[ "${YES:-0}" -ne 1 ]]; then
warn "re-run with --yes to confirm"
exit 2
fi
```
### 11. 환경을 암묵적으로 추론하지 않는다
- 대상 overlay / namespace / context 는 **명시적 인자**로.
- `kubectl config current-context` 에 몰래 의존 금지.
- 필요한 env var 는 시작 시 `[[ -z "${FOO:-}" ]] && fatal "FOO required"` 로 검증.
### 12. JSON 파싱은 `jq` / `kubectl -o jsonpath`, 절대 regex 로 하지 않는다
```bash
# BAD
kubectl get pod foo -o yaml | grep "image:" | awk '{print $2}'
# GOOD
kubectl get pod foo -o jsonpath='{.spec.containers[0].image}'
# GOOD
kubectl get pod foo -o json | jq -r '.spec.containers[0].image'
```
kubectl/kubernetes 출력에 regex 쓰면 field 순서 / 라벨 / 버전 변화에 깨진다.
### 13. 비밀값은 로그 / stdout / 파일에 남기지 않는다
- env var / secret value 를 `set -x` 아래에서 직접 사용 금지.
- debug 모드에서는 masking:
```bash
mask_secrets() {
sed -E \
-e 's/(password=)[^ ]+/\1***/g' \
-e 's/(token=)[^ ]+/\1***/g' \
-e 's/(Authorization: Bearer )[A-Za-z0-9._-]+/\1***/g'
}
some_command --debug | mask_secrets
```
- secret 을 참조해야 하면 `--from-file` 이나 stdin pipe 로 주입, argv 금지.
### 14. 스크립트는 선언형 원본을 소유하지 않는다
**금지**:
- 대규모 heredoc YAML 생성기 (스크립트 내부에 매니페스트 숨김).
- 환경별 로직이 if/else 로만 존재.
- 스크립트만 실행해야 실제 상태를 알 수 있는 구조.
**허용**:
- `kubectl apply -k overlays/<env>` wrapping.
- Helm chart render + apply orchestration.
- backup / restore (stateful data 만 대상).
- bootstrap (namespace, secret store 설치 같은 일회성).
- smoke test.
### 15. 폴더 구조
```text
scripts/
bin/ # 엔트리포인트 (얇게)
render
diff
apply
backup-k3s
restore-k3s
lib/ # 공통 함수
common.sh # log, fatal, require_cmd, confirm
kubectl.sh # kubectl wrappers
kustomize.sh # kustomize render helpers
tasks/ # 도메인 작업
keycloak.sh
vault.sh
flyway.sh
ci/ # CI 검증 전용
lint.sh
validate.sh
```
- `bin/` 파일 이름은 동사.
- `lib/` 는 20개 내외, 잡동사니 함수 금지.
- 하나의 거대 `deploy.sh` 금지.
### 16. retry 는 함수화, 무한 루프 금지
```bash
retry() {
local max="$1"; shift
local delay="$1"; shift
local n=0
until "$@"; do
n=$((n + 1))
if (( n >= max )); then
return 1
fi
sleep "${delay}"
done
}
retry 5 3 kubectl rollout status deployment/foo --timeout=30s
```
backoff 는 선형/지수 명시, 무한 retry 금지.
### 17. quoting / array 기본값
- 모든 변수 전개는 `"${VAR}"`.
- 인자 list 는 array: `args=(--namespace foo --context bar)`.
- `"$@"` 유지.
- unquoted glob / word splitting 금지.
### 18. 출력 채널 규칙
- stdout → 머신 판독 결과 (jsonpath 결과, 렌더된 YAML 등).
- stderr → 로그, 경고, 에러, 진행 표시.
- exit code → 0 success, 1 error, 2 usage error.
pipeline 하류 도구가 stdout 을 parse 한다는 전제로 작성.
## 프로젝트 기준 요약
- strict mode `set -euo pipefail` + `IFS=$'\n\t'` 필수.
- trap EXIT INT TERM 으로 정리 보장.
- ShellCheck + shfmt CI 필수.
- ISO 8601 UTC + LEVEL 로그 함수 (stderr).
- `main "$@"` 패턴 + usage() 함수.
- local 선언과 command substitution 분리.
- idempotent 기본, destructive 는 `--yes` / `CONFIRM=yes` gate.
- `kubectl diff``apply`, `--dry-run=server` validation.
- 환경 추론 금지, overlay/namespace/context 명시.
- JSON 은 jq / jsonpath, 절대 regex 금지.
- secret 은 log / argv 에 남기지 않고 masking.
- 스크립트는 선언형 원본을 소유하지 않는 orchestration 레이어.
- `bin/ lib/ tasks/ ci/` 폴더 분리, giant deploy.sh 금지.
+183
View File
@@ -0,0 +1,183 @@
# security hardening 기준
## 목적
이 문서는 K3s/Kubernetes 기반 인프라(1000+ 서비스 규모)에서
- 어떤 Pod Security Standard(PSS) 수준을 강제할지
- Pod/ServiceAccount/RBAC/NetworkPolicy/Secret/Image supply chain을 어디까지 하드닝할지
- Platform 예외를 어떻게 선언할지
를 단일 ground truth로 고정한다.
이 문서의 목표는 다음과 같다.
- root/privileged/host namespace 사용을 기본 금지하고 예외는 manifest로 증명한다
- allow-all network/RBAC을 운영 기본값으로 두지 않는다
- Secret의 저장·접근·전송 모든 단계에서 신뢰 경계를 명시한다
- K3s production hardening(PSS, NetworkPolicy, audit, at-rest encryption)을 묶음으로 본다
## 공식 의미 (근거)
- Kubernetes 1.25부터 `PodSecurityPolicy`(PSP)는 제거되었다. 대체는 **Pod Security Admission (PSA)** + `pod-security.kubernetes.io/*` namespace label이다.
- Pod Security Standards는 `privileged`, `baseline`, `restricted` 세 프로파일이다. `restricted`는 업계 최신 hardening best practice를 반영한다.
- PSA는 `enforce`, `audit`, `warn` 세 모드를 지원하고, 각 모드마다 버전을 `latest`/`vX.Y`로 고정할 수 있다.
- `restricted` 프로파일이 강제하는 주요 필드: `runAsNonRoot=true`, `allowPrivilegeEscalation=false`, `capabilities.drop=["ALL"]`(네트워크 capability는 `NET_BIND_SERVICE`만 추가 허용), `seccompProfile.type in {RuntimeDefault, Localhost}`, host namespace/Port/Path 금지, `privileged=false`, `procMount=Default`, ephemeral volume 화이트리스트.
- NetworkPolicy는 namespace 내 매칭되는 Pod가 하나라도 있으면 그 Pod의 해당 방향 트래픽은 **정책 합집합**만 허용된다(그 외 default deny). 매칭되는 Pod가 없으면 기본은 allow-all이다.
- NetworkPolicy `from`/`to` 원소 내에서 `namespaceSelector``podSelector`**동일 엔트리** 안에 두면 AND(교집합), **별도 엔트리**로 두면 OR(합집합)로 계산된다. 이 차이가 cross-namespace 정책 버그의 1순위 원인이다.
- Secret은 기본적으로 etcd에 base64로만 저장되므로 운영 클러스터는 `EncryptionConfiguration`(aescbc/aesgcm/KMS)을 필수로 구성한다. K3s는 `--secrets-encryption` 플래그로 aescbc provider를 활성화한다.
- ServiceAccount token은 Pod에 기본 자동 마운트된다. 1.24부터는 time-bound projected token이 기본이다.
- RBAC는 additive-only이며 `Role`/`RoleBinding`(namespace) 우선, `ClusterRole`/`ClusterRoleBinding`은 예외적이다.
## 기본 규칙
### 1. 모든 application namespace는 PSA `restricted` enforce 라벨이 기본
namespace 생성 시 다음 라벨을 **enforce** 수준으로 붙인다(예외는 rule 3).
```yaml
metadata:
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/enforce-version: v1.29
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/audit-version: v1.29
pod-security.kubernetes.io/warn: restricted
pod-security.kubernetes.io/warn-version: v1.29
```
- `enforce`: deny on violation (hard gate)
- `audit`: audit log 기록
- `warn`: kubectl 사용자에 경고
- version은 `latest` 대신 **명시 버전 pin**을 기본으로 한다. 업그레이드는 ADR로 관리한다.
- PSP는 1.25에서 제거되었으므로 어떤 manifest/차트에도 `policy/v1beta1 PodSecurityPolicy`를 남기지 않는다.
### 2. Restricted 프로파일 전 필드 계약
application Pod/Deployment는 아래 전부를 만족해야 한다. 하나라도 빠지면 PSA가 reject한다.
- `spec.automountServiceAccountToken: false` (API 호출 불필요 시)
- `spec.securityContext.runAsNonRoot: true`
- `spec.securityContext.runAsUser: <non-zero numeric>` (예: `10001`)
- `spec.securityContext.runAsGroup: <non-zero numeric>` (예: `10001`)
- `spec.securityContext.fsGroup: <numeric>` (volume 쓰기 필요 시)
- `spec.securityContext.seccompProfile.type: RuntimeDefault` (Pod 또는 컨테이너 레벨)
- 컨테이너 `securityContext`:
- `allowPrivilegeEscalation: false`
- `privileged: false`
- `readOnlyRootFilesystem: true`
- `runAsNonRoot: true`
- `capabilities.drop: ["ALL"]`
- `capabilities.add`: 비어있거나 `["NET_BIND_SERVICE"]`만 허용
- Pod spec 금지 필드: `hostNetwork`, `hostPID`, `hostIPC`, `hostUsers=false`, `hostPath` volume, `hostPort`, `ephemeralContainers`에 한해 `privileged`
### 3. Platform 예외 namespace는 ADR 문서 + 좁은 enforce
`kube-system`, `ingress-traefik`, `vault`, `cert-manager`, `vault-secrets-operator`, `monitoring` 등은 `baseline` 또는 `privileged` 프로파일이 필요할 수 있다. 예외는 다음을 manifest에 고정한다.
- namespace label은 **필요한 최저 수준**(`baseline` 우선, `privileged`는 CNI/CSI/Node-exporter에 한정)
- 예외 근거 ADR 링크 annotation: `platform.example.com/psa-exception: "ADR-0042"`
- 예외 받는 구체 필드(예: `hostNetwork`, `CAP_NET_ADMIN`)만 열고 나머지는 restricted
### 4. privileged / host namespace 기본 금지
application Pod는 `privileged: true`, `hostNetwork`, `hostPID`, `hostIPC`, `hostPath`, `hostPort`를 쓰지 않는다. 필요성이 있으면 rule 3의 platform 예외로 이관한다.
### 5. runAsNonRoot + numeric UID 강제
image가 `USER` 지시어로 non-numeric user만 지정해도 PSA는 runtime에 UID 확인이 불가능하면 reject할 수 있다. 항상 numeric UID를 명시한다(권장 범위: 1000065535). UID 0은 전 영역 금지.
### 6. seccompProfile은 RuntimeDefault 우선
Pod 레벨 `seccompProfile.type: RuntimeDefault`를 기본으로 두어 모든 컨테이너에 상속. 특정 컨테이너가 custom profile이 필요하면 `Localhost`로 개별 선언하고 profile 파일 경로를 문서화한다. kubelet `--seccomp-default=true`를 클러스터 플래그로 검토한다.
### 7. capabilities drop-first
`drop: ["ALL"]`이 기본이다. 추가 허용 화이트리스트는 `NET_BIND_SERVICE`뿐. `CAP_SYS_ADMIN`, `CAP_NET_ADMIN`, `CAP_SYS_PTRACE`, `CAP_NET_RAW`는 platform 예외에서만 허용한다.
### 8. readOnlyRootFilesystem + writable emptyDir
모든 application container는 `readOnlyRootFilesystem: true`. 쓰기 경로는 `emptyDir`(가능하면 `medium: Memory`, `sizeLimit` 명시)로 분리한다. `/tmp`, `/var/run`, 앱 cache 경로는 별도 volume mount.
### 9. ServiceAccount는 workload 1:1, 토큰 기본 비마운트
- `default` SA 사용 금지. namespace당 Deployment별 전용 SA 생성.
- `automountServiceAccountToken: false`를 Pod spec에 기본 명시.
- Kubernetes API를 호출해야 하는 Pod만 `true` + projected token volume을 명시적으로 선언.
### 10. RBAC는 namespace Role + RoleBinding 우선
- `*` resource/verb 금지.
- `secrets` 리소스는 `get` + `resourceNames` 명시. `list`/`watch`는 controller/operator에만 허용.
- `ClusterRole`/`ClusterRoleBinding`은 CRD controller, metrics scraper, admission webhook 같은 cluster-wide 컴포넌트에 한정하고 subject는 platform SA로 제한.
- `system:masters` group binding 금지.
### 11. NetworkPolicy default-deny + 명시적 allow
운영 namespace는 생성 직후 다음 3종을 배포한다.
1. default-deny-all (ingress + egress)
2. allow-dns-egress (to `kube-system``k8s-app=kube-dns`, UDP/TCP 53)
3. allow-from-ingress-controller (namespaceSelector=`ingress-traefik` + podSelector=`app.kubernetes.io/name=traefik`)
추가 allow는 서비스별 요구(예: DB, Redis, Vault, S3, OIDC endpoint)에 맞춰 **한 엔트리 = AND, 여러 엔트리 = OR** 규칙을 지켜 작성한다.
### 12. NetworkPolicy enforcement 전제 검증
- K3s 기본 CNI(flannel) + kube-router policy controller가 실제로 enforce하는지 배포 후 negative test 필수.
- Calico/Cilium 전환 시 `--disable-network-policy` + `--flannel-backend=none` 조합을 ADR로 관리.
- 운영 정책 변경 후에는 synthetic probe(`netshoot` Pod)로 deny/allow 경로를 모두 검증한다.
### 13. Secret at-rest encryption은 운영 필수
- API Server `--encryption-provider-config` 지정: `aescbc` 또는 KMS provider(권장: AWS KMS/GCP KMS/HashiCorp Vault Transit).
- K3s는 `--secrets-encryption` 플래그 활성화(aescbc). 기존 Secret은 `kubectl get secrets -A -o json | kubectl replace -f -`로 재암호화.
- etcd 백업 자체도 별도 암호화 저장.
### 14. 민감정보 delivery 경로 표준화
1순위: Vault Secrets Operator(VSO)가 Vault → K8s Secret으로 sync → envFrom/volume
2순위: External Secrets Operator(ESO) + AWS/GCP Secret Manager
3순위: CSI Secret Store Driver (volume mount only, K8s Secret 미생성)
4순위: SealedSecrets / SOPS (GitOps + encrypted-at-rest in Git)
모든 경로는 config-and-secrets 문서의 선택 기준 표를 따른다.
### 15. Image supply chain 통제
- `imagePullPolicy: Always`는 mutable tag(`latest`, `main`)에만. 운영은 **digest pin** `image: registry.example.com/auth-server@sha256:<64hex>` 을 기본으로.
- `kubernetes.io/dockerconfigjson` 타입 `imagePullSecret`은 workload SA에 `spec.imagePullSecrets`로 연결.
- Private registry만 허용: `ImagePolicyWebhook` 또는 Kyverno/Gatekeeper로 public Docker Hub deny.
- Image signing(cosign)과 SBOM 요구를 CI에서 강제, cluster-level로는 `ClusterImagePolicy` (sigstore policy-controller) 검토.
### 16. health/metrics/admin endpoint 내부 전용 기본
- `/metrics`는 Service 별도 포트(`name: metrics`) + NetworkPolicy로 `monitoring` namespace Prometheus만 허용.
- `/actuator/*`, `/admin`, `/debug/pprof`는 Ingress 경로에 노출 금지.
- Keycloak `/admin/`, `/metrics`, `/health`는 외부 공개 금지 기본값(network-ingress-tls 문서와 함께 enforce).
### 17. audit + policy together
- API Server `--audit-policy-file`로 최소한 Secret/RBAC/PodSecurity violation을 `RequestResponse` 수준으로 기록.
- PSA `audit` 라벨을 모든 namespace에 붙여 violation을 audit log로 수집.
- Kyverno 또는 Gatekeeper로 PSA 밖의 policy(resource limits, image registry, required labels)를 보완.
### 18. Pod spec 기타 하드닝 기본
- `resources.limits.cpu`, `resources.limits.memory` 필수. memory limit 없는 Pod는 OOM-Kill 전파 위험.
- `terminationGracePeriodSeconds` 명시(기본 30은 서비스별로 재조정).
- `readinessProbe` + `livenessProbe` 분리. `startupProbe`는 JVM/느린 기동 앱에 필수.
- `topologySpreadConstraints` 또는 `podAntiAffinity`로 node 단일 장애 블라스트 반경 축소.
### 19. 현재 스택 기본 권장안
#### auth-server / test-server / keycloak / migration-flyway
- namespace PSA: `restricted` enforce pinned to `v1.29`
- SA: 서비스별 1:1, `automountServiceAccountToken: false`
- Secret: VSO로 Vault → K8s Secret sync
- NetworkPolicy: default-deny + dns + ingress + db + vault + metrics-scrape
#### ingress-traefik
- namespace PSA: `baseline` (예외 ADR 기록)
- `hostNetwork` 금지(ServiceLB 또는 MetalLB 사용), hostPort는 80/443/8443만
- CAP_NET_BIND_SERVICE만 add
#### vault / vault-secrets-operator
- namespace PSA: `baseline` (Vault server IPC_LOCK 필요)
- storage PVC는 encrypted StorageClass
- unseal key는 cluster 밖(HSM/KMS auto-unseal)
#### db (Postgres/MySQL)
- namespace PSA: `restricted` (StatefulSet, fsGroup 999)
- NetworkPolicy: application SA의 Pod만 5432 허용
- backup은 별도 namespace의 Job에서 수행, 해당 Job에만 read-only secret 부여
## 프로젝트 기준 요약
- 모든 app namespace에 `pod-security.kubernetes.io/enforce: restricted` + pinned version 라벨 부착
- PSP는 제거되었으므로 어디에도 남기지 않는다
- Restricted 프로파일 전 필드 계약을 Pod/Deployment가 만족
- default-deny NetworkPolicy + DNS allow + ingress allow + metrics-scrape allow를 namespace 기본 세트로 배포
- `namespaceSelector`+`podSelector` AND/OR 차이를 정확히 사용
- Secret at-rest encryption + VSO(1순위) delivery 표준화
- 운영 이미지는 digest pin, private registry only
- audit policy + Kyverno/Gatekeeper 보완 정책과 함께 운영
+227
View File
@@ -0,0 +1,227 @@
# storage / PVC 기준
## 목적
PVC는 단순히 "데이터를 남기기 위한 옵션"이 아니라,
- 어떤 워크로드가 상태를 가지는지
- 그 상태의 수명과 복구 단위가 무엇인지
- 어떤 storage class / access mode / reclaim policy / binding mode가 필요한지
- snapshot / expansion 지원이 필요한지
를 먼저 고정한 뒤에 사용한다.
이 문서의 목표는 다음과 같다.
- 상태 저장 워크로드와 무상태 워크로드를 저장소 기준으로 명확히 구분한다
- separate PVC 남발을 막는다
- K3s 기본 local-path provisioner의 운영 사용 범위를 통제한다
- PVC lifecycle과 backup/restore 단위를 먼저 고정한다
- StorageClass / VolumeSnapshotClass / reclaim policy / binding mode를 선언적으로 명시한다
## 공식 의미 (Kubernetes 기준)
- PV는 클러스터의 저장소 리소스이며 Pod lifecycle과 독립적이다.
- PVC는 저장소에 대한 요청(size, access mode, StorageClass, volumeMode 등)이다.
- StorageClass는 동적 프로비저닝 파라미터, `reclaimPolicy`, `allowVolumeExpansion`, `volumeBindingMode`, `mountOptions`를 정의한다.
- `reclaimPolicy`는 PV 해제 시 동작을 결정한다. 동적 프로비저닝 PV의 기본값은 `Delete`다. 운영 데이터가 있으면 StorageClass에서 `Retain`을 명시한다.
- `volumeBindingMode`의 기본값은 `Immediate`이며, topology-aware / late-binding이 필요하면 `WaitForFirstConsumer`를 사용한다.
- `hostPath`는 single-node testing 전용이다. 운영 클러스터에서 사용하지 않는다.
- K3s는 Rancher Local Path Provisioner를 기본 제공해 노드 로컬 저장소를 사용할 수 있지만, RWO만 지원하고 snapshot/expansion은 지원하지 않는다.
- VolumeSnapshot / VolumeSnapshotContent / VolumeSnapshotClass는 CSI snapshot을 위한 K8s API다. `deletionPolicy: Retain` / `Delete`를 정책에 맞게 선택한다.
- StatefulSet은 `persistentVolumeClaimRetentionPolicy`로 삭제/스케일다운 시 PVC 보존 여부를 제어할 수 있다.
## 기본 규칙
### 1. PVC는 상태가 있을 때만 사용
다음 중 하나가 아니면 PVC를 붙이지 않는다.
- 재시작 후에도 유지되어야 하는 데이터가 있음
- Pod 교체와 무관하게 보존되어야 하는 파일/데이터가 있음
- 복구 대상이 되는 저장 상태가 있음
- 애플리케이션이 명시적으로 영속 저장소를 요구함
금지:
- "혹시 몰라서" PVC 추가
- 로그/캐시/임시 파일을 습관적으로 PVC에 저장
- stateless 앱에 관성적으로 PVC 부착
### 2. PVC 존재만으로 StatefulSet을 결정하지 않는다
PVC가 있다고 무조건 StatefulSet은 아니다.
먼저 묻는다.
- Pod마다 고유한 저장소가 필요한가?
- stable network identity가 필요한가?
- 순서 있는 확장/축소가 필요한가?
아니면:
- Deployment + 단일 PVC(RWO, replicas 1) 또는 Deployment + RWX PVC
도 가능하다.
### 3. separate PVC는 "데이터 수명과 복구 단위가 다를 때만"
하나의 워크로드가 여러 PVC를 가져도 되는 경우는 아래와 같다.
- 데이터 종류별 수명주기가 다름
- backup/restore 단위가 다름
- 성능 요구(StorageClass) 또는 IOPS 특성이 다름
- 보안/접근 제어 단위가 다름
- 장애 시 독립적으로 보존/삭제되어야 함
금지:
- 디렉터리 몇 개를 기계적으로 PVC로 분리
- mount path별로 습관적으로 PVC 추가
- 이유 없이 "앱 데이터/설정/로그"를 모두 개별 PVC로 분리
### 4. 기본 원칙은 "적게, 명확하게"
기본적으로는 하나의 워크로드 / 하나의 상태 저장 목적 / 하나의 PVC를 먼저 검토한다.
분리는 정당한 이유(#3)가 있을 때만 한다.
### 5. StorageClass는 항상 명시적으로 지정
PVC는 `storageClassName`을 항상 명시한다. 클러스터 default annotation에 의존하지 않는다.
기본:
- 운영 표준 StorageClass 3~5개를 미리 정의 (예: `fast-ssd-retain`, `standard-delete`, `archive-retain`, `rwx-shared`)
- 성능/복제/노드 종속성 차이가 있으면 workload별로 구분
- 각 StorageClass는 `provisioner`, `reclaimPolicy`, `volumeBindingMode`, `allowVolumeExpansion`을 모두 선언
### 6. StorageClass `volumeBindingMode` 기본값은 `WaitForFirstConsumer`
운영 표준은 `WaitForFirstConsumer`다.
이유:
- Pod가 스케줄되는 노드의 topology(zone, node-local disk, GPU affinity 등)에 맞춰 PV를 바인딩한다
- `Immediate`는 PVC 생성 즉시 PV를 바인딩하므로, 이후 Pod가 해당 노드/zone에 스케줄되지 못하는 상황이 생긴다
- K3s local-path provisioner는 노드 로컬이므로 반드시 `WaitForFirstConsumer`여야 한다
`Immediate` 허용 예외:
- 네트워크 스토리지(Ceph, NFS, S3 CSI 등)이고 topology 제약이 없는 경우
- 사전에 PV를 warm-up 해야 하는 특수 케이스
### 7. StorageClass `reclaimPolicy`는 데이터 등급에 맞춘다
동적 프로비저닝의 기본 `reclaimPolicy``Delete`다. 이는 PVC 삭제 시 PV와 데이터가 사라진다는 뜻이다.
기본:
- production stateful data (DB, object store backend, identity store 등) → `Retain`
- dev/test, ephemeral cache, rebuild-safe data → `Delete`
- `Retain`을 쓰면 PVC 삭제 후 남은 PV를 정리하는 책임이 운영자에게 생긴다. runbook에 정리 절차를 명시한다.
### 8. `allowVolumeExpansion`은 기본 `true`로 두되 축소는 불가
PVC 확장 요구는 자주 생긴다. StorageClass에서 `allowVolumeExpansion: true`를 기본으로 둔다.
주의:
- PVC 용량 축소는 K8s가 지원하지 않는다
- 파일시스템 online expansion 지원 여부는 CSI 드라이버마다 다르다
- 확장 후 Pod 재시작이 필요한 드라이버가 있다
### 9. AccessMode는 실제 요구에 맞게 고른다
기본:
- 단일 writer면 `ReadWriteOnce` (RWO)
- 동일 노드의 여러 Pod가 공유 필요시 `ReadWriteOncePod` (K8s 1.27+) 또는 RWO
- 여러 Pod/노드 동시 read/write가 진짜 필요할 때만 `ReadWriteMany` (RWX)
- 읽기 전용 공유는 `ReadOnlyMany` (ROX)
편의상 RWX를 기본값으로 두지 않는다. RWX는 NFS/CephFS 같은 별도 스토리지 백엔드를 요구한다.
### 10. K3s local-path provisioner는 운영에서 기본값 아님
K3s 기본 local-path provisioner의 하드 제약:
- RWO 전용 (RWX 불가)
- VolumeSnapshot 미지원
- VolumeExpansion 미지원
- 노드 로컬이므로 Pod가 특정 노드에 pin 됨 → 노드 장애 시 데이터 접근 불가
- backup은 노드 파일시스템에 직접 접근해야 함
기본:
- dev/test: 허용
- production: Longhorn, OpenEBS, Rook-Ceph, 또는 클라우드 CSI driver(EBS, PD, Azure Disk 등)로 교체
- 불가피하게 prod에서 local-path를 쓸 경우 `backup-restore.md`와 반드시 연동하고 노드 affinity/zone 분리를 명시
### 11. `hostPath` 직접 사용 금지
운영 PV/PVC에 `hostPath`를 사용하지 않는다.
예외:
- 학습/단일 노드 로컬 테스트
- 매우 제한된 디버깅 용도 (CSI driver 진단 등)
운영 표준으로 채택하지 않는다.
### 12. VolumeSnapshotClass를 StorageClass와 1:1로 매칭
snapshot 대상 PVC가 있는 StorageClass는 대응되는 VolumeSnapshotClass를 반드시 정의한다.
기본:
- `driver`는 StorageClass의 provisioner와 맞춤
- `deletionPolicy`는 운영 데이터면 `Retain`, ephemeral이면 `Delete`
- snapshot class는 `labels`로 RPO/retention 정책과 연결
### 13. PVC lifecycle은 workload 생성 전에 문서화
PVC를 만들기 전에 아래를 정한다.
- 누가 생성하는가 (Helm, Kustomize, Operator, manual)
- 누가 삭제하는가 (GitOps sync, 운영자 수동)
- scale down 시 어떻게 되는가
- workload 삭제 시 어떻게 되는가
- backup 대상인가 (어떤 RPO/RTO)
- restore 단위인가 (PVC / VolumeSnapshot / backup tool 별)
"삭제하면 같이 정리되겠지"를 금지한다.
### 14. StatefulSet의 PVC retention policy를 명시적으로 검토
StatefulSet을 쓰는 경우 `persistentVolumeClaimRetentionPolicy.whenDeleted` / `whenScaled`를 기본값에 두지 않는다.
기본:
- 운영 데이터: 둘 다 `Retain`
- ephemeral 데이터: 둘 다 `Delete`
- 혼용시 명시적 이유를 주석에 남김
### 15. Pod와 PVC는 같은 namespace 소유권
PVC는 Pod와 같은 namespace에서 사용된다. 스토리지도 workload의 namespace 소유권을 따라간다.
금지:
- "공용 저장소 namespace"에 무분별하게 PVC 몰아넣기
- 여러 서비스가 의미 없이 같은 PVC를 기대하는 구조
### 16. 워크로드별 기본 선택
| Workload | 기본 PVC | StorageClass | AccessMode | Snapshot |
|---|---|---|---|---|
| auth-server | 없음 | - | - | - |
| test-server | 없음 | - | - | - |
| ingress-controller | 없음 | - | - | - |
| migration-flyway (Job) | 없음 | - | - | - |
| Keycloak (external DB) | 없음 | - | - | - |
| PostgreSQL / CNPG | 필수 | fast-ssd-retain | RWO | 필수 |
| Vault (raft) | 필수 | fast-ssd-retain | RWO | 필수 |
| MinIO | 필수 | standard-retain | RWO | 보조 (replication 우선) |
### 17. 로그와 임시 파일은 PVC 기본 금지
다음은 기본적으로 PVC에 저장하지 않는다.
- application log (→ stdout + 로그 수집기)
- temp file (→ `emptyDir`)
- cache (→ `emptyDir` 또는 memory-backed)
- rendered config copy
- transient upload staging
정말 영속화가 필요하면 이유를 주석에 명시한다.
### 18. backup/restore와 반드시 연결
PVC를 허용한 워크로드는 반드시 아래와 연결한다.
- `backup-restore.md` (Velero schedule, snapshot class, RPO/RTO)
- `operations-runbook-upgrade-rollback.md` (복구 절차)
PVC가 생기면 복구 전략도 같이 생겨야 한다. 백업 없는 PVC는 merge 금지.
### 19. 파일시스템 / 블록 모드 명시
`volumeMode`는 기본 `Filesystem`이지만, DB raw block 같은 경우 `Block`을 쓸 수 있다. DB 운영이 요구하지 않으면 `Filesystem` 고정.
### 20. securityContext와 fsGroup
PVC를 쓰는 Pod는 `securityContext.fsGroup` 또는 `fsGroupChangePolicy: OnRootMismatch`를 명시해서 permission 문제를 예방한다. restricted PSA 하에서는 `runAsNonRoot: true`, `runAsUser`, `fsGroup`을 모두 설정한다.
## 프로젝트 기준 요약
- PVC는 상태가 있을 때만, separate PVC는 수명/복구 단위가 다를 때만
- StorageClass는 항상 명시, `volumeBindingMode: WaitForFirstConsumer` 기본, `reclaimPolicy`는 데이터 등급에 맞춤
- 동적 프로비저닝 기본 `reclaimPolicy=Delete`를 인지하고 운영 데이터는 `Retain` 명시
- K3s local-path는 RWO / no snapshot / no expansion — prod 기본값 아님
- VolumeSnapshotClass를 StorageClass와 매칭해서 정의
- StatefulSet PVC retention policy 명시
- 로그/임시 파일은 PVC 기본 금지
- PVC가 생기면 backup/restore 기준도 같이 만든다
+277
View File
@@ -0,0 +1,277 @@
# Vault 기준
## 목적
이 문서는 Kubernetes 환경에서 HashiCorp Vault 1.17+ 를 1000+ 서비스의 secret / PKI / dynamic credential 소스로 운영하기 위한 기준을 고정한다.
- Integrated Storage (Raft) HA + auto-unseal을 1차 권장 경로로 둔다
- 공식 Helm chart (`hashicorp/vault`) values.yaml의 핵심 필드를 명시한다
- Vault Secrets Operator (VSO) 0.8+ CRD 경로를 secret delivery 기본값으로 둔다
- Vault Agent Injector는 Kubernetes Secret을 우회하고 싶은 워크로드의 2차 경로로 둔다
- Raft snapshot / audit device / telemetry / TLS / Kubernetes auth role을 운영 필수 요소로 둔다
## 공식 의미 (Vault 1.17+ 기준)
- Vault는 **sealed** 상태로 기동한다. Shamir 수동 unseal 또는 auto-unseal (`awskms`, `gcpckms`, `azurekeyvault`, `transit`)로 unseal한다.
- **Integrated Storage (Raft)**는 공식 지원 HA backend다. 기동 시 `storage "raft"` stanza, `cluster_addr`, listener의 `cluster_address`가 모두 필요하다. `ha_storage`와 동시 선언 금지.
- Vault는 두 포트를 쓴다: **`8200` (API/client), `8201` (cluster-to-cluster Raft replication)**. Service는 8201을 반드시 expose해야 peer-to-peer Raft가 성립한다.
- `/v1/sys/health` 는 단일 endpoint로 상태 코드로 응답한다: `200` active, `429` standby (`standbyok=true`면 200), `472` DR secondary, `473` performance standby, `501` uninitialized, `503` sealed.
- **Audit device는 최소 하나 활성화해야 한다.** audit device가 전부 실패하면 Vault는 요청 처리를 멈춘다(블로킹). 여러 개 운영 권장.
- Kubernetes auth method는 ServiceAccount JWT를 TokenReview API로 검증한다. Vault 1.17+는 short-lived projected SA token(`audiences`)을 권장한다.
- VSO 0.8+는 `secrets.hashicorp.com/v1beta1` API group을 사용하고 `VaultConnection`, `VaultAuth`, `VaultStaticSecret`, `VaultDynamicSecret`, `VaultPKISecret`, `HCPAuth`, `HCPVaultSecretsApp` CRD를 제공한다.
- Vault Agent Injector는 `vault.hashicorp.com/agent-inject: "true"` 같은 Pod annotation으로 sidecar/init container를 주입해 secret을 `/vault/secrets/<name>` 파일로 렌더링한다.
- DR replication / Performance replication은 **Enterprise 기능**이다. OSS에서는 Raft snapshot restore가 복구 경로다.
- Telemetry는 `telemetry { prometheus_retention_time = "24h" disable_hostname = true }` stanza로 활성화하고 `/v1/sys/metrics?format=prometheus`에서 scrape한다.
## 기본 규칙
### 1. 배포는 공식 Helm chart (`hashicorp/vault`)
기본:
- `helm repo add hashicorp https://helm.releases.hashicorp.com`
- `server.ha.enabled=true` + `server.ha.raft.enabled=true`
- `injector.enabled` 는 secret delivery 전략에 따라 결정 (VSO만 쓰면 `false`)
- values.yaml은 Git에 보관 + Helmfile / Argo CD Application로 배포
기본 금지:
- 수제 StatefulSet으로 처음부터 조립
- `dev` 모드 운영
- chart 기본 `standalone` 모드 production 사용 (single node + file storage)
### 2. HA topology: Raft 3-node 또는 5-node
기본:
- `server.ha.replicas: 3` (과반수 장애 허용: 1 node)
- critical path면 `5`로 확장 (2 node 장애 허용)
- `server.ha.raft.setNodeId: true` (각 pod의 hostname을 node_id로 자동 주입)
- anti-affinity: hostname 기준 required, zone 기준 preferred
### 3. Raft config: listener 8200 + cluster 8201 + service_registration
`server.ha.raft.config` HCL에 최소한 아래 stanza가 필요하다.
```hcl
ui = true
listener "tcp" {
address = "[::]:8200"
cluster_address = "[::]:8201"
tls_disable = 0
tls_cert_file = "/vault/tls/tls.crt"
tls_key_file = "/vault/tls/tls.key"
}
storage "raft" {
path = "/vault/data"
node_id = "$(HOSTNAME)"
}
cluster_addr = "https://$(HOSTNAME).vault-internal:8201"
api_addr = "https://$(HOSTNAME).vault-internal:8200"
service_registration "kubernetes" {}
telemetry {
prometheus_retention_time = "24h"
disable_hostname = true
}
```
`cluster_addr`는 headless service(`vault-internal`)의 pod FQDN을 쓴다. 8201 Service expose 필수.
### 4. Auto-unseal 채택 (1차 권장)
기본:
- AWS: `seal "awskms" { region = "..." kms_key_id = "..." }`
- GCP: `seal "gcpckms" { project = "..." region = "..." key_ring = "..." crypto_key = "..." }`
- Azure: `seal "azurekeyvault" { tenant_id = "..." vault_name = "..." key_name = "..." }`
- Vault-to-Vault: `seal "transit" { address = "..." token = "..." key_name = "autounseal" mount_path = "transit/" }`
기본 금지:
- Shamir key를 CI/CD 환경변수나 Kubernetes Secret에 저장
- seal backend에 lifecycle 보호 없음 (KMS key deletion protection 필수)
### 5. Audit device는 최소 2개
Audit device 전부 실패 시 Vault가 요청을 block한다. redundancy 확보.
기본:
- `auth/kubernetes/login` 경로 포함 모든 API 감사
- `file`: `server.auditStorage.enabled: true``/vault/audit/audit.log`
- `syslog` 또는 `socket`: 중앙 로그 파이프라인 (Loki, Splunk, CloudWatch)
- `vault audit enable file file_path=/vault/audit/audit.log`
기본 금지:
- audit device 0개 운영
- audit log PVC 용량 무제한 (log rotation + sink 필수)
### 6. TLS는 end-to-end
기본:
- cert-manager Certificate로 `vault-tls` Secret 발급 (cluster issuer)
- listener에 `tls_cert_file`, `tls_key_file`, `tls_min_version = "tls13"`
- client (app, VSO, Injector)는 CA bundle trust
- Vault ↔ Storage ↔ seal backend 전 구간 TLS
### 7. Vault는 기본 내부 전용 (ClusterIP)
기본:
- Service type: ClusterIP (8200, 8201)
- Ingress 기본 금지
- 외부 관리자 접근은 VPN / bastion / port-forward / OIDC-protected admin Ingress
### 8. Probe: `/v1/sys/health` 상태코드 의미 반영
기본:
- readiness: `GET /v1/sys/health?standbyok=true&perfstandbyok=true&uninitcode=204` (uninitialized를 200으로 수용 초기 bootstrap 허용)
- liveness: `GET /v1/sys/health?standbyok=true&sealedcode=204&uninitcode=204` (sealed + uninit이어도 pod 생존)
- startup: initialDelay 10s, failureThreshold 12 (2분 유예)
기본 금지:
- `GET /` 단순 probe
- sealed 상태에서 liveness 실패 → 무한 재시작 루프
### 9. Kubernetes auth method 구성
Vault 쪽 (1회 bootstrap):
```bash
vault auth enable kubernetes
vault write auth/kubernetes/config \
token_reviewer_jwt="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \
kubernetes_host="https://kubernetes.default.svc.cluster.local" \
kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt \
disable_iss_validation=false
```
Role은 ServiceAccount + namespace에 바인딩:
```bash
vault write auth/kubernetes/role/auth-server \
bound_service_account_names=auth-server \
bound_service_account_namespaces=auth-prod \
policies=auth-server-read \
ttl=1h \
audience=vault
```
기본 금지:
- `bound_service_account_names=*` 또는 `bound_service_account_namespaces=*`
- TTL 무한 또는 24h 이상
### 10. Secret delivery: VSO가 1차 권장
기본:
- 클러스터 전체 1개 `VaultConnection` (namespace: `vault`)
- 앱 namespace마다 `VaultAuth` (ServiceAccount 바인딩)
- 정적 KV 동기화: `VaultStaticSecret`
- 동적 DB credential: `VaultDynamicSecret`
- TLS 인증서: `VaultPKISecret`
- `destination.create: true`로 K8s Secret 자동 생성, `rolloutRestartTargets`로 consumer 재시작
### 11. Vault Agent Injector: Kubernetes Secret 우회가 필요할 때
기본 annotation set:
```yaml
vault.hashicorp.com/agent-inject: "true"
vault.hashicorp.com/role: "auth-server"
vault.hashicorp.com/agent-inject-secret-db-creds: "database/creds/auth-server"
vault.hashicorp.com/agent-inject-template-db-creds: |
{{ with secret "database/creds/auth-server" -}}
DATABASE_USERNAME={{ .Data.username }}
DATABASE_PASSWORD={{ .Data.password }}
{{- end }}
vault.hashicorp.com/agent-pre-populate-only: "true" # init-only (앱이 파일 1회 읽음)
vault.hashicorp.com/agent-inject-file-db-creds: "db.env"
```
기본:
- etcd에 민감정보를 남기고 싶지 않을 때 선택
- 앱이 파일 기반 secret 소비 가능해야 함
- 장기 실행 sidecar 대신 `agent-pre-populate-only: "true"`로 init container만 사용해 resource overhead 감소
### 12. Raft snapshot 백업은 운영 필수
기본:
- 하루 1회 `vault operator raft snapshot save` CronJob
- snapshot을 off-cluster object storage (S3, GCS, MinIO replicated bucket)에 저장
- retention 30일 이상 + 주간 / 월간 snapshot 분리
- restore 절차를 runbook으로 문서화
### 13. Telemetry + Prometheus scrape
기본:
- config: `telemetry { prometheus_retention_time = "24h" disable_hostname = true }`
- 내부 Prometheus token policy:
```
path "sys/metrics" { capabilities = ["read"] }
```
- Prometheus scrape: `/v1/sys/metrics?format=prometheus` + Bearer token (unauth-endpoint 가능하지만 권장하지 않음)
### 14. 포트 expose: 8200 + 8201 둘 다
기본:
- Pod containerPort: 8200 (api), 8201 (cluster)
- Service `vault`: ClusterIP, 8200
- Service `vault-internal`: Headless, 8200 + **8201** (Raft peer discovery 필수)
- 8201 누락 시 Raft peer-to-peer 실패, leader election 불가
### 15. Replication 경계: OSS vs Enterprise
DR replication, performance replication, namespace multi-tenancy는 **Vault Enterprise** 전용이다.
OSS 기준 복구:
- Raft snapshot restore로 state 복원
- 동일 seal backend 요구 (auto-unseal이면 KMS key 필요)
기본 금지:
- OSS에서 DR topology를 가정한 설계
- Enterprise 기능을 OSS manifest에 넣기
### 16. Security context: Restricted PSS
기본:
- `runAsNonRoot: true`, `runAsUser: 100` (vault user)
- `readOnlyRootFilesystem: true`
- `allowPrivilegeEscalation: false`, `capabilities.drop: [ALL]`, `capabilities.add: [IPC_LOCK]` (mlockall을 위함, swap 방지)
- `seccompProfile: RuntimeDefault`
### 17. Resource 요청
기본 단일 replica (Raft 3 node 클러스터 중 하나):
- requests: `cpu: 250m`, `memory: 256Mi`
- limits: `cpu: 1`, `memory: 512Mi`
대규모 PKI / dynamic secret 발급량이 많으면 `memory: 1Gi` 이상.
### 18. Token / root token 취급
기본:
- `vault operator init` 출력 root token은 1회성 bootstrap
- 초기 설정 완료 후 `vault token revoke <root-token>`
- 장기 root 필요 시 `vault operator generate-root` 절차로 ephemeral 생성
- app token은 Kubernetes auth login 경로로만 발급
- CLI history에 unseal key, root token 남기지 않음 (`HISTCONTROL=ignorespace`)
### 19. 현재 스택 기본 권장안
- 배포: Helm chart `hashicorp/vault`, `server.ha.enabled=true` + `server.ha.raft.enabled=true`
- Replicas: 3
- Storage: Integrated Storage (Raft) + dataStorage PVC + auditStorage PVC
- Unseal: auto-unseal (awskms / gcpckms / azurekeyvault / transit)
- TLS: end-to-end, cert-manager Certificate
- Service: ClusterIP 8200 + Headless 8200/8201
- Ingress: 기본 금지 (관리자 경로만 OIDC-protected 예외)
- Probe: `/v1/sys/health` status-code aware
- Audit: file + syslog 중복
- Secret delivery: VSO 1차, Injector 2차
- Backup: daily Raft snapshot → off-cluster object storage
## 프로젝트 기준 요약
- Helm chart 공식 배포, HA Raft 3-node, auto-unseal
- 8200 (client) + 8201 (cluster) Service expose 필수
- `/v1/sys/health` status-code 기반 probe
- audit device 최소 2개, 전체 실패 시 block 특성 인지
- Kubernetes auth role은 SA + namespace 단위, wildcard 금지
- VSO 1차 / Injector 2차 (`agent-pre-populate-only` init-only 선호)
- Raft snapshot daily CronJob → off-cluster 보관
- Telemetry `/v1/sys/metrics?format=prometheus` + Prometheus token policy
- DR/perf replication은 Enterprise 기능, OSS 경계 분명
- Restricted PSS + IPC_LOCK capability (mlockall)
+324
View File
@@ -0,0 +1,324 @@
# workload selection 기준
## 목적
이 문서는 각 컴포넌트를
- Deployment
- StatefulSet
- DaemonSet
- Job
- CronJob
중 무엇으로 배포할지 먼저 고정한다.
목표:
- 상태 저장 / 무상태 / 노드로컬 / 일회성 / 주기성 워크로드를 섞지 않는다
- PVC가 필요하다는 이유만으로 StatefulSet을 선택하는 실수를 막는다
- Ingress controller / CNI / CSI / 로그 shipper / node-exporter 같은 노드로컬 에이전트를 Deployment로 배포하는 실수를 막는다
- migration / bootstrap / 백업을 장기 실행 앱과 분리한다
- 1000+ 서비스 스케일에서 operator-managed 패턴이 기본인 영역(DB, Kafka, monitoring)은 operator를 기본 선택으로 문서화한다
## 공식 의미 (근거)
- **Deployment**: stateless 장기 실행. `spec.replicas` 기반 수평 확장. ReplicaSet으로 rolling update. `https://kubernetes.io/docs/concepts/workloads/controllers/deployment/`.
- **StatefulSet**: stable network identity, stable persistent storage, ordered deployment/scaling. 각 Pod는 `<name>-<ordinal>` 이름을 가지고 PVC가 `volumeClaimTemplates`로 자동 생성. `persistentVolumeClaimRetentionPolicy` (GA since 1.27) 필드: 기본값 `{whenDeleted: Retain, whenScaled: Retain}`. `podManagementPolicy` (OrderedReady / Parallel). `updateStrategy` (RollingUpdate / OnDelete).
- **DaemonSet**: 선택된 모든 노드에 정확히 한 Pod를 실행. 노드 추가/제거에 따라 자동 생성/삭제. `https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/`.
- **Job**: 한 번 실행되어 완료. `restartPolicy: Never | OnFailure`. `backoffLimit` / `activeDeadlineSeconds` / `ttlSecondsAfterFinished` / `parallelism` / `completions`.
- **CronJob**: 시간 기반 스케줄 Job. `concurrencyPolicy: Allow | Forbid | Replace`, `startingDeadlineSeconds`, `successfulJobsHistoryLimit` / `failedJobsHistoryLimit`.
- **Operator pattern**: CRD + controller. 1000-서비스 스케일에서 DB/Kafka/Redis/monitoring은 사실상 Deployment/StatefulSet을 직접 쓰지 않고 operator가 소유.
## 기본 규칙
### 1. 기본 선택 기준은 "상태 + 수명 + 배치 위치"
3축으로 먼저 분류:
1. **수명**: 장기 실행 / 일회성 / 주기성
2. **상태**: stable identity + persistent storage 필요 / 불필요
3. **배치**: 노드별 한 Pod 필요 / cluster-wide 자유 배치
매핑:
- 장기 + 무상태 + 자유 배치 → **Deployment**
- 장기 + stateful + 자유 배치 → **StatefulSet** (또는 operator)
- 장기 + 무상태 + 노드별 한 Pod → **DaemonSet**
- 일회성 → **Job**
- 주기성 → **CronJob**
### 2. Deployment는 stateless 장기 실행 기본값
조건:
- Pod identity가 교체 가능
- durable state가 외부 DB / 외부 storage / 외부 cache에 있음
- 수평 확장이 자연스러움
- Pod 이름 / 순서가 의미 없음
적용 후보:
- `auth-server`
- `test-server` (장기 실행 모드)
- 외부 DB 사용하는 `keycloak`
- 대부분의 stateless API / worker
**필수 동반 리소스 (replicas≥2인 prod 워크로드):**
- `PodDisruptionBudget` (minAvailable ≥ 50% 또는 SLO tier에 맞춘 값)
- `HorizontalPodAutoscaler` v2 (behavior 포함)
- `topologySpreadConstraints` (zone + hostname)
- `ServiceMonitor` 또는 Prometheus scrape annotation
### 3. StatefulSet은 "stable identity + storage" 모두 맞을 때만
조건 중 하나라도 강하면:
- stable network identity (DNS name per replica) 필요
- stable persistent storage per Pod 필요
- ordered rollout / termination 필요
- replica 간 peer discovery가 ordinal에 의존
적용 후보:
- `postgres` (operator 없을 때 — 있으면 CloudNativePG 같은 operator 우선)
- `minio` (MinIO Operator 가능)
- `vault` (Raft storage mode)
- `etcd` (외부)
- `kafka`, `zookeeper` (Strimzi operator 우선)
- `elasticsearch` (ECK operator 우선)
**필수 선언**:
- `serviceName` (headless Service 참조)
- `volumeClaimTemplates`
- `podManagementPolicy: OrderedReady` 기본. Parallel은 peer discovery가 순서를 요구하지 않을 때만.
- `updateStrategy: RollingUpdate` + `partition`으로 canary rollout
- `persistentVolumeClaimRetentionPolicy` 명시 (prod 기본 `{whenDeleted: Retain, whenScaled: Retain}`)
### 4. DaemonSet은 노드 전역 에이전트 전용
조건:
- 노드별 한 개만 떠야 함 (또는 특정 노드 group에 한 개)
- 노드 추가/제거에 자동 반응
- hostPath / hostNetwork / hostPID 필요한 경우 다수
적용 후보:
- 로그 shipper: `fluent-bit`, `fluentd`, `vector`
- metric exporter: `node-exporter`, `cadvisor`
- CNI agent: `calico-node`, `cilium-agent`
- CSI node plugin: `longhorn-manager`, `ceph-csi-node`
- security agent: `falco`, `tetragon`
- service mesh node proxy: `istio-cni-node`
- ingress-nginx **DaemonSet 모드** (edge 노드가 고정되고 모든 edge 노드가 80/443 HostPort / hostNetwork로 외부 노출해야 할 때)
**Ingress controller: DaemonSet vs Deployment 결정**:
- **Deployment** + `Service type=LoadBalancer` (MetalLB / 외부 LB): 기본 권장. 노드와 ingress replica 수가 분리됨. HPA 적용 가능.
- **DaemonSet** + `hostNetwork: true` / HostPort 80,443: bare-metal + 외부 LB 없이 모든 노드가 ingress가 되어야 할 때. 80/443 노드 포트 점유, HPA 불가, 노드 수 = replica 수.
**필수**:
- `tolerations`로 노드 taint (예: `node-role.kubernetes.io/control-plane`) 대응 결정
- `nodeSelector` 또는 `affinity`로 대상 노드 그룹 명시 (label 기반)
- `updateStrategy: RollingUpdate` + `maxUnavailable` 지정
- `priorityClassName: system-node-critical` (필수 인프라 에이전트)
### 5. PVC가 있다고 무조건 StatefulSet 아니다
체크리스트:
- Pod마다 고유한 storage identity 필요? 아니면 단일 PVC 공유?
- Pod 이름 / 순서가 의미 있나?
- peer discovery가 stable DNS name에 의존?
아니면:
- **단일 replica Deployment + PVC (ReadWriteOnce)** 도 유효
- **Deployment + ReadWriteMany PVC** (여러 replica가 동일 storage 공유) 도 유효 (shared cache 등)
### 6. Job은 migration / bootstrap / one-off 기본값
적용 후보:
- `flyway-migrate` / `liquibase-migrate`
- schema validation
- 초기 admin 사용자 bootstrap
- 데이터 리페어 / 정리
- 이미지 빌드 trigger
**필수**:
- `restartPolicy: Never` (실패 원인 디버깅 가능) 또는 `OnFailure` (transient 실패 재시도)
- `backoffLimit` 명시 (기본값 6은 prod에서 너무 관대할 수 있음)
- `activeDeadlineSeconds` (무한 실행 방지)
- `ttlSecondsAfterFinished` (완료 Job 자동 정리, 1000-서비스 스케일 필수)
- ServiceAccount 최소 권한
### 7. CronJob은 주기 실행 전용
적용 후보:
- etcd / DB 백업
- 정기 정리 (old PVC, old Snapshot, old Job)
- 정기 검증 / 리포트
- 비즈니스 배치 (야간 집계)
**필수**:
- `concurrencyPolicy: Forbid` 기본 (동시 실행 방지). 멱등하면 `Allow`.
- `startingDeadlineSeconds` (노드 장애로 miss 된 job 무한 누적 방지)
- `successfulJobsHistoryLimit: 3` / `failedJobsHistoryLimit: 5`
- schedule timezone 명시 (`spec.timeZone` v1.25+)
금지:
- 항상 떠 있어야 하는 서버를 CronJob으로 배포
- 본 서비스 온라인 처리를 CronJob에 의존
### 8. Stateful workload는 retention / scale-down 정책을 먼저 박는다
StatefulSet의 `persistentVolumeClaimRetentionPolicy`:
- `whenDeleted` (StatefulSet이 삭제될 때 PVC 처리): `Retain` (기본) / `Delete`
- `whenScaled` (replica 축소될 때 PVC 처리): `Retain` (기본) / `Delete`
**prod 기본**: `{whenDeleted: Retain, whenScaled: Retain}` (기본값). DB/Vault/MinIO 모두 여기서 이탈하지 않는다.
**dev/staging**: `{whenDeleted: Delete, whenScaled: Delete}` 허용 (클러스터 재생성 시 자동 정리).
### 9. 외부 DB를 쓰는 앱 서버는 stateless 우선
Pod에 durable state가 없으면 Deployment. Pod identity가 고정되어야 한다는 이유만으로 StatefulSet 선택 금지.
기준:
- `auth-server` → Deployment
- 외부 DB 사용 `keycloak` → Deployment (caching은 external Redis / Infinispan cluster)
- 외부 object store 사용 앱 → Deployment
### 10. Keycloak은 앱 레이어와 저장소를 분리
Keycloak 서버 자체는 stateless로 다룬다.
- **외부 DB (PostgreSQL)** 사용이 prod 기본
- session / cache는 Infinispan embedded 또는 remote 모드 결정 (remote 선호, replica 간 peer discovery는 Kubernetes DNS)
- **Deployment** + externalTrafficPolicy 고려
- HA replica ≥ 2 + PDB
### 11. Vault는 모드별로 다르다
- **dev mode**: 학습 전용. prod 절대 금지.
- **standalone (file storage)**: StatefulSet + PVC. replica=1. 단일 장애점.
- **HA Raft**: StatefulSet (integrated storage). replica 3 또는 5. `podManagementPolicy: Parallel` 허용.
- **HA Consul backend**: StatefulSet (Vault) + StatefulSet (Consul). operator 권장.
- **external Vault**: 클러스터 내부 서버 없음, ExternalSecrets로 참조만.
prod 권장: **HA Raft mode StatefulSet (replica 3)** 또는 **external Vault**.
### 12. MinIO는 Operator 기본 (StatefulSet은 fallback)
1000-서비스 스케일에서 MinIO는 MinIO Operator + `Tenant` CRD가 기본. tenant가 StatefulSet을 내부적으로 생성.
raw StatefulSet은 단일 node/dev 환경에서만 예외 허용.
### 13. Flyway는 Job 기본값
- 앱 startup 내부 migration 금지 (앱 부팅 실패와 migration 실패가 섞임)
- Flyway Job이 선행되고 Success 후에 Deployment rollout
- ArgoCD PostSync hook 또는 Argo Workflows로 순서 제어
- `migrate`, `validate`, `info`, `repair` 각각 독립 Job
### 14. Ingress controller 배치 결정
prod 권장:
- **ingress-nginx Deployment** + `Service type=LoadBalancer` (MetalLB L2 또는 BGP, 또는 외부 LB)
- 또는 **Envoy Gateway / Gateway API 기반 Deployment**
- HPA 가능, PDB 필수 (tier-1 로 취급)
- 복수 IngressClass (`nginx-public`, `nginx-internal`) 분리
DaemonSet 선택 조건:
- edge 노드가 고정되어 있고 hostNetwork 80/443이 필요
- 외부 LB가 없고 DNS round-robin으로 다수 노드 IP 노출
### 15. DB는 operator 기본, StatefulSet은 fallback
1000-서비스 스케일의 PostgreSQL:
- **CloudNativePG Operator** 기본 → `Cluster` CRD. operator가 StatefulSet/Service/Secret/ConfigMap/Backup 전부 관리.
- **Zalando Postgres Operator**도 대안
- raw StatefulSet은 dev / 특수 케이스에만
MySQL/MariaDB:
- **MariaDB Operator** / **mysql-operator**
Redis:
- **Spotahome redis-operator** / **Redis Enterprise Operator**
- cluster 모드면 StatefulSet, sentinel 모드면 Deployment(sentinel) + StatefulSet(redis)
### 16. Batch / 대량 병렬은 Job + `parallelism` + IndexedJob
단일 Job으로 수천 개 task 병렬 실행:
- `completionMode: Indexed` + `parallelism: N`
- 각 Pod가 `JOB_COMPLETION_INDEX` env로 자기 작업 식별
- 더 복잡한 DAG는 Argo Workflows / Tekton
### 17. workload 종류만 맞는다고 품질이 보장되지 않는다
최종 결정 전 동반 표준 확인:
- `storage-pvc.md` (StorageClass / volumeClaimTemplate / snapshot)
- `network-ingress-tls.md` (ingressClassName / TLS)
- `resources-probes-availability.md` (PDB / HPA / probe / resources)
- `backup-restore.md` (RPO / RTO / 절차)
- `security-podsecurity.md` (PSA / seccomp / capabilities)
- `observability.md` (ServiceMonitor / log shipping)
### 18. priority class / preemption 전략
- 플랫폼 에이전트 (CNI, CSI, log shipper, node-exporter): `system-node-critical`
- 클러스터 컨트롤러 (cert-manager, external-secrets, operator): `system-cluster-critical`
- 비즈니스 tier-1: 커스텀 `tier-1-critical` (value 1000000)
- 비즈니스 tier-2: `tier-2` (value 100000)
- 비즈니스 tier-3 / batch: `tier-3` (value 10000)
## 현재 스택 기본 권장안 (prod)
| 컴포넌트 | workload 종류 | operator | 비고 |
|----------------------|--------------------------------|-------------------|--------------------------------|
| `auth-server` | Deployment | — | tier-1 HPA+PDB |
| `test-server` (장기) | Deployment | — | |
| `test-server` (검증) | Job | — | ttlSecondsAfterFinished |
| `keycloak` | Deployment (외부 DB) | — | HA replicas≥2 |
| `postgres-identity` | StatefulSet (via CNPG) | CloudNativePG | replica 3 |
| `vault` | StatefulSet (Raft) | Vault Operator | replica 3 |
| `minio` | StatefulSet (via Tenant) | MinIO Operator | |
| `kafka` | StatefulSet (via Strimzi) | Strimzi | |
| `redis` | StatefulSet (via operator) | redis-operator | sentinel 또는 cluster 모드 |
| `flyway-migrate` | Job | — | ArgoCD PostSync hook |
| `postgres-backup` | CronJob | CNPG ScheduledBkp | operator가 소유 |
| `ingress-nginx` | Deployment + MetalLB | — | public / internal 분리 |
| `cert-manager` | Deployment | — | system-cluster-critical |
| `external-secrets` | Deployment | — | system-cluster-critical |
| `prometheus` | StatefulSet (via Prometheus) | Prometheus Op | |
| `fluent-bit` | DaemonSet | — | system-node-critical |
| `node-exporter` | DaemonSet | — | system-node-critical |
| `cilium-agent` | DaemonSet | Cilium Op (opt) | system-node-critical |
| `longhorn-manager` | DaemonSet | Longhorn | |
## 프로젝트 기준 요약
- stateless 장기 → Deployment (+PDB+HPA+topologySpread 필수)
- stateful + stable identity/storage → StatefulSet (또는 operator)
- 노드 전역 에이전트 → DaemonSet
- 일회성 → Job (ttlSecondsAfterFinished 필수)
- 주기성 → CronJob (concurrencyPolicy + startingDeadlineSeconds)
- PVC ≠ StatefulSet 신호 전부 아님
- DB/Kafka/Redis/Prometheus는 operator 기본
- `persistentVolumeClaimRetentionPolicy` 명시, prod는 Retain/Retain
- Flyway는 Job, 앱 startup 내부 migration 금지
- ingress controller는 Deployment+LB 기본, DaemonSet은 edge 조건에만