infra
Platform

모듈 맵

[Kubernetes] requests와 limits 적정 값 계산과 CPU 스로틀링 대처

0 / 29 완료

펼치기
0 / 29 완료0%

쿠버네티스 & GitOps · 11 / 29

[Kubernetes] requests와 limits 적정 값 계산과 CPU 스로틀링 대처

CPU·메모리 requests/limits와 QoS 클래스를 이해하고 OOMKilled를 예방합니다

🚨INCIDENT ALERT
HIGH

신규 배치 파드 하나가 노드 메모리를 모두 사용하면서 같은 노드의 API 파드까지 느려졌습니다. requests와 limits를 설정하지 않으면 스케줄러도 용량을 예측할 수 없고 kubelet도 보호선을 그을 수 없습니다. 리소스 제한은 멀티테넌트 클러스터의 기본 안전장치입니다.

프로덕션 클러스터에서 새벽 2시 알람이 울렸습니다. 특정 노드의 메모리 사용률이 98%를 넘어서며 여러 파드가 동시에 죽기 시작한 겁니다. 원인을 파보니 며칠 전 배포한 데이터 처리 파드에 메모리 limits가 설정되어 있지 않았고, 배치 작업 중 메모리 누수가 쌓이면서 노드 전체를 잠식했습니다. 이 패턴은 Kubernetes 초기 운영 팀이 가장 자주 겪는 인시던트 유형입니다. requests와 limits를 이해하면 이런 상황을 사전에 차단할 수 있습니다.

Kubernetes의 리소스 관리 모델은 단순합니다. requests는 "이 파드를 스케줄링할 노드에 최소한 이만큼은 남아있어야 한다"는 예약량이고, limits는 "이 이상 사용하면 강제 종료한다"는 상한선입니다. 이 두 값의 비율이 파드의 QoS(Quality of Service) 클래스를 결정하고, 노드가 리소스 부족에 빠졌을 때 어떤 파드가 먼저 종료되는지를 결정합니다. 실무에서 이를 제대로 설정하지 않은 클러스터는 언제 터질지 모르는 시한폭탄과 같습니다.

이번 챕터에서 배울 것
  • 1CPU requests와 limits의 동작 원리(throttling과 OOM)를 설명할 수 있다
  • 2메모리 requests와 limits의 차이점을 설명할 수 있다
  • 3QoS 클래스 3종(Guaranteed, Burstable, BestEffort)을 구분할 수 있다
  • 4LimitRange로 네임스페이스 기본값을 강제할 수 있다
  • 5ResourceQuota로 팀별 리소스를 할당할 수 있다
  • 6실무 limits 설정 가이드라인을 적용할 수 있다
실습 환경 준비
클러스터 노드 리소스 확인
kubectl get nodes -o custom-columns='NAME:.metadata.name,CPU:.status.capacity.cpu,MEM:.status.capacity.memory'
실습용 네임스페이스 생성
kubectl create namespace resource-demo
metrics-server 설치 여부 확인
kubectl top nodes 2>&1 | head -3
현재 파드 리소스 사용량 확인
kubectl top pods -A 2>/dev/null | head -10
💡개념

requests와 limits: 스케줄러와 kubelet의 역할 분담

배치 작업 파드 하나가 노드 메모리를 모두 소진하면서 같은 노드에 있던 API 서버 파드들이 연이어 OOMKilled됐습니다. limits가 없었기 때문입니다. 반대로 requests를 너무 높게 설정하면 실제로는 여유가 있는 노드인데 파드가 Pending 상태로 스케줄이 안 됩니다. requests와 limits는 Kubernetes가 파드를 배치하고 실행 중에 제어하는 두 단계의 안전장치입니다. 이 두 값의 관계를 이해해야 클러스터 자원을 낭비하지 않으면서도 이웃 파드를 보호하는 올바른 설정을 할 수 있습니다.

requests와 limits는 각각 다른 Kubernetes 컴포넌트가 사용합니다. requests는 스케줄러(kube-scheduler)가 파드를 어느 노드에 배치할지 결정할 때 사용하고, limits는 kubelet이 실행 중인 컨테이너를 제어할 때 사용합니다.

