# 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` | `lab` \| `dev` \| `staging` \| `prod` | | `example.com/owner-team` | 팀 slug (e.g., `auth-platform`, `sre`) | | `example.com/cost-center` | 회계 코스트 센터 ID | | `example.com/data-classification` | `public` \| `internal` \| `confidential` \| `restricted` | | `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. 기본 스키마: `--` — 예: `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 | `` | `auth-server` | | Service (ClusterIP) | `` (Deployment와 동일) | `auth-server` | | Headless Service (StatefulSet peer 통신용) | `-headless` (옆에 일반 ClusterIP `` 병행) | `keycloak-headless`, `keycloak` | | ServiceAccount | `-sa` | `auth-server-sa` | | Secret (앱 소유) | `-` | `auth-server-db`, `auth-server-oidc` | | ConfigMap | `-` | `auth-server-config`, `auth-server-runtime` | | PDB | `-pdb` | `auth-server-pdb` | | HPA | `-hpa` | `auth-server-hpa` | | NetworkPolicy | `--` | `auth-server-egress-db`, `auth-server-ingress-traefik` | | Job (일회성) | `--` | `auth-server-migrate-1-24-0` | | CronJob | `-` | `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 환경**: `/@sha256:` 형태 digest pin **필수**. 뮤터블 태그(`:1`, `:latest`, `:main`) 금지. 2. **staging**: digest 권장, 최소 semver tag(`:1.24.0`) 허용. 절대 `:latest` 금지. 3. **lab / dev**: semver tag 허용, `:latest` 지양 (로컬 / 노드 cache invalidation 이슈). `lab` 은 폐기 가능한 단일 클러스터 실험 환경에만 사용한다. 4. `imagePullPolicy`: - digest 사용 시 `IfNotPresent` (이미지 콘텐츠는 immutable) - 뮤터블 태그 사용 시 `Always` 5. 레지스트리: 조직 내부 미러가 우선한다. 예: `registry.example.com//`. 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 | 128–256Mi | | 무거운 JVM (Keycloak, Elasticsearch) | 500m–1 | 1–2Gi (req == limit) | | 배경 worker | 250m | 512Mi–1Gi | | 전환성(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 에서 기계 검증된다. - 실행: `scripts/ci/validate-docs.sh` - Lint 설정 위치: `.kube-linter.yaml` (repo root) - 목표 스코어: - syntax 에러: **0** - schema 에러: **0** - lint warning: **≤ 5** syntax 또는 schema 에러가 있으면 PR 은 머지 불가. lint warning 이 임계치를 넘으면 리뷰어가 수정 또는 예외 주석을 요구한다.