Files
project-infra/docs/standards/infra/STYLE.md
T

276 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 이 임계치를 넘으면 리뷰어가 수정 또는 예외 주석을 요구한다.