requests와 limits: CPU·메모리 — requests는 스케줄러가 파드 배치 노드를 정할 때 쓰는 최소 보장량, limits는 kubelet이 실행 중 제어하는 상한. CPU는 압축 가능 자원이라 limits 초과 시 throttling(느려짐), 메모리는 비압축이라 초과 시 즉시 OOMKilled(종료 코드 137)확대

CPU와 메모리는 동작 방식이 다릅니다.

  • CPU limits 초과: 컨테이너는 죽지 않습니다. CPU throttling이 걸려 속도가 느려집니다.
  • 메모리 limits 초과: 컨테이너가 즉시 OOMKilled(Out of Memory Killed, 종료 코드 137)됩니다.
YAML
# resource-demo.yaml
apiVersion: v1
kind: Pod
metadata:
  name: resource-demo
  namespace: resource-demo
spec:
  containers:
  - name: app
    image: nginx:1.25
    resources:
      requests:
        memory: "128Mi"   # 스케줄러: 이 노드에 128MiB 여유가 있어야 함
        cpu: "250m"       # 스케줄러: 0.25 코어(250 밀리코어) 여유 필요
      limits:
        memory: "256Mi"   # kubelet: 초과 시 OOMKilled
        cpu: "500m"       # kubelet: 초과 시 CPU throttling (죽이지 않음)

CPU 단위 m은 밀리코어입니다. 1000m = 1 CPU 코어. 250m은 1코어의 25%를 의미합니다.

Kubernetes
# 파드 생성 및 리소스 설정 확인
kubectl apply -f resource-demo.yaml

kubectl describe pod resource-demo -n resource-demo | grep -A 10 "Limits\|Requests"
# Limits:
#   cpu:     500m
#   memory:  256Mi
# Requests:
#   cpu:      250m
#   memory:   128Mi

# 실제 사용량 모니터링 (metrics-server 필요)
kubectl top pod resource-demo -n resource-demo
# NAME            CPU(cores)   MEMORY(bytes)
# resource-demo   3m           8Mi
🔍실행 후 확인할 것
  • kubectl top pod <name> -n <ns>에서 현재 CPU/메모리 사용량을 먼저 확인 — 메모리 사용량이 limits에 설정한 값의 80% 이상이면 OOMKilled 임박. kubectl describe pod로 limits 값과 대조
  • RESTARTS 수치가 오르면서 Events에 "OOMKilled"가 보이면 → 메모리 limits 부족. kubectl describe pod <name>의 "Last State: Terminated Reason: OOMKilled"로 확인 후 limits.memory를 최소 2배로 늘려 테스트
  • kubectl top node에서 CPU% 높고 kubectl top pods에서 limits가 없는 파드가 있으면 → BestEffort QoS 파드가 CPU를 독점 중. requests와 limits를 모두 설정해 Guaranteed QoS로 전환하면 노드 압박 시 eviction 우선순위가 낮아짐
💡개념

QoS 클래스: 메모리 부족 시 누가 먼저 죽는가

Kubernetes는 노드 메모리가 부족해질 때 파드를 evict(강제 퇴거)합니다. 이때 QoS 클래스에 따라 우선순위가 결정됩니다. BestEffort가 가장 먼저, Guaranteed가 가장 마지막으로 종료됩니다.

QoS 클래스와 OOM 우선순위 — requests/limits 설정에 따라 Guaranteed(requests=limits)·Burstable(일부 설정)·BestEffort(미설정)로 분류. 노드 메모리 부족 시 BestEffort → Burstable → Guaranteed 순으로 OOM Kill 대상이 되므로, 중요 워크로드는 Guaranteed로 설정해 보호확대

YAML
# QoS: Guaranteed — requests == limits (모든 컨테이너, CPU+메모리 전부)
resources:
  requests:
    memory: "256Mi"
    cpu: "500m"
  limits:
    memory: "256Mi"  # requests와 동일
    cpu: "500m"      # requests와 동일

