# 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.