17 KiB
17 KiB
resources / probes / availability 예시
아래 예시는 1000+ 서비스를 운영하는 기준선이다. 모든 YAML은 그대로 kubectl apply -f 가능한 형태이며, 라벨 / probe / PDB / HPA / topologySpread / ServiceMonitor / NetworkPolicy가 한 세트로 맞물린다.
좋은 예시 1: auth-server 완전 매니페스트 세트 (Burstable + HPA)
Deployment
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: auth-server
namespace: auth-prod
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: identity-platform
app.kubernetes.io/managed-by: argocd
spec:
replicas: 3
revisionHistoryLimit: 10
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 25%
maxUnavailable: 0
selector:
matchLabels:
app.kubernetes.io/name: auth-server
app.kubernetes.io/instance: auth-server-prod
template:
metadata:
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: identity-platform
spec:
serviceAccountName: auth-server
terminationGracePeriodSeconds: 45
securityContext:
runAsNonRoot: true
runAsUser: 10001
fsGroup: 10001
seccompProfile:
type: RuntimeDefault
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app.kubernetes.io/name: auth-server
app.kubernetes.io/instance: auth-server-prod
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app.kubernetes.io/name: auth-server
app.kubernetes.io/instance: auth-server-prod
containers:
- name: auth-server
image: registry.example.com/identity/auth-server:1.24.0
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 8080
- name: metrics
containerPort: 8081
env:
- name: JAVA_OPTS
value: "-XX:MaxRAMPercentage=75.0 -XX:+UseG1GC"
resources:
requests:
cpu: "500m"
memory: "1Gi"
limits:
memory: "1536Mi"
startupProbe:
httpGet:
path: /actuator/health/started
port: http
periodSeconds: 5
failureThreshold: 24
timeoutSeconds: 3
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: http
periodSeconds: 5
failureThreshold: 3
timeoutSeconds: 2
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: http
periodSeconds: 15
failureThreshold: 3
timeoutSeconds: 3
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 15"]
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
왜 좋은가:
- QoS는 의도적으로 Burstable (CPU limit 생략으로 throttling 회피, memory는 1.5× headroom).
- startup probe가 최대 120초 (24×5) cold start를 덮으며 그 전까지 readiness/liveness는 실행되지 않는다.
- topologySpreadConstraints로 zone 장애 격리 + host 분산.
- app.kubernetes.io/* 표준 라벨 full set.
- preStop sleep 15초로 endpoint 제거 전파 시간을 확보한다.
- rolling update
maxUnavailable: 0으로 항상 N replica 이상 유지.
Service + PDB
---
apiVersion: v1
kind: Service
metadata:
name: auth-server
namespace: auth-prod
labels:
app.kubernetes.io/name: auth-server
app.kubernetes.io/instance: auth-server-prod
spec:
type: ClusterIP
selector:
app.kubernetes.io/name: auth-server
app.kubernetes.io/instance: auth-server-prod
ports:
- name: http
port: 80
targetPort: http
- name: metrics
port: 8081
targetPort: metrics
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: auth-server
namespace: auth-prod
labels:
app.kubernetes.io/name: auth-server
app.kubernetes.io/instance: auth-server-prod
spec:
maxUnavailable: 1
unhealthyPodEvictionPolicy: AlwaysAllow
selector:
matchLabels:
app.kubernetes.io/name: auth-server
app.kubernetes.io/instance: auth-server-prod
HorizontalPodAutoscaler v2 with behavior block
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: auth-server
namespace: auth-prod
labels:
app.kubernetes.io/name: auth-server
app.kubernetes.io/instance: auth-server-prod
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: auth-server
minReplicas: 3
maxReplicas: 20
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Pods
pods:
metric:
name: http_requests_in_flight
target:
type: AverageValue
averageValue: "50"
behavior:
scaleUp:
stabilizationWindowSeconds: 0
policies:
- type: Percent
value: 100
periodSeconds: 30
- type: Pods
value: 4
periodSeconds: 30
selectPolicy: Max
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Percent
value: 25
periodSeconds: 60
selectPolicy: Max
왜 좋은가:
- Resource metric과 custom Pods metric을 동시에 평가.
- scaleUp stabilization 0s → 스파이크에 즉시 반응.
- scaleDown 300s stabilization + 25%/min rate → flapping 방지.
- Pods metric은 pod당 in-flight request 수 (label cardinality 안전).
ServiceMonitor (kube-prometheus-stack)
---
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: auth-server
namespace: auth-prod
labels:
app.kubernetes.io/name: auth-server
app.kubernetes.io/instance: auth-server-prod
release: kube-prometheus-stack
spec:
namespaceSelector:
matchNames:
- auth-prod
selector:
matchLabels:
app.kubernetes.io/name: auth-server
app.kubernetes.io/instance: auth-server-prod
endpoints:
- port: metrics
path: /actuator/prometheus
scheme: http
interval: 30s
scrapeTimeout: 10s
honorLabels: false
relabelings:
- sourceLabels: [__meta_kubernetes_pod_name]
targetLabel: pod
- sourceLabels: [__meta_kubernetes_namespace]
targetLabel: namespace
- action: labeldrop
regex: "pod_template_hash"
NetworkPolicy (scrape만 prometheus namespace에서 허용)
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: auth-server-metrics-from-prom
namespace: auth-prod
labels:
app.kubernetes.io/name: auth-server
app.kubernetes.io/instance: auth-server-prod
spec:
podSelector:
matchLabels:
app.kubernetes.io/name: auth-server
app.kubernetes.io/instance: auth-server-prod
policyTypes: ["Ingress"]
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: monitoring
podSelector:
matchLabels:
app.kubernetes.io/name: prometheus
ports:
- port: metrics
protocol: TCP
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: ingress-nginx
ports:
- port: http
protocol: TCP
좋은 예시 2: Keycloak — Guaranteed QoS + slow startup
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: keycloak
namespace: identity
labels:
app.kubernetes.io/name: keycloak
app.kubernetes.io/instance: keycloak-prod
app.kubernetes.io/version: "24.0.4"
app.kubernetes.io/component: identity-provider
app.kubernetes.io/part-of: identity-platform
spec:
serviceName: keycloak-headless
replicas: 3
selector:
matchLabels:
app.kubernetes.io/name: keycloak
app.kubernetes.io/instance: keycloak-prod
template:
metadata:
labels:
app.kubernetes.io/name: keycloak
app.kubernetes.io/instance: keycloak-prod
app.kubernetes.io/version: "24.0.4"
spec:
terminationGracePeriodSeconds: 60
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
seccompProfile:
type: RuntimeDefault
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app.kubernetes.io/name: keycloak
app.kubernetes.io/instance: keycloak-prod
containers:
- name: keycloak
image: quay.io/keycloak/keycloak:24.0.4
args: ["start"]
ports:
- name: http
containerPort: 8080
- name: mgmt
containerPort: 9000
resources:
requests:
cpu: "1"
memory: "2Gi"
limits:
cpu: "1"
memory: "2Gi"
startupProbe:
httpGet:
path: /health/started
port: mgmt
periodSeconds: 10
failureThreshold: 30
timeoutSeconds: 5
readinessProbe:
httpGet:
path: /health/ready
port: mgmt
periodSeconds: 10
failureThreshold: 3
livenessProbe:
httpGet:
path: /health/live
port: mgmt
periodSeconds: 30
failureThreshold: 3
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
volumeMounts:
- { name: tmp, mountPath: /tmp }
- { name: data, mountPath: /opt/keycloak/data }
volumes:
- name: tmp
emptyDir: {}
- name: data
emptyDir: {}
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: keycloak
namespace: identity
spec:
maxUnavailable: 1
unhealthyPodEvictionPolicy: AlwaysAllow
selector:
matchLabels:
app.kubernetes.io/name: keycloak
app.kubernetes.io/instance: keycloak-prod
왜 좋은가:
- Guaranteed QoS (request == limit 모든 리소스) → eviction 우선순위 최고.
- startup budget = 10s × 30 = 300s, Keycloak cold boot p99 덮음.
- management port 9000에만 health, HTTP 8080은 traffic 전용.
- PDB maxUnavailable: 1로 3-node infinispan cluster 중 최소 2개 생존 보장.
좋은 예시 3: 1.29+ native sidecar (log forwarder)
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: report-worker
namespace: reporting
labels:
app.kubernetes.io/name: report-worker
app.kubernetes.io/instance: report-worker-prod
spec:
replicas: 2
selector:
matchLabels:
app.kubernetes.io/name: report-worker
app.kubernetes.io/instance: report-worker-prod
template:
metadata:
labels:
app.kubernetes.io/name: report-worker
app.kubernetes.io/instance: report-worker-prod
spec:
securityContext:
runAsNonRoot: true
runAsUser: 10001
runAsGroup: 10001
fsGroup: 10001
seccompProfile:
type: RuntimeDefault
initContainers:
- name: schema-check
image: registry.example.com/tools/schema-check:1.2.0
command: ["/bin/schema-check", "--fail-fast"]
resources:
requests: { cpu: 50m, memory: 64Mi }
limits: { memory: 128Mi }
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
runAsNonRoot: true
capabilities:
drop: ["ALL"]
- name: log-forwarder
image: grafana/alloy:v1.2.0
restartPolicy: Always # <- native sidecar (1.29+)
args: ["run", "/etc/alloy/config.alloy"]
resources:
requests: { cpu: 50m, memory: 128Mi }
limits: { memory: 256Mi }
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
runAsNonRoot: true
capabilities:
drop: ["ALL"]
volumeMounts:
- name: alloy-config
mountPath: /etc/alloy
- name: shared-logs
mountPath: /var/log/app
containers:
- name: worker
image: registry.example.com/reporting/worker:2.3.1
resources:
requests:
cpu: "200m"
memory: "512Mi"
limits:
memory: "768Mi"
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
runAsNonRoot: true
capabilities:
drop: ["ALL"]
volumeMounts:
- name: shared-logs
mountPath: /var/log/app
- name: tmp
mountPath: /tmp
volumes:
- name: alloy-config
configMap:
name: alloy-config
- name: shared-logs
emptyDir: {}
- name: tmp
emptyDir: {}
왜 좋은가:
restartPolicy: Alwayson init container = native sidecar 패턴 (1.29+).- init container 순서: schema-check 완료 → log-forwarder sidecar 시작 → main container.
- sidecar는 main 종료 후 SIGTERM 받음 (log flush 가능).
나쁜 예시 1: CPU limit 기계적 설정 (throttling 유발)
resources:
requests:
cpu: "500m"
memory: "1Gi"
limits:
cpu: "500m" # BAD
memory: "1Gi"
문제:
- Linux CFS quota가 100ms period 내 burst만으로도 throttle을 발생시킨다.
- p99 latency가 간헐적으로 튀어도 원인이 숨는다 (metric은 평균 usage 기준).
- Google SRE / Tim Hockin 공식 가이드: "대부분의 워크로드에서 CPU limit를 제거하라".
Fix: CPU는 request만, memory만 limit로.
나쁜 예시 2: liveness로 readiness 대신함
livenessProbe:
httpGet:
path: /actuator/health # BAD - deep check
port: 8080
periodSeconds: 5
failureThreshold: 2
# readiness 없음
문제:
- deep
/actuator/health는 DB/외부 의존성 포함. DB blip → 모든 Pod 재시작 → cascading failure. - 트래픽 수용 준비 상태를 표현할 수단이 없다.
Fix: startup / readiness / liveness 세 축 분리. liveness는 /health/live 같은 shallow check.
나쁜 예시 3: podAntiAffinity로 spread 시도 (legacy)
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values: ["auth-server"]
topologyKey: kubernetes.io/hostname
문제:
- replica 수가 노드 수보다 많으면 스케줄 불가.
- zone 분산이 회계되지 않는다 (skew 개념 없음).
- maxSkew 튜닝 불가.
Fix: topologySpreadConstraints 사용 (좋은 예시 1 참조).
나쁜 예시 4: replica 1 서비스에 PDB
apiVersion: policy/v1
kind: PodDisruptionBudget
spec:
minAvailable: 1 # BAD - with replica=1
selector:
matchLabels:
app: singleton-app
문제:
- node drain이 영구 블록된다 (
PDB violation). - Kubernetes 업그레이드가 불가능해진다.
Fix: replica 1은 PDB 제거. 필요 시 replica 2+로 늘리고 PDB 적용.
나쁜 예시 5: HPA v1 스타일 (behavior 없음)
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: auth-server
minReplicas: 1
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 80
# behavior block 없음
문제:
- 기본 scale-down stabilization 300s지만 scale-up도 쓸데없이 보수적.
- 트래픽 burst에 대응 지연.
- 트래픽 drop 뒤 flapping 발생 가능 (policy 정의 없음).
Fix: behavior block 필수 (좋은 예시 1 참조).
나쁜 예시 6: limit만 있고 request 없음
resources:
limits:
cpu: "1"
memory: "1Gi"
문제:
- Kubernetes가 request = limit로 복사 → 암묵적 Guaranteed.
- 스케줄러 회계가 과대 평가되어 cluster density 저하.
- 의도한 QoS class와 다름.
Fix: requests 명시 필수.