---
# QoS: Burstable — requests < limits, 또는 일부 컨테이너만 설정
resources:
  requests:
    memory: "128Mi"
    cpu: "250m"
  limits:
    memory: "512Mi"  # requests와 다름
    cpu: "1000m"

---
# QoS: BestEffort — requests, limits 모두 미설정
# (설정하지 않으면 자동으로 BestEffort)
resources: {}
Kubernetes
# 파드의 QoS 클래스 확인
kubectl get pod resource-demo -n resource-demo \
  -o jsonpath='{.status.qosClass}'
# Burstable

# 모든 파드의 QoS 클래스 일괄 확인
kubectl get pods -A \
  -o custom-columns='NAMESPACE:.metadata.namespace,NAME:.metadata.name,QOS:.status.qosClass'

실무 가이드:

  • DB, 메시지 큐, 결제 서비스 → Guaranteed (안정성 최우선)
  • API 서버, 백엔드 서비스 → Burstable (평소에는 낮게, 트래픽 몰릴 때 버스트)
  • 배치 작업, 개발 환경 → BestEffort (OK, 단 limits는 꼭 설정)
💡개념

requests·limits가 스케줄부터 런타임까지 작동하는 법 — 4단계

같은 resources 블록 하나가 서로 다른 두 컴포넌트에서, 서로 다른 두 시점에 쓰입니다. requests는 파드가 뜨기 전 스케줄러가, limits는 파드가 뜬 뒤 kubelet이 씁니다. 이 분업을 단계로 펼쳐 보면 왜 어떤 파드는 Pending으로 안 뜨고(스케줄 실패), 어떤 컨테이너는 느려지기만 하고(CPU throttle), 어떤 컨테이너는 죽는지(OOMKilled), 그리고 노드가 터질 때 누가 먼저 쫓겨나는지가 한 줄기로 이어집니다.

TEXT
파드 생성 (requests: cpu 250m·mem 128Mi / limits: cpu 500m·mem 256Mi)
   │
   ① 스케줄 배정                (kube-scheduler가 requests로 노드 여유 확인 → 배치)
   │
   ② 런타임 제한 설정            (kubelet이 limits를 노드의 cgroup에 기록)
   │
   ├─③a CPU가 limit 초과         (압축 가능 → CFS로 throttle: 느려질 뿐 안 죽음)
   ├─③b 메모리가 limit 초과       (압축 불가 → OOMKilled, exit 137, 재시작)
   │
   ④ 노드 메모리 부족 시 축출 순위  (QoS: BestEffort → Burstable → Guaranteed)
   ▼
requests=배치 기준 · limits=상한(초과 시 throttle 또는 kill) · 둘의 관계=QoS 등급

각 단계에서 무슨 일이 일어나고, 막히면 어떤 증상인가:

단계하는 일 (누가·언제)여기서 어긋나면
① 스케줄 배정kube-scheduler가 파드 생성 시 requests 합이 들어갈 노드를 찾음(bin-packing)어떤 노드도 requests만큼 여유가 없음 → 파드가 Pending. requests 과대 설정도 같은 낭비 증상
② 런타임 제한kubelet이 파드 기동 시 limits를 cgroup(CPU 쿼터·메모리 상한)으로 커널에 설정limits 미설정이면 상한이 없어 이웃 파드까지 위협(BestEffort)
③a CPU 초과CPU는 압축 가능 자원 → CFS가 주기마다 쿼터로 조임throttle로 느려짐. top은 낮아도 스로틀 카운터가 오르면 p99 지연
③b 메모리 초과메모리는 압축 불가 → 회수 못 해 커널이 프로세스 종료OOMKilled(exit 137) → RESTARTS 증가 → CrashLoopBackOff
④ 축출 우선순위노드 메모리 압박 시 QoS 등급으로 evict 순서 결정설정 없는 BestEffort 파드가 가장 먼저 쫓겨남

즉 하나의 증상은 하나의 단계를 가리킵니다 — Pending이면 ①(requests가 노드 여유보다 큼), 느려지기만 하면 ③a(CPU throttle), RESTARTS와 exit 137이 함께면 ③b(메모리 OOMKilled), 노드가 붐빌 때 유독 먼저 죽으면 ④(BestEffort/Burstable). 그래서 requests는 표시값이 아니라 ①과 ④의 입력이고, limits는 ②에서 설정돼 ③의 방식(throttle 대 kill)을 가르는 상한입니다.

