276 lines
12 KiB
Markdown
276 lines
12 KiB
Markdown
# 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 | 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 에서 기계 검증된다.
|
||
|
||
- 실행: `k8s/scripts/ci/validate-docs.sh`
|
||
- Lint 설정 위치: `.kube-linter.yaml` (repo root)
|
||
- 목표 스코어:
|
||
- syntax 에러: **0**
|
||
- schema 에러: **0**
|
||
- lint warning: **≤ 5**
|
||
|
||
syntax 또는 schema 에러가 있으면 PR 은 머지 불가. lint warning 이 임계치를 넘으면 리뷰어가 수정 또는 예외 주석을 요구한다.
|