노드가 "나 지금 바쁘다"고 말하는 법 — cordon, drain, 그리고 node pressure

유지보수를 위해 노드 한 대를 재부팅해야 한다. 그냥 재부팅 버튼을 누르면? 그 노드의 Pod 20개가 즉시 죽고, Service가 요청을 보내다 실패한다. Kubernetes의 정답은 다르다 — 먼저 그 노드에 새 Pod가 못 들어오게 막고(cordon), 기존 Pod를 안전하게 옮긴 뒤(drain) 재부팅한다. 이 절차를 모르면 "유지보수 = 무차별 중단"이 된다.

이 글이 푸는 것은: 노드의 생명주기 상태(conditions), 노드가 스스로 "지금 압박 중"을 알리는 기전(node-pressure), 그리고 운영자가 노드를 안전하게 비우는 두 명령(cordon/drain)이다.

노드의 conditions — 노드 건강의 다섯 가지 표시

노드는 여러 condition(상태 표시)을 갖는다. Ready가 가장 중요하고, 나머지는 "압박(pressure)" 신호다.

Condition 의미 True일 때
Ready 노드가 정상적으로 Pod를 받을 수 있나 True=정상, False/Unknown=문제
DiskPressure 노드 디스크가 거의 찼나 True → kubelet이 Pod 퇴거 시작
MemoryPressure 노드 메모리가 부족한가 True → 퇴거 시작
PIDPressure 프로세스 수가 너무 많은가 True → 퇴거
NetworkUnavailable 네트워크가 잘못 설정됐나 True → 문제
# Kubernetes 1.36 — 노드 상태 확인
kubectl get nodes
kubectl describe node k8s-study-worker | grep -A6 Conditions

확인할 것: 각 condition의 True/False와 kubelet_last_observed_time.

Conditions:
  Type              Status  Last Heartbeat
  Ready             True    ...
  MemoryPressure    False   ...
  DiskPressure      False   ...

conditions는 kubelet이 apiserver에 주기적으로 보고한다(heartbeat). 이 보고가 멈추면(네트워크 단절 등) Node Controller는 일정 시간 후 Ready=Unknown으로 간주한다(기본 40초). 그리고 5분 후 그 노드의 Pod를 다른 곳으로 옮길 준비를 한다(02장).

node-pressure taint — 노드가 스스로를 "위험" 표시

노드가 디스크/메모리 압박 condition이 True가 되면, kubelet은 그 노드에 taint를 단다(08장의 taint와 같은 메커니즘). 예를 들어 DiskPressure taint. 이 taint가 있으면 일반 Pod는 그 노드로 새로 스케줄 안 된다. 그리고 kubelet은 condition을 해소하려 기존 Pod를 퇴거(eviction)하기 시작한다(11장).

이것이 자동 보호막이다 — 노드가 위험해지면 새 부하를 차단하고, 기존 부하를 줄인다. 운영자가 개입 전에 시스템이 스스로 안전한 방향으로 움직인다.

cordon — 새 Pod가 안 들어오게 막기

cordon은 노드를 Unschedulable로 표시한다. 효과: 그 노드에 새 Pod가 스케줄되지 않는다. 기존 Pod는 그대로 둔다.

kubectl cordon k8s-study-worker
kubectl get node k8s-study-worker

확인할 것: ROLES/SchedulingDisabled 표시. 새 Pod는 안 들어오지만 기존 Pod는 동작.

NAME             STATUS                     ROLES    AGE   VERSION
k8s-study-worker Ready,SchedulingDisabled   <none>   20m   v1.36.x

cordon은 "이제부터 안 받겠다"의 신호일 뿐, 기존 Pod를 옮기진 않는다. 기존 Pod까지 옮기려면 drain이 필요.

drain — 기존 Pod까지 안전하게 옮기기

drain은 cordon에 추가로 그 노드의 Pod를 다른 노드로 옮긴다(삭제 → ReplicaSet이 다른 곳에 재생성). 유지보수 전 노드를 비울 때 쓴다.

kubectl drain k8s-study-worker --ignore-daemonsets --delete-emptydir-data

확인할 것: 노드의 Pod들이 삭제되고, 다른 노드에 새 Pod가 생긴다.

  • --ignore-daemonsets: DaemonSet Pod는 옮길 수 없으니(노드마다 1개라) 무시.
  • --delete-emptydir-data: emptyDir을 쓴 Pod는 drain이 기본적으로 보호(데이터 손실 우려). 이 플래그로 허용.

drain은 단순 삭제가 아니라 안전 절차다. Pod에 PodDisruptionBudget(PDB)이 있으면 그것을 존중해 — "최소 2개는 유지해라"면 drain은 그 이하로 안 내려간다. 이것이 drain과 무차별 삭제의 차이.