💡개념

LimitRange: 네임스페이스 기본값 강제

팀원이 resources를 빠뜨리고 배포할 때를 대비해 LimitRange로 기본값과 허용 범위를 강제할 수 있습니다. resources를 설정하지 않은 파드에는 자동으로 default 값이 주입됩니다.

YAML
# limitrange.yaml
apiVersion: v1
kind: LimitRange
metadata:
  name: default-limits
  namespace: resource-demo
spec:
  limits:
  - type: Container
    default:          # limits 미설정 시 자동 주입
      cpu: "500m"
      memory: "256Mi"
    defaultRequest:   # requests 미설정 시 자동 주입
      cpu: "100m"
      memory: "128Mi"
    max:              # 이 값 이상은 허용 안 함
      cpu: "2"
      memory: "2Gi"
    min:              # 이 값 미만은 허용 안 함
      cpu: "50m"
      memory: "64Mi"
Kubernetes
kubectl apply -f limitrange.yaml

# 검증: resources 없이 파드 생성
kubectl run test-default --image=nginx:1.25 -n resource-demo

# 자동으로 default 값이 주입됐는지 확인
kubectl describe pod test-default -n resource-demo | grep -A 8 "Limits\|Requests"
# Limits:
#   cpu:     500m    ← LimitRange에서 주입됨
#   memory:  256Mi   ← LimitRange에서 주입됨

kubectl delete pod test-default -n resource-demo

실습: 메모리 limits 효과 직접 확인

실제로 메모리 limits를 초과하면 어떤 일이 발생하는지 확인합니다.

위험 명령어이 파드는 128MB를 요청하지만 limits는 64MB로 설정되어 의도적으로 OOMKilled를 발생시킵니다. 클러스터 메모리가 부족한 상황에서 실행하면 같은 노드의 다른 파드에 영향을 줄 수 있습니다. 반드시 개발 클러스터의 resource-demo 네임스페이스에서만 실행하세요.

OOMKilled 유발 파드 생성 — 노드 메모리 압박

안전한 실행 조건: kubectl config current-context 로 개발 클러스터인지 확인. kubectl top nodes 로 노드 여유 메모리가 충분한지(최소 200MB 이상) 확인 후 진행하세요.

실행 전 반드시 확인

  • kubectl config current-context 로 개발 클러스터(minikube 등)인지 확인
  • kubectl top nodes 로 노드 메모리 여유가 충분한지 확인
  • 실습 완료 후 kubectl delete pod memory-stress -n resource-demo 로 즉시 정리
kubectl apply -f - (memory-stress 파드 생성)

위 항목을 모두 확인한 후 복사할 수 있습니다

로컬 터미널
# 메모리를 급격히 소모하는 파드 생성 (limits: 64Mi)
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: memory-stress
  namespace: resource-demo
spec:
  containers:
  - name: stress
    image: polinux/stress
    args: ["--vm", "1", "--vm-bytes", "128M", "--vm-hang", "1"]
    resources:
      requests:
        memory: "64Mi"
      limits:
        memory: "64Mi"  # 128MB 요청하지만 64MB만 허용
EOF

# 파드 상태 관찰 (OOMKilled 대기)
kubectl get pod memory-stress -n resource-demo -w
# NAME            READY   STATUS      RESTARTS   AGE
# memory-stress   0/1     OOMKilled   0          5s
# memory-stress   0/1     CrashLoopBackOff   1   10s

# 종료 이유 확인
kubectl describe pod memory-stress -n resource-demo | grep -A 5 "Last State"
# Last State:     Terminated
#   Reason:       OOMKilled
#   Exit Code:    137

# 정리
kubectl delete pod memory-stress -n resource-demo

실습: ResourceQuota로 네임스페이스 총량 제한

로컬 터미널
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: ResourceQuota
metadata:
  name: team-quota
  namespace: resource-demo
