리소스 관리 — requests와 limits가 실제로 하는 일
Pod에 리소스 설정을 안 하면 편하긴 한데, 하나의 Pod이 노드의 CPU와 메모리를 다 써버리면 어떻게 될까요?
리소스 설정은 Kubernetes에서 가장 중요하면서도 가장 많이 놓치는 부분입니다. requests와 limits를 올바르게 설정하지 않으면 스케줄링 실패, OOMKill, CPU 쓰로틀링 같은 문제가 발생합니다. 이 두 값이 실제로 어떻게 동작하는지 정확하게 이해해야 합니다.
requests vs limits
spec:
containers:
- name: app
image: my-app:1.0
resources:
requests: # 최소 보장 리소스
cpu: 200m # 0.2 CPU 코어
memory: 256Mi # 256 MiB
limits: # 최대 사용 제한
cpu: 500m # 0.5 CPU 코어
memory: 512Mi # 512 MiB
| 항목 | requests | limits |
|---|---|---|
| 용도 | 스케줄링 기준 | 런타임 상한 |
| CPU 초과 시 | - | 쓰로틀링 (성능 저하) |
| 메모리 초과 시 | - | OOMKill (컨테이너 종료) |
| 미설정 시 | 스케줄러가 0으로 간주 | 무제한 사용 가능 |
CPU 단위
| 표기 | 의미 |
|---|---|
1 | 1 CPU 코어 |
500m | 0.5 코어 (밀리코어) |
100m | 0.1 코어 |
메모리 단위
| 표기 | 의미 |
|---|---|
128Mi | 128 MiB (메비바이트) |
1Gi | 1 GiB |
128M | 128 MB (메가바이트, Mi보다 약간 작음) |
CPU — 압축 가능한 리소스
CPU는 시분할로 공유할 수 있는 "압축 가능한" 리소스입니다.
CPU requests의 역할
- **스케줄링 **: 스케줄러는 노드의 남은 CPU가 requests 이상인 노드를 선택합니다
- **CPU 가중치 **: Linux cgroups에서 CPU shares로 변환됩니다 (requests가 클수록 더 많은 CPU 시간)
CPU limits와 쓰로틀링
limits를 초과하면 컨테이너가 종료되는 것이 아니라 ** 쓰로틀링 **됩니다. 100ms 주기 중 limits에 해당하는 시간만큼만 CPU를 사용할 수 있습니다.
limits: 200m → 100ms 중 20ms만 CPU 사용 가능
limits: 500m → 100ms 중 50ms만 CPU 사용 가능
쓰로틀링은 응답 지연을 유발합니다. P99 레이턴시가 갑자기 튀는 경우 CPU 쓰로틀링이 원인일 수 있습니다.
# 쓰로틀링 확인
kubectl exec my-pod -- cat /sys/fs/cgroup/cpu/cpu.stat
# nr_throttled 1234 # 쓰로틀링된 횟수
# throttled_time 567890 # 쓰로틀링된 총 시간 (ns)
공부하다 보니 CPU limits를 너무 낮게 설정해서 불필요한 쓰로틀링이 발생하는 경우가 꽤 많더라고요. 일부에서는 CPU limits를 아예 설정하지 않는 것을 권장하기도 합니다.
메모리 — 압축 불가능한 리소스
메모리는 반환하지 않으면 회수할 수 없는 "비압축" 리소스입니다.
OOMKill
메모리 사용량이 limits를 초과하면 Linux OOM Killer가 컨테이너를 강제 종료합니다.
# OOMKill 확인
kubectl describe pod my-pod
# Last State: Terminated
# Reason: OOMKilled
# Exit Code: 137
OOMKill이 발생하면 Pod의 restartPolicy에 따라 재시작되며, 반복되면 CrashLoopBackOff 상태가 됩니다.
메모리 requests의 역할
- ** 스케줄링 **: 노드의 남은 메모리가 requests 이상이어야 스케줄
- **eviction 우선순위 **: 노드 메모리 부족 시 requests 대비 사용량이 높은 Pod이 먼저 퇴출
QoS (Quality of Service) 클래스
Kubernetes는 리소스 설정에 따라 Pod에 QoS 클래스를 자동 할당합니다. 노드 리소스가 부족하면 QoS가 낮은 Pod부터 퇴출합니다.
Guaranteed — 최우선
# 모든 컨테이너에 requests = limits (CPU, 메모리 모두)
resources:
requests:
cpu: 500m
memory: 512Mi
limits:
cpu: 500m
memory: 512Mi
Burstable — 중간
# requests < limits 이거나, 일부만 설정
resources:
requests:
cpu: 200m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
BestEffort — 최하위
# requests와 limits 모두 미설정
resources: {}
** 퇴출 우선순위 **: BestEffort → Burstable → Guaranteed
프로덕션 중요 워크로드는 Guaranteed 또는 Burstable을 사용하고, BestEffort는 절대 피해야 합니다.
# Pod의 QoS 클래스 확인
kubectl get pod my-pod -o jsonpath='{.status.qosClass}'
LimitRange — 기본값과 범위 설정
네임스페이스에서 리소스 설정을 빠뜨린 Pod에 기본값을 적용하고, 과도한 요청을 제한합니다.
apiVersion: v1
kind: LimitRange
metadata:
name: default-limits
namespace: production
spec:
limits:
- type: Container
default: # limits 기본값
cpu: 500m
memory: 512Mi
defaultRequest: # requests 기본값
cpu: 100m
memory: 128Mi
max/min으로 허용 범위를 제한하면 과도한 리소스 요청이나 너무 작은 설정을 방지할 수 있습니다.
max: # 최대값
cpu: 2
memory: 4Gi
min: # 최소값
cpu: 50m
memory: 64Mi
- type: Pod
max:
cpu: 4
memory: 8Gi
리소스를 설정하지 않은 Pod에 자동으로 defaultRequest와 default가 적용됩니다.
ResourceQuota — 네임스페이스 총량 제한
네임스페이스 전체가 사용할 수 있는 리소스의 총합을 제한합니다.
apiVersion: v1
kind: ResourceQuota
metadata:
name: team-quota
namespace: team-a
spec:
hard:
requests.cpu: "10" # 총 CPU requests 합계
requests.memory: 20Gi # 총 메모리 requests 합계
limits.cpu: "20" # 총 CPU limits 합계
limits.memory: 40Gi # 총 메모리 limits 합계
pods: "50" # 최대 Pod 수
persistentvolumeclaims: "10" # 최대 PVC 수
# 사용량 확인
kubectl describe resourcequota team-quota -n team-a
# Name: team-quota
# Resource Used Hard
# -------- ---- ----
# limits.cpu 4 20
# limits.memory 8Gi 40Gi
# pods 15 50
# requests.cpu 2 10
# requests.memory 4Gi 20Gi
리소스 설정 가이드
권장하는 설정 방법
- ** 먼저 VPA를 Off 모드로 배포 **하여 추천값을 확인합니다
- requests는 일반적인 사용량 으로, limits는 최대 사용량 으로 설정합니다
- **CPU limits는 신중하게 **: 불필요한 쓰로틀링을 유발할 수 있습니다
- ** 메모리 limits는 반드시 설정 **: OOMKill보다 메모리 무한 사용이 더 위험합니다
# 웹 서버 예제
resources:
requests:
cpu: 200m # 일반적인 사용량
memory: 256Mi
limits:
cpu: 1 # 피크 시 상한 (또는 미설정)
memory: 512Mi # 반드시 설정
# Java 애플리케이션 예제 (힙 메모리 고려)
resources:
requests:
cpu: 500m
memory: 1Gi # -Xmx 값보다 여유 있게
limits:
memory: 1536Mi # 힙 + 메타스페이스 + 네이티브 메모리
실무에서 자주 하는 실수
- **requests 미설정 **: 스케줄러가 0으로 간주하여 노드에 과도하게 배치됨
- limits = requests: 안전하지만 리소스 활용률이 낮아짐
- ** 메모리 limits 미설정 **: 메모리 누수가 노드 전체에 영향
- **CPU limits가 너무 낮음 **: 불필요한 쓰로틀링으로 레이턴시 증가
주의할 점
1. CPU limits를 너무 타이트하게 잡으면 쓰로틀링으로 레이턴시가 급증한다
CPU limits에 도달하면 커널의 CFS(Completely Fair Scheduler)가 해당 컨테이너의 CPU 시간을 강제로 제한합니다. 평소에는 CPU를 30%만 쓰더라도 순간 스파이크 시 limits에 걸려서 응답 시간이 수배로 늘어납니다. CPU limits를 아예 설정하지 않거나, requests의 3~5배 정도로 여유 있게 잡는 팀이 늘어나고 있습니다.
2. 메모리 limits 없이 운영하면 한 Pod의 메모리 누수가 노드 전체를 죽인다
메모리는 CPU와 달리 압축(throttle)이 불가능합니다. limits가 없는 Pod에 메모리 누수가 발생하면 노드의 전체 메모리를 소진하고, 같은 노드의 다른 Pod들까지 OOM Kill됩니다. 메모리 limits는 반드시 설정하세요.
3. requests와 limits를 동일하게 설정하면 안전하지만 리소스 활용률이 낮아진다
requests = limits(Guaranteed QoS)로 설정하면 OOM Kill 우선순위가 가장 낮아서 안전합니다. 하지만 실제로는 requests만큼의 리소스가 항상 예약되어 있어서 노드의 리소스 활용률이 떨어집니다. 비용이 중요한 환경에서는 requests < limits(Burstable)로 설정하고 모니터링으로 보완하는 전략이 현실적입니다.
정리
requests는 스케줄링과 최소 보장의 기준, limits는 런타임 상한선입니다. CPU 초과는 쓰로틀링, 메모리 초과는 OOMKill로 이어집니다. QoS 클래스를 이해하고 LimitRange/ResourceQuota로 네임스페이스 수준의 관리를 추가하면, 클러스터 리소스를 효율적이고 안전하게 사용할 수 있습니다.