PodDisruptionBudget — 자발적 장애 시 최소 가용성 보장

PDB는 "자발적 장애"(drain, 노드 업그레이드 등 운영자가 일으키는) 시 최소 몇 개는 유지할지를 정한다.

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata: {name: web-pdb}
spec:
  minAvailable: 2          # 또는 maxUnavailable: 1
  selector: {matchLabels: {app: web}}

drain은 PDB를 위반하면 대기한다 — "지금 web Pod 3개 중 2개만 남아야 하는데 drain하면 1개가 되니, 다른 Pod가 복구될 때까지 기다린다."

PDB는 자발적 장애(drain 등)만 보호한다. 노드 비정상 장애(노드 죽음)엔 PDB가 무력하다 — 그땐 Pod가 강제로 사라진다. PDB는 운영 작업 중단을 막지, 하드웨어 장애를 막지는 못한다.

uncordon — 유지보수 후 복구

노드 재부팅 후 돌아오면:

kubectl uncordon k8s-study-worker

이제 그 노드가 다시 Schedulable. 스케줄러가 새 Pod를 배치하기 시작한다. (이전에 drain으로 옮겨갔던 Pod가 자동으로 돌아오지는 않는다 — 이미 다른 노드에 있으니. 그냥 새 부하가 이 노드를 다시 쓰게 될 뿐.)

노드 자원 용량과 allocatable

노드는 Capacity(전체 자원)와 Allocatable(Kubernetes가 Pod에 줄 수 있는 자원)을 구분해 보고한다. Allocatable은 시스템 kubelet/OS 보유분을 뺀 양.

kubectl describe node k8s-study-worker | grep -A4 "Allocated resources"

확인할 것: requests 합이 allocatable을 넘으면 노드 과밀 → 압박/eviction 위험.

직접 확인하기

# Kubernetes 1.36 — cordon/drain/uncodon 전체 흐름
kubectl get nodes
kubectl cordon k8s-study-worker
kubectl drain k8s-study-worker --ignore-daemonsets --delete-emptydir-data --force
kubectl get pods -A -o wide --field-selector spec.nodeName=k8s-study-worker

확인할 것: drain 후 그 노드엔 (DaemonSet 제외) Pod가 없다.

# (실제 노드 재부팅 대신) uncordon으로 복구
kubectl uncordon k8s-study-worker
kubectl get node k8s-study-worker

확인할 것: SchedulingDisabled가 사라지고 정상 Ready.

--force는 PodDisruptionBudget/리소스 보호를 우회 — 실습에서만. 운영에선 빼고 PDB 존중.

graceful node shutdown — 전원 버튼보다 안전한 종료

노드를 재부팅할 때, 단순히 전원을 끄면 Pod가 강제로 죽으며 진행 중인 요청이 손실된다. graceful node shutdown(1.21+)이 kubelet이 종료 신호를 받으면 순서대로 Pod를 정리하게 한다:

flowchart LR
    SIG["종료 신호(systemd stop)"] --> KL["kubelet 종료 절차 시작"]
    KL --> PRI["priority 클래스별 순서 결정"]
    PRI --> STOP1["1. 일반 Pod 정상 종료 (기본 30초)"]
    STOP1 --> STOP2["2. critical Pod"]
    STOP2 --> DONE["노드 종료"]

kubelet config에서 종료 순서와 타임아웃을 설정:

# kubelet config
shutdownGracePeriod: 60s
shutdownGracePeriodCriticalPods: 20s

일반 Pod에 더 긴 시간을, critical Pod(예: monitoring)에 짧은 시간을. 이것이 "드레인 없이 재부팅" 시에도 우아하게 처리되게 한다 — systemd가 kubelet에 종료를 알리고, kubelet이 Pod를 순서대로 정리.

graceful shutdown이 없다면: 노드가 죽는 순간 실행 중인 요청이 잘린다. 데이터베이스 트랜잭션이 중간에 끊기고, 업로드가 실패하고, 연결이 RST. graceful shutdown이 이 "잘림"을 우아한 종료로 바꾼다.

Capacity vs Allocatable — 왜 차이가 나나

노드는 두 자원 값을 보고한다:

  • Capacity: 노드의 물리 전체 자원(CPU/메모리).
  • Allocatable: Kubernetes가 Pod에 줄 수 있는 자원 = Capacity − 시스템 보유(kube-reserved/system-reserved/eviction-threshold).
Capacity:      cpu 4000m, memory 16384Mi
Allocatable:   cpu 3800m, memory 15000Mi   ← 스케줄러가 보는 값