spec:
  hard:
    requests.cpu: "2"
    requests.memory: "2Gi"
    limits.cpu: "4"
    limits.memory: "4Gi"
    pods: "10"
EOF

# 현재 사용량 vs 할당량 확인
kubectl describe resourcequota team-quota -n resource-demo
# Name:            team-quota
# Namespace:       resource-demo
# Resource         Used  Hard
# --------         ----  ----
# limits.cpu       500m  4
# limits.memory    256Mi 4Gi
# pods             1     10
# requests.cpu     100m  2
# requests.memory  128Mi 2Gi

신규 데이터 집계 서비스를 배포한 후 처음 며칠은 정상 동작했습니다. 그런데 시간이 지나면서 파드가 주기적으로 재시작되고, 재시작 간격이 점점 짧아지더니 결국 노드 전체가 느려지기 시작했습니다. kubectl get pods를 보면 RESTARTS 숫자가 비정상적으로 높습니다.

Kubernetes
# 증상 확인
kubectl get pods -n production
# NAME              READY   STATUS             RESTARTS   AGE
# data-aggregator   0/1     CrashLoopBackOff   47         6h

# 1단계: 종료 이유 파악
kubectl describe pod data-aggregator -n production | grep -A 10 "Last State\|Reason\|Exit Code"
# Last State:     Terminated
#   Reason:       OOMKilled       ← 핵심 단서
#   Exit Code:    137
#   Started:      Fri, 16 May 2026 03:14:22
#   Finished:     Fri, 16 May 2026 03:19:57

# 2단계: 현재 리소스 설정 확인
kubectl get pod data-aggregator -n production \
  -o jsonpath='{.spec.containers[0].resources}' | python3 -m json.tool
# {}  ← resources가 아예 없음!

# 3단계: 파드가 실제로 얼마나 쓰고 있었는지 (이전 로그)
kubectl logs data-aggregator -n production --previous 2>&1 | tail -20

# 4단계: 동종 파드가 있다면 현재 메모리 사용량 측정
kubectl top pod -l app=data-aggregator -n production
# NAME                      CPU(cores)   MEMORY(bytes)
# data-aggregator-xyz-abc   15m          487Mi

# 5단계: 안전한 limits 계산 (측정값 × 1.5 여유)
# 측정값 487Mi → limits: 768Mi, requests: 256Mi 로 설정

# 6단계: Deployment 수정
kubectl set resources deployment/data-aggregator \
  -n production \
  --requests=cpu=100m,memory=256Mi \
  --limits=cpu=500m,memory=768Mi

# 7단계: 롤아웃 확인
kubectl rollout status deployment/data-aggregator -n production

# 8단계: 이후 안정성 모니터링
kubectl top pod -l app=data-aggregator -n production --watch

근본 원인: resources 블록 자체가 없어 QoS 클래스가 BestEffort였습니다. 메모리 누수가 있는 상황에서 limits가 없으니 프로세스는 계속 메모리를 늘렸고, 결국 노드의 OOM killer가 컨테이너를 강제 종료했습니다.

예방: 모든 Deployment에 resources 블록을 필수로 작성하도록 팀 컨벤션을 정하고, LimitRange로 미설정 시 기본값이 주입되도록 네임스페이스를 설정해두세요. CI 파이프라인에서 kubectl apply --dry-run=server로 LimitRange 위반 여부를 사전 검사할 수도 있습니다.

심화 — CPU가 한가한데 왜 느릴까

💡개념

심화: CPU limit은 어떻게 강제되나 — CFS 쿼터와 스로틀링의 함정

메모리 limit은 초과하면 OOMKilled로 죽어서 눈에 잘 띕니다. 반면 CPU limit은 죽이지 않고 느리게 만들기만 해서, 정작 장애 원인일 때조차 지나치기 쉽습니다. CPU limit이 실제로 어떻게 강제되는지 알면 이 함정을 피할 수 있습니다.

