Kubernetes 심화 — HPA, Resource 관리, Pod Scheduling
즉, Kubernetes 운영의 본질은 YAML을 많이 아는 데 있지 않습니다. 더 중요한 건 클러스터 자원을 어떻게 배분하고, 장애 시 어떤 서비스부터 보호할지 결정하는 것입니다.
이 글은 HPA, requests/limits, scheduling, disruption budget 같은 요소를 각각 소개하는 데서 멈추지 않고, 이들이 실제로 어떻게 함께 작동하는지 실무 관점에서 정리합니다.
아키텍처 그림 설명
[Traffic Increase]
|
v
[Service / Ingress]
|
v
[Pods]
| |
v v
Probe Resource request/limit
| |
+-----> [Scheduler]
|
v
[Nodes]
|
v
[HPA / PDB]
Kubernetes 운영에서 중요한 것은 개별 YAML 옵션보다 이 요소들이 함께 작동하는 방식입니다. Probe가 비정상 Pod를 걸러내고, requests와 limits가 스케줄링과 QoS를 결정하며, HPA와 PDB가 확장과 안정성 사이의 균형을 잡습니다. 그래서 실무에서는 값 하나보다 전체 제어 루프를 보는 시각이 필요합니다.
requests와 limits는 단순 숫자가 아니라 스케줄링 계약이다
resources:
requests:
cpu: "250m"
memory: "256Mi"
limits:
cpu: "500m"
memory: "512Mi"
많은 팀이 requests와 limits를 그냥 적당한 숫자로 넣지만, 실제 의미는 꽤 큽니다.
requests: 스케줄러가 이 Pod를 어느 노드에 놓을지 판단하는 최소 보장치limits: 컨테이너가 사용할 수 있는 최대 상한선
이 차이를 이해하지 못하면 두 가지 문제가 자주 생깁니다.
- requests를 너무 낮게 잡아 노드에는 들어가지만 실제 부하에서 금방 밀린다
- limits를 너무 낮게 잡아 CPU throttling, OOMKill이 잦아진다
즉, requests/limits는 성능 옵션이 아니라 클러스터와 애플리케이션 사이의 자원 계약에 가깝습니다.
QoS 클래스는 리소스 부족 시 우선순위를 결정한다
Guaranteed: requests == limitsBurstable: requests < limitsBestEffort: 설정 없음
이 분류는 단순 정보가 아닙니다. 노드 자원이 부족할 때 어떤 Pod가 먼저 축출(eviction)될지에 영향을 줍니다. 프로덕션에서 중요한 서비스는 최소한 Burstable 이상, 가능하면 핵심 워크로드는 Guaranteed에 가깝게 관리하는 편이 안정적입니다.
메모리 설정은 애플리케이션 런타임 특성과 함께 봐야 한다
apiVersion: apps/v1
kind: Deployment
metadata:
name: order-service
spec:
template:
spec:
containers:
- name: app
image: order-service:1.2.0
env:
- name: JAVA_OPTS
value: "-Xms512m -Xmx512m -XX:+UseContainerSupport"
resources:
requests:
cpu: "500m"
memory: "768Mi"
limits:
cpu: "1000m"
memory: "768Mi"
Java처럼 런타임 오버헤드가 있는 애플리케이션은 heap만 보고 메모리를 잡으면 금방 OOM이 납니다. 메타스페이스, 스레드 스택, 네이티브 메모리까지 고려해야 합니다. 즉, Kubernetes 리소스 설정은 앱 런타임을 모른 채로 할 수 있는 일이 아닙니다.
HPA는 마법이 아니라 지연된 제어 시스템이다
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: order-service-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: order-service
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
behavior:
scaleUp:
stabilizationWindowSeconds: 60
policies:
- type: Pods
value: 2
periodSeconds: 60
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Percent
value: 10
periodSeconds: 60
HPA는 부하가 오르면 바로 마법처럼 따라오는 기능이 아닙니다. 실제로는 다음 특징을 가집니다.
- 메트릭 수집에도 지연이 있습니다
- 새 Pod가 뜨는 데도 시간이 걸립니다
- scale up/down이 너무 민감하면 진동(flapping)이 생깁니다
그래서 HPA는 “순간 부하를 막아주는 장치”보다 지속되는 부하를 흡수하는 지연된 제어 루프로 보는 편이 정확합니다.
CPU만 보면 안 되는 이유
많은 팀이 HPA를 CPU 70% 기준으로만 둡니다. 하지만 서비스 성격에 따라 실제 병목은 전혀 다를 수 있습니다.
- API 서버: CPU보다 요청 대기 시간이나 RPS가 더 중요할 수 있음
- 워커: 큐 backlog가 더 직접적인 스케일 지표일 수 있음
- 캐시성 서비스: 메모리가 병목일 수 있음
즉, HPA는 쿠버네티스 기능이지만 사실상 서비스별 workload 모델링 문제입니다.
custom metric이 필요한 순간이 온다
- type: External
external:
metric:
name: kafka_consumer_lag
selector:
matchLabels:
topic: order-events
target:
type: AverageValue
averageValue: "100"
예를 들어 Kafka consumer는 CPU가 낮아도 backlog가 쌓일 수 있습니다. 이런 워크로드에 CPU 기반 HPA만 쓰면 스케일링이 늦거나 엇나갑니다. 그래서 메시지 지연, 큐 길이, 요청 대기 수 같은 도메인 메트릭을 스케일 기준으로 쓰는 편이 더 현실적일 수 있습니다.
Scheduling은 “아무 데나 올린다”가 아니라 실패를 분산하는 기술이다
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: order-service
topologyKey: kubernetes.io/hostname
이 설정의 핵심은 단순 배치가 아닙니다. 같은 서비스 Pod가 한 노드에 몰려 있으면, 그 노드 하나가 죽는 순간 서비스 전체 가용성이 크게 흔들립니다.
즉, anti-affinity는 성능 최적화보다도 장애 도메인 분산에 더 가깝습니다.
Node affinity와 taint는 클러스터 자원 계층화를 만든다
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: accelerator
operator: In
values: ["nvidia-tesla-t4"]
kubectl taint nodes high-mem-node1 dedicated=memory-intensive:NoSchedule
tolerations:
- key: "dedicated"
operator: "Equal"
value: "memory-intensive"
effect: "NoSchedule"
이 기능들은 결국 클러스터 안에 우선순위를 만드는 도구입니다.
- 어떤 워크로드는 비싼 노드에만 올린다
- 어떤 노드는 특정 팀/서비스 전용으로 둔다
- GPU/고메모리 노드를 아무 Pod나 쓰지 못하게 막는다
규모가 커질수록 이런 자원 계층화가 중요해집니다.
PDB는 무중단 배포보다 “동시 중단 상한선”에 가깝다
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: order-service-pdb
spec:
selector:
matchLabels:
app: order-service
minAvailable: 2
PDB는 많은 팀이 “무중단 보장”으로 오해하지만, 실제로는 voluntary disruption 시 동시 중단 허용 범위를 제한하는 장치입니다. 즉, 노드 드레인이나 롤링 업데이트에서 너무 많은 Pod가 한꺼번에 내려가지 않게 막아줍니다.
이것도 결국 숫자보다 서비스 특성을 봐야 합니다.
- 최소 몇 개가 살아 있어야 정상 서비스인가
- readiness가 느린 서비스인가
- scale 수가 작을 때
minAvailable이 오히려 배포를 막지 않는가
Probe는 헬스체크가 아니라 트래픽과 재시작 정책이다
containers:
- name: app
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
failureThreshold: 3
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
failureThreshold: 3
startupProbe:
httpGet:
path: /actuator/health
port: 8080
failureThreshold: 30
periodSeconds: 10
세 probe는 역할이 다릅니다.
startupProbe: 초기 기동이 오래 걸릴 때 premature restart 방지readinessProbe: 트래픽을 받을 준비가 되었는지 판단livenessProbe: 살아는 있지만 망가진 프로세스를 재시작할지 판단
특히 readiness와 liveness를 같은 엔드포인트로 두는 실수는 자주 보입니다. 준비 안 된 상태와 회복 불가능한 상태는 구분하는 편이 좋습니다.
최적화보다 먼저 관측이 있어야 한다
kubectl top pods -n production
kubectl top nodes
리소스 튜닝은 감으로 하면 실패하기 쉽습니다. 최소한 아래는 봐야 합니다.
- 실제 CPU/메모리 사용량 분포
- throttling 발생 여부
- OOMKill 빈도
- HPA scale 이벤트 빈도
- pending pod 발생 여부
- 노드별 배치 편중
즉, 쿠버네티스 운영은 설정을 예쁘게 쓰는 것보다 관측 결과를 바탕으로 조정하는 피드백 루프가 더 중요합니다.
실무에서 자주 하는 실수
- requests를 너무 낮게 잡아 noisy neighbor 문제를 만든다
- limits를 너무 타이트하게 잡아 CPU throttling, OOMKill을 유발한다
- HPA를 CPU만으로 구성해 실제 병목을 놓친다
- anti-affinity 없이 동일 Pod가 한 노드에 몰린다
- readiness/liveness를 구분하지 않는다
특히 가장 흔한 문제는 “잘 돌아갈 때 기준”으로만 리소스를 잡고, 피크 트래픽이나 장애 전환 상황을 고려하지 않는 것입니다.
마무리
Kubernetes 심화 운영의 핵심은 HPA, affinity, probe 같은 기능을 각각 아는 데 있지 않습니다. 더 본질적으로는 클러스터 자원을 어떤 원칙으로 배분하고, 장애 시 무엇을 보호할지 결정하는 것이 중요합니다.
리소스 설정, 스케일링, 배치 정책, disruption 제어는 결국 하나의 문제를 다른 각도에서 보는 도구들입니다. 그 관점이 잡히면 쿠버네티스는 단순 오케스트레이터가 아니라 운영 의도를 시스템에 반영하는 플랫폼이 됩니다.
운영 환경에서 어려워지는 지점
- 고급 쿠버네티스 운영은 멀티테넌시, 보안 경계, 네트워크 정책, 운영 도구 같은 플랫폼 tradeoff를 다루는 일에 가깝다.
- 많은 팀과 다양한 배포 패턴이 한 클러스터에 모이면 복잡도는 예상보다 빠르게 올라간다.
- 가장 큰 실수는 플랫폼 소유권 모델 없이 고급 기능만 계속 추가하는 것이다.
중요한 아키텍처 결정
- 고급 controller와 policy를 도입하기 전에 플랫폼 팀과 애플리케이션 팀 책임을 분명히 한다.
- ingress, secret, observability, 정책 강제를 표준화해 엔트로피를 줄인다.
- namespace, quota, admission rule, workload identity를 테넌시 도구로 의도적으로 사용한다.
실무 예시
성숙한 플랫폼은 단순 접근 권한이 아니라 가드레일을 정의한다.
team namespace
resource quota
network policy
workload identity
표준 ingress와 logging
피해야 할 안티패턴
- 운영 능력보다 빠르게 operator와 CRD를 설치하는 것.
- 공유 클러스터를 공짜 멀티테넌시처럼 다루는 것.
- 플랫폼 표준을 문서화하지 않고 구전 지식에 의존하는 것.
운영 체크리스트
- 클러스터 add-on과 controller 소유권을 감사한다.
- admission 정책 위반과 drift를 검토한다.
- noisy neighbor 사고와 quota 압력을 측정한다.
- etcd, ingress, secret 의존성의 재해 복구를 테스트한다.
최종 판단
고급 쿠버네티스는 본질적으로 플랫폼 엔지니어링이다. 기능 수보다 명확한 가드레일, 소유권, 운영 절제가 성공을 좌우한다.
Continue Reading
다음으로 읽기 좋은 글
Kubernetes 클러스터 유형 완전 가이드
단일 노드부터 고가용성, 관리형, 멀티 클러스터까지 Kubernetes 클러스터 유형별 구조, 선택 기준, 구성 방법, 운영 트레이드오프를 실무 관점으로 정리합니다.
🚀 DevOpsKubernetes 기초 설계 가이드
Kubernetes의 Pod, Deployment, Service를 단순 오브젝트 소개가 아니라 운영 모델 관점에서 이해합니다. 선언형 배포, 네트워크 추상화, 설정 분리, 실제 도입 시 주의점까지 다룹니다.
📈 최신 동향2026 쿠버네티스 플랫폼 트렌드: v1.35 이후 운영팀이 보는 포인트
2026년 4월 21일 기준 Kubernetes는 1.35, 1.34, 1.33을 유지보수합니다. 지금 중요한 것은 기능 개수보다, 운영팀이 어떤 비용 구조를 줄이고 있는가입니다.
📚 IT 이야기컨테이너와 쿠버네티스는 어떻게 배포의 감각을 바꿨나
한때 배포는 늘 불안한 행사처럼 느껴졌지만, 컨테이너와 쿠버네티스는 그것을 더 반복 가능하고 더 자동화된 일상으로 바꾸려 했습니다.
다음 탐색