1. 개요

스케줄러는 새 Pod 를 어느 노드에 둘지 정한다. 이때 Pod 가 얼마나 쓸지 모르면 판단할 수 없으니, 우리가 리소스 숫자를 적어 줘야 한다. 이 숫자를 어떻게 잡느냐에 따라 Pod 가 아예 안 뜨거나, 뜬 뒤에 죽거나, 옆 Pod 를 밀어낸다. 이 글에서는 그 숫자 둘(requests 와 limits)과 노드를 고르는 규칙을 본다.


2. 핵심 내용

2-1. requests 와 limits 는 용도가 다르다

resources:
  requests:
    cpu: 250m        # 0.25 코어
    memory: 256Mi
  limits:
    memory: 512Mi
  • requests: 스케줄링의 기준이다. “이만큼은 보장해 달라” 는 값이고, 스케줄러는 이 합만 본다.
  • limits: 실행 중의 상한이다. 넘으면 제재를 받는다.

핵심은 스케줄러가 실제 사용량을 보지 않는다는 것이다. 노드에 요청된 requests 의 합이 노드의 allocatable(OS·kubelet 몫을 뺀 배정 가능량)을 넘지 않는 범위에서만 Pod 를 받는다. 아무도 실제로는 그만큼 안 쓰고 있어도 그렇다.

2-2. CPU 는 스로틀, 메모리는 OOMKilled

limit 를 넘었을 때의 결과가 자원마다 다르다.

  • CPU: 압축 가능한 자원이다. limit 를 넘으면 죽지 않고 느려진다(스로틀). 응답이 이유 없이 느린데 CPU limit 이 낮게 걸려 있는 경우가 이 사례다.
  • 메모리: 압축이 안 된다. limit 를 넘으면 커널이 프로세스를 죽이고, Pod 상태에 OOMKilled(종료 코드 137)가 남는다. JVM 처럼 힙 외 메모리까지 쓰는 앱은 limit 를 힙 크기와 같게 잡으면 죽는다.

2-3. QoS 클래스

requests 와 limits 를 어떻게 적었는지에 따라 Pod 에 등급이 붙는다.

클래스조건노드 메모리가 부족할 때
Guaranteed모든 컨테이너의 CPU·메모리 requests 와 limits 가 같음마지막에 쫓겨남
Burstable일부만 지정하거나 requests 가 limits 보다 작음중간
BestEffort아무것도 안 적음가장 먼저 쫓겨남

노드가 메모리 압박을 받으면 kubelet 이 이 순서를 참고해 Pod 를 내쫓는다. 중요한 DB 를 BestEffort 로 두면 가장 먼저 후보가 된다.

2-4. 노드를 고르는 규칙

  • nodeSelector: 노드 라벨이 맞는 곳에만 배치. 가장 단순하다.
  • affinity: 같은 걸 더 유연하게. “가능하면(preferred)” 과 “반드시(required)” 를 고르고, Pod 끼리 붙이거나(affinity) 떨어뜨리는(anti-affinity) 것도 된다. 복제본을 서로 다른 노드에 흩어 놓을 때 쓴다.
  • taint 와 toleration: 노드가 “특정 Pod 만 오라” 고 밀어내는 표시가 taint 이고, 그걸 견디겠다는 선언이 Pod 의 toleration 이다. 예를 들어 컨트롤 플레인 노드에 일반 Pod 가 안 오게 하는 데 쓴다.

셋의 방향이 다르다. selector 와 affinity 는 Pod 가 노드를 끌어당기고, taint 는 노드가 Pod 를 밀어낸다.

2-5. 합이 넘치면 Pending

requests 를 넣을 자리가 어느 노드에도 없으면 스케줄러는 Pod 를 배치하지 못하고 Pending 으로 둔다. 이벤트에는 Insufficient memory 같은 메시지가 남는다.

kubectl describe pod web-xxxxx
# Events:
#   Warning  FailedScheduling  0/3 nodes are available:
#   3 Insufficient memory.
kubectl describe node <노드> | grep -A8 "Allocated resources"

3편에서 롤아웃 중 새 Pod 가 겹쳐 뜬다고 했는데, 여유가 없으면 바로 여기서 막힌다.

2-6. 어디서 실수하나

  • requests 를 과하게 잡아 실제 사용량은 낮은데 노드가 꽉 찬 것으로 계산된다.
  • requests 를 안 적어서 BestEffort 가 되고, 압박 때 먼저 쫓겨난다.
  • 메모리 limit 를 앱의 평균 사용량 근처로 빡빡하게 잡아 피크에 OOMKilled 가 난다.
  • CPU limit 를 낮게 걸고 응답 지연의 원인을 딴 곳에서 찾는다.

3. 마무리

요약

  • requests 는 스케줄링 기준이고 limits 는 실행 중 상한이다. 스케줄러는 실제 사용량을 보지 않는다.
  • CPU 는 limit 를 넘으면 느려지고, 메모리는 넘으면 OOMKilled 로 죽는다.
  • QoS 는 Guaranteed, Burstable, BestEffort 순으로 보호되며 메모리 압박 때 쫓겨나는 순서가 반대다.
  • requests 합이 어느 노드의 allocatable 도 못 채우면 Pod 는 Pending 이 된다.

다음은 Kubernetes 기초 8 - kubectl 로 문제 찾는 순서이다.