CPU limit은 리눅스 커널의 CFS 대역폭 컨트롤러가 강제합니다. 시간을 짧은 주기(period, 기본 100ms) 로 쪼개고, 그 주기 안에서 cgroup이 쓸 수 있는 쿼터(quota) 를 정합니다. limit이 500m이면 매 100ms마다 50ms의 CPU 시간을 줍니다(모든 코어 합산). 문제는 쿼터를 언제 다 쓰느냐입니다.

  • 버스트가 쿼터를 초반에 소진하면 남은 주기 내내 스로틀됩니다. 예컨대 4개 스레드가 동시에 25ms씩 벽시계 시간을 돌면 100ms 주기의 쿼터를 초반 25ms 만에 소진하고, 나머지 75ms 동안 그 컨테이너는 CPU를 못 받습니다. 이 75ms가 그대로 응답 지연이 됩니다.
  • 그래서 1분 평균 사용률(kubectl top)이 낮아도 스로틀은 심할 수 있습니다. 평균은 낮지만 매 주기마다 짧고 굵게 스로틀당하는 것이라, top으로는 절대 안 보이고 p99 지연으로만 드러납니다.
  • 멀티코어일수록 쉽게 걸립니다. 쿼터는 코어 합산이라, 병렬 처리를 하는 앱에 낮은 CPU limit을 걸면 짧은 순간에 여러 코어가 쿼터를 함께 소진합니다.

확인 지표는 사용률이 아니라 스로틀 카운터입니다. cgroup의 cpu.stat에서 nr_throttled(스로틀된 주기 수)와 throttled_time, 또는 Prometheus의 container_cpu_cfs_throttled_periods_total을 봅니다. 스로틀된 주기 비율이 높으면 CPU limit이 병목입니다. 그래서 지연에 민감한 서비스는 CPU requests만 두고 CPU limit은 제거하거나 넉넉히 잡는 팀이 많습니다 — CPU는 압축 가능 자원이라 limit이 없어도 노드가 붐빌 때 requests 비율대로 공정하게 나눠 갖기 때문입니다(메모리와 달리 폭주로 노드를 죽이지 않습니다).

상황: 트래픽도 배포도 그대로인데 특정 서비스의 p99 지연이 주기적으로 튑니다. kubectl top pod로 보면 CPU는 limit의 30% 남짓이라 리소스 부족처럼 보이지 않고, 메모리도 여유가 있습니다. 슬로우 쿼리나 GC를 의심해 봐도 딱 맞지 않습니다.

원인: CPU limit에 의한 CFS 스로틀링입니다. 평균 사용률은 낮지만, 요청이 몰리는 순간 여러 스레드가 100ms 주기의 쿼터를 초반에 소진해 남은 주기 동안 스로틀됩니다. 평균값(top)으로는 절대 안 보이고, 순간 스로틀이 꼬리 지연(p99)으로만 나타납니다.

진단: 사용률이 아니라 스로틀 카운터를 봅니다. 파드 안에서 cat /sys/fs/cgroup/cpu.stat(cgroup v2, v1은 cpu.cfs_throttled...)의 nr_throttled·throttled_time이 계속 증가하는지, 또는 대시보드에서 container_cpu_cfs_throttled_periods_total을 전체 주기 수와 비교합니다. 스로틀 비율이 유의미하게 높고 그 시점이 지연 스파이크와 겹치면 확정입니다.

해결: CPU limit을 실제 버스트를 담을 만큼 올리거나, 지연에 민감한 서비스라면 CPU limit을 제거합니다(CPU requests는 유지 — 스케줄링·공정 분배 기준). CPU는 압축 가능 자원이라 limit이 없어도 노드가 붐빌 때 requests 비율대로 나눠 가질 뿐, 메모리처럼 노드를 위협하지 않습니다. 메모리 limit은 그대로 두는 것과 대비됩니다. 조정 후 다시 스로틀 카운터가 잠잠해지는지, p99가 안정되는지 확인합니다(HPA(Horizontal Pod Autoscaler) 메트릭 기반 파드 확장).

💼
실무 맥락
현업 패턴

시나리오: 신규 API 서비스의 리소스 설정값 결정