왜 차이를 두나? 노드 OS/kubelet 자체도 자원이 필요. Allocatable을 Capacity보다 낮춰 시스템 컴포넌트 여유를 보장. 그래야 Pod가 노드를 굶기지 않는다(kubelet 자체가 OOM으로 죽으면 노드 전체 장애).

kube-reserved(kubelet/컨테이너 런타임용), system-reserved(OS 데몬용)를 설정으로 조정. 이것을 안 하면 — Pod가 노드 자원을 전부 쓰려 해서 시스템 컴포넌트가 굶고, 노드가 불안정해진다. 대규모 운영에선 이 예약 설정이 필수.

kubelet의 주요 설정 — 동작을 결정하는 플래그들

kubelet config 파일(/var/lib/kubelet/config.yaml)이 노드 동작을 결정:

  • podPidsLimit: Pod당 최대 프로세스 수(PID pressure 방지).
  • imageGCHighThresholdPercent: 디스크가 이 임계를 넘으면 이미지 가비지 컬렉션 시작.
  • evictionHard: 강제 퇴거 임계값(memory.available < 100Mi 등, 11장).
  • maxPods: 노드당 최대 Pod 수(기본 110).

이 설정이 노드의 행동 한계를 정한다. 예: maxPods를 높이면 한 노드에 더 많은 Pod가 들어가지만 — IPAM 블록/CPU/메모리 한계에 부딪힐 수 있다. 설정 튜닝이 노드 용량·안정성의 관건.

흔히 묻는 것, 흔히 틀리는 것

오해 정정
"cordon은 Pod를 옮긴다" 안 옮김. 새 Pod만 차단. 옮기려면 drain
"drain은 그냥 Pod를 다 지운다" PDB를 존중하고 안전하게. 무차별 삭제와 다름
"PDB는 노드 장애도 막아준다" 자발적 장애(drain)만. 비자발적(노드 죽음)엔 무력
"uncordon하면 옮겨갔던 Pod가 돌아온다" 안 돌아옴. 이미 다른 노드에 있음. 새 부하가 그 노드를 쓸 뿐
"DiskPressure condition은 디스크가 꽉 찼다는 뜻" 거의 찼다는 뜻(임계값). 그래서 미리 퇴거 시작
"노드가 Unknown이면 바로 Pod를 옮긴다" 기본 40초 Unknown 후, 5분 경과해야 옮길 준비

요약 — 이 글의 결론

  • 노드 conditions(Ready/DiskPressure/MemoryPressure/PIDPressure/NetworkUnavailable)이 건강 표시. kubelet이 heartbeat로 보고, 멈추면 Node Controller가 Unknown 처리.
  • node-pressure taint가 압박 노드에 자동으로 붙어 새 Pod 차단 + 기존 Pod 퇴거. 시스템의 자기 보호.
  • cordon은 "이제부터 안 받겠다"(새 Pod 차단, 기존 유지). drain은 cordon + 기존 Pod 안전 퇴거(PDB 존중). 유지보수 전의 정답.
  • PodDisruptionBudget이 자발적 장애(drain) 시 최소 가용성을 보장. 단 비자발적(노드 죽음)엔 무력.
  • uncordon으로 복구하되, 옮겨갔던 Pod는 자동 복귀 안 함. 새 부하가 그 노드를 다시 쓰게 될 뿐.
  • Capacity vs Allocatable 구분이 자원 계산의 기본. requests 합이 allocatable을 넘으면 과밀 위험.

생각해 볼 문제

  1. cordon 없이 drain을 하면 어떤 일이 생기나? (사실 drain은 자동으로 cordon까지 함)
  2. drain 중인데 PDB 때문에 멈췄다. 어떻게 진행하겠는가? 강제 vs 대기의 trade-off.
  3. 노드가 6분간 heartbeat 안 보냈다. Node Controller는 어떤 일을 하는가? 시간순으로.
  4. DiskPressure condition이 True가 됐다. kubelet의 자동 동작 두 가지는?
  5. uncordon 후 "왜 Pod가 안 돌아오나요?"라는 질문에 "재스케줄되지 않는다"(04장)와 연결해 설명하라.
  6. 노드 Allocatable이 Capacity보다 작다. 그 차이는 무엇인가? 운영 의미는?

참고

'Tech Artifacts > Kubernetes' 카테고리의 다른 글

Kubernetes - 15. helm  (0) 2026.07.13
Kubernetes - 14. kubeadm  (0) 2026.07.13
Kubernetes - 12. autoscaling  (0) 2026.07.13
Kubernetes - 11. resource qos  (0) 2026.07.13
Kubernetes - 10. config secret  (0) 2026.07.13