스타트업의 첫 번째 Kubernetes 마이그레이션을 맡았습니다. VM에서 잘 돌던 Node.js API 서버를 K8s로 옮기는데, resources를 어떻게 설정해야 할지 모르겠습니다.

로컬 터미널
# 1단계: VM에서 현재 사용량 측정 (이전 환경 참고)
# VM에서: top, htop, free -m 등으로 1주일치 평균 측정
# 측정 결과: 평균 CPU 0.15코어, 평균 메모리 180MB, 피크 메모리 320MB

# 2단계: 초기 값 보수적으로 설정하고 배포
cat <<EOF > api-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-server
  namespace: production
spec:
  replicas: 2
  selector:
    matchLabels:
      app: api-server
  template:
    metadata:
      labels:
        app: api-server
    spec:
      containers:
      - name: api
        image: myapp/api:v1.0.0
        ports:
        - containerPort: 3000
        resources:
          requests:
            cpu: "200m"    # 평균 0.15코어 + 여유
            memory: "256Mi" # 평균 180MB 올림
          limits:
            cpu: "800m"    # 버스트 허용 (Burstable QoS)
            memory: "512Mi" # 피크 320MB + 안전 여유
EOF

kubectl apply -f api-deployment.yaml

# 3단계: 1주일 후 실제 사용량 기반으로 튜닝
kubectl top pods -l app=api-server -n production
# NAME                   CPU(cores)   MEMORY(bytes)
# api-server-xxx-aaa     45m          198Mi
# api-server-xxx-bbb     52m          205Mi

# CPU가 요청의 25%밖에 안 쓰므로 requests 하향 조정
kubectl set resources deployment/api-server \
  -n production \
  --requests=cpu=100m,memory=256Mi \
  --limits=cpu=500m,memory=512Mi

# 4단계: HPA 설정을 위한 기준선 완성
# (다음 모듈에서 HPA 추가 예정)

실무 포인트: requests는 측정값 기반으로, limits는 "이게 넘어가면 뭔가 잘못된 것"이라는 안전망 개념으로 설정합니다. 처음부터 완벽한 값을 찾으려 하지 말고, 배포 후 kubectl top으로 1-2주 모니터링해 실측값 기반으로 조정하는 것이 현실적입니다.

실습 단계
1

requests와 limits가 설정된 파드 배포

kubectl create namespace resource-demo kubectl apply -f - <<EOF apiVersion: v1 kind: Pod metadata: name: resource-demo namespace: resource-demo spec: containers: - name: app image: nginx:1.25 resources: requests: memory: '128Mi' cpu: '250m' limits: memory: '256Mi' cpu: '500m' EOF kubectl get pod resource-demo -n resource-demo

예상 출력

NAME            READY   STATUS    RESTARTS   AGE
resource-demo   1/1     Running   0          10s
2

파드 리소스 설정 확인

kubectl describe pod resource-demo -n resource-demo | grep -A6 'Limits\|Requests'

예상 출력

Limits:
  cpu:     500m
  memory:  256Mi
Requests:
  cpu:      250m
  memory:   128Mi
3

QoS 클래스 확인

kubectl get pod resource-demo -n resource-demo \ -o jsonpath='{.status.qosClass}'

예상 출력

Burstable
4

LimitRange 적용 후 기본값 자동 주입 확인

kubectl apply -f - <<EOF apiVersion: v1 kind: LimitRange metadata: name: default-limits namespace: resource-demo spec: limits: - type: Container default: cpu: '500m' memory: '256Mi' defaultRequest: cpu: '100m' memory: '128Mi' EOF kubectl run test-default --image=nginx:1.25 -n resource-demo kubectl describe pod test-default -n resource-demo | grep -A6 'Limits\|Requests'

예상 출력

Limits:
  cpu:     500m
  memory:  256Mi
Requests:
  cpu:      100m
  memory:   128Mi
5

실제 리소스 사용량 확인 (metrics-server 필요)

kubectl top pod resource-demo -n resource-demo

예상 출력

NAME            CPU(cores)   MEMORY(bytes)
resource-demo   3m           8Mi

핵심 요약

개념역할초과 시 동작
CPU requests스케줄링 기준
CPU limits실행 중 제어throttling (느려짐, 종료 없음)
Memory requests스케줄링 기준
Memory limits실행 중 제어OOMKilled (종료 코드 137)
LimitRange네임스페이스 기본값/범위초과 시 파드 거부
ResourceQuota네임스페이스 총량 제한초과 시 파드 거부
QoS 클래스조건Eviction 우선순위
Guaranteedrequests == limits (전 컨테이너)마지막
Burstablerequests < limits (일부라도 설정)중간
BestEffort설정 없음가장 먼저

명령어·단축키 빠른 참조

이 모듈에서 사용량을 재고 QoS·OOM·스로틀을 진단할 때 쓴 kubectl 명령을 모았습니다.

명령어/단축키용도자주 쓰는 예
kubectl top pod실제 CPU/메모리 사용량 측정kubectl top pod --sort-by=memory · kubectl top pods -A
kubectl top node노드 여유 자원 확인kubectl top nodes
kubectl describe node노드 Allocatable·예약된 requests 확인kubectl describe node X | grep -A5 Allocated
kubectl describe podlimits·requests·종료 사유 확인kubectl describe pod X | grep -A5 "Last State" (OOMKilled)
kubectl get pod -o jsonpathQoS 클래스 확인kubectl get pod X -o jsonpath='{.status.qosClass}'
kubectl get pods -o custom-columnsQoS 일괄 조회... -o custom-columns='NAME:.metadata.name,QOS:.status.qosClass'
kubectl set resourcesrequests/limits 즉시 수정kubectl set resources deploy/X --requests=cpu=100m,memory=256Mi --limits=cpu=500m,memory=768Mi
kubectl describe resourcequota사용량 vs 할당량 대조kubectl describe resourcequota team-quota -n <ns>
kubectl get pod -wOOMKilled→CrashLoop 전환 관찰kubectl get pod memory-stress -w
cat .../cpu.statCPU 스로틀 카운터 확인nr_throttled·throttled_time이 늘면 CFS 스로틀
Exit Code 137OOMKilled 판별describeReason: OOMKilled + Exit Code: 137

관련 모듈로 더 깊이:

다음 모듈 hpa-autoscaling에서는 설정한 CPU requests를 기준으로 자동 스케일링하는 HPA를 다룹니다. requests 설정이 HPA 동작에 왜 필수인지, maxReplicas 설정으로 비용을 어떻게 제어하는지 실습합니다.

지식 확인

퀴즈 — 8문제

Q1

파드에 requests만 설정하고 limits를 설정하지 않으면 어떤 일이 발생할 수 있는가?

Q2

노드 메모리가 부족해 파드 Eviction이 시작됐다. 같은 노드에 A(requests=limits 설정), B(requests만 설정), C(requests/limits 미설정) 파드가 있을 때 가장 먼저 Evict되는 파드는?

Q3

컨테이너가 OOMKilled 상태로 반복 재시작될 때 올바른 초기 진단 명령어는?

Q4

파드의 모든 컨테이너에 requests == limits로 설정하면 QoS 클래스는 무엇이고 그 이점은?

Q5

CPU limit을 초과하는 컨테이너와 memory limit을 초과하는 컨테이너의 처리 차이는?

Q6

requests를 실제 사용량보다 훨씬 높게 잡으면 생기는 문제는?

Q7

[심화] kubectl top에서 CPU 사용량이 limit보다 한참 낮은데도 컨테이너가 CPU throttling을 겪을 수 있는 이유는?

Q8

[심화] CPU 사용률은 낮은데 p99 지연이 주기적으로 튀는 서비스를 진단할 때 가장 직접적으로 봐야 할 지표는?

0 / 8 답변

🧪 실습으로 확인하기

Pod OOMKilled — 메모리 한도 진단과 설정

중급

Pod가 주기적으로 재시작되고 kubectl describe에서 OOMKilled 메시지를 확인한 후, 메모리 사용량을 분석하고 resources.requests/limits를 적절히 설정해 안정화한다.

50📋 4단계💻 직접 환경
실습 시작하기 →

이것도 배워보세요