Published on

EKS 노드그룹 업그레이드 드레인 멈춤 점검법

Authors

EKS Managed Node Group(이하 MNG) 업그레이드나 AMI 롤링 업데이트를 돌리면, 어느 순간부터 노드가 Draining 상태에서 멈춘 것처럼 보이는 경우가 있습니다. 실제로는 Kubernetes가 안전한 축출(eviction) 을 지키느라 멈춘 것이고, 그 “안전 조건”을 가장 자주 막는 게 PDB, DaemonSet, local PV(노드 로컬 디스크) 입니다.

이 글은 “왜 멈추는지”를 원인별로 분해하고, 무중단을 최대한 유지하면서 드레인을 끝내는 방법을 체크리스트 형태로 정리합니다.


드레인이 멈추는 메커니즘: EKS가 하는 일

MNG 업그레이드 시 EKS는 대체로 다음을 반복합니다.

  1. 새 노드(새 AMI/새 버전) 생성
  2. 기존 노드 cordon 처리(스케줄링 금지)
  3. 기존 노드 drain 수행(파드 축출)
  4. 기존 노드 종료

여기서 핵심은 drain이 단순 강제 종료가 아니라 Eviction API 를 통해 “지금 이 파드를 내보내도 되는가?”를 Kubernetes에 질의한다는 점입니다. 이때 Kubernetes가 PDB나 볼륨 제약 때문에 “안 된다”고 판단하면, 노드는 계속 드레인 중으로 남습니다.


1) 가장 흔한 원인: PDB가 축출을 막는다

증상

  • 특정 Deployment/StatefulSet 파드가 계속 노드에 남아 있음
  • 이벤트에 Cannot evict pod as it would violate the pod's disruption budget 류 메시지

빠른 진단 커맨드

# 드레인 중인 노드에서 남아있는 파드 확인
kubectl get pod -A -o wide --field-selector spec.nodeName=$NODE

# PDB 전체 현황(허용 가능한 축출 수가 0이면 위험 신호)
kubectl get pdb -A

# 특정 PDB 상세
kubectl describe pdb -n $NS $PDB

# 파드 이벤트에서 PDB 관련 에러 확인
kubectl describe pod -n $NS $POD | sed -n '1,200p'

kubectl get pdb 결과에서 특히 봐야 할 컬럼은 다음입니다.

  • MIN AVAILABLE 또는 MAX UNAVAILABLE
  • ALLOWED DISRUPTIONS (이 값이 0이면 eviction이 막힐 가능성이 큼)

왜 이런 일이 생기나

대표 패턴은 아래와 같습니다.

  • 레플리카가 1인데 PDB가 minAvailable: 1
  • 레플리카가 2인데 PDB가 minAvailable: 2
  • HPA가 축소해버려서 현재 레플리카 수가 줄었는데 PDB는 그대로

즉, PDB가 “항상 이만큼은 살아있어야 한다”고 말하는데, 현재 클러스터 상태가 그 조건을 만족할 여유가 없으면 드레인은 멈춥니다.

안전한 해소 방법(우선순위)

(1) 레플리카를 늘려 ALLOWED DISRUPTIONS 만들기

kubectl -n $NS scale deploy/$DEPLOY --replicas=3
kubectl -n $NS rollout status deploy/$DEPLOY
kubectl -n $NS get pdb

(2) 업그레이드 윈도우 동안만 PDB를 완화

예: minAvailable을 낮추거나 maxUnavailable을 늘립니다.

# 예시: maxUnavailable을 1로 설정(상황에 맞게 조정)
kubectl -n $NS patch pdb $PDB --type=merge -p '{"spec":{"maxUnavailable":1}}'

(3) 최후의 수단: PDB 삭제 후 복구

운영 리스크가 크므로, 반드시 변경 이력과 복구 계획을 함께 가져가야 합니다.

kubectl -n $NS get pdb $PDB -o yaml > /tmp/$PDB.yaml
kubectl -n $NS delete pdb $PDB

# 업그레이드/드레인 종료 후 복구
kubectl apply -f /tmp/$PDB.yaml

2) DaemonSet 파드는 기본적으로 드레인 대상이 아니다

증상

  • 드레인 중 노드에 DaemonSet 파드가 남아 있어도 정상처럼 보이기도 함
  • 하지만 “남아있는 파드가 있어서 드레인이 끝나지 않는다”고 오해하는 경우가 잦음

Kubernetes의 drain은 기본적으로 DaemonSet 파드를 무시합니다(옵션에 따라 다름). 다만, 다음 케이스는 드레인을 실제로 방해할 수 있습니다.

  • DaemonSet 파드가 emptyDir나 특정 마운트로 인해 종료가 지연
  • terminationGracePeriodSeconds가 과도하게 큼
  • DaemonSet이 아닌데 DaemonSet처럼 동작하도록 구성된 특수 워크로드

진단

# 해당 노드에서 DaemonSet 파드만 추려보기
kubectl get pod -A -o wide --field-selector spec.nodeName=$NODE \
  | grep -E 'daemonset|node-exporter|fluent|cni|aws'

# 파드가 어떤 컨트롤러 소유인지 확인
kubectl -n $NS get pod $POD -o jsonpath='{.metadata.ownerReferences[0].kind}{"\n"}'

해소 포인트

  • DaemonSet 자체는 “남아도 되는 파드”인 경우가 많으니, 드레인이 정말 막혔는지 먼저 확인합니다.
  • 드레인이 막혔다면, 이벤트/종료 지연 원인을 봐야 합니다.
kubectl -n $NS describe pod $POD | sed -n '1,220p'

그리고 정말로 업그레이드 윈도우 동안 DaemonSet을 내려야 한다면(예: 커스텀 에이전트가 종료를 방해) 다음처럼 nodeSelectortoleration을 조정해 대상 노드에서만 비활성화하는 방식이 일반적으로 더 안전합니다.


3) LocalPV(노드 로컬 볼륨)가 축출을 “불가능”하게 만든다

증상

  • StatefulSet 파드가 특정 노드에 강하게 고정
  • 다른 노드로 재스케줄이 안 되면서 eviction이 계속 실패
  • EBS 같은 네트워크 볼륨은 이동되는데, local PV는 이동이 안 됨

LocalPV는 말 그대로 “그 노드의 디스크”에 데이터가 있으므로, 파드를 다른 노드로 옮기면 데이터가 사라지거나(혹은 접근 불가) 애플리케이션이 깨집니다. 그래서 드레인 과정에서 매우 자주 병목이 됩니다.

진단: 남아있는 파드가 어떤 PV를 쓰는지 확인

# 파드가 마운트하는 PVC 이름 확인
kubectl -n $NS get pod $POD -o jsonpath='{.spec.volumes[*].persistentVolumeClaim.claimName}{"\n"}'

# PVC가 바인딩된 PV 확인
kubectl -n $NS get pvc $PVC -o wide

# PV의 타입/노드 어피니티 확인(local인지, 특정 노드에 묶였는지)
kubectl get pv $PV -o yaml | sed -n '1,260p'

PV 스펙에서 local: 이 보이거나, nodeAffinity로 특정 노드(또는 특정 라벨)로 강하게 묶여 있으면 LocalPV일 가능성이 큽니다.

해소 전략(데이터/서비스 특성에 따라 선택)

(1) 설계를 바꾸는 것이 정답인 경우

  • 데이터가 중요한 상태 저장 서비스라면, LocalPV 기반 단일 노드 고정은 업그레이드 자동화와 충돌합니다.
  • 가능하면 EBS CSI 같은 네트워크 스토리지로 전환하거나, 애플리케이션 레벨 복제(예: DB replica)로 노드 교체를 흡수하도록 설계합니다.

(2) 유지보수 윈도우에서 “의도된 다운타임”으로 처리

LocalPV 기반 StatefulSet을 가진 노드그룹은, 업그레이드 시 해당 워크로드를 계획적으로 중단하고 재기동하는 절차가 필요할 수 있습니다.

예시 흐름:

# 1) 트래픽 차단(서비스 라우팅/인그레스/배치 중단 등)
# 2) StatefulSet 스케일 다운
kubectl -n $NS scale statefulset/$STS --replicas=0

# 3) 노드 드레인 재시도(혹은 노드그룹 업그레이드 재개)
# 4) 업그레이드 후 스케일 업
kubectl -n $NS scale statefulset/$STS --replicas=1

(3) 노드그룹을 “분리”해서 업그레이드 blast radius 줄이기

  • LocalPV 워크로드 전용 노드그룹을 따로 두면, 다른 stateless 서비스 업그레이드가 LocalPV 때문에 멈추는 상황을 줄일 수 있습니다.

4) 드레인 멈춤을 빠르게 특정하는 실전 체크리스트

아래 순서대로 보면 대부분 원인이 10분 내에 좁혀집니다.

1) 해당 노드에 남아있는 파드 목록부터 확정

kubectl get pod -A -o wide --field-selector spec.nodeName=$NODE

2) 남은 파드가 어떤 컨트롤러 소유인지 분류

kubectl -n $NS get pod $POD -o jsonpath='{.metadata.ownerReferences[0].kind}{"/"}{.metadata.ownerReferences[0].name}{"\n"}'
  • DaemonSet이면 “원래 남아도 되는지”부터 확인
  • ReplicaSet/StatefulSet이면 PDB/PV 의심

3) 이벤트에서 eviction 실패 이유를 확인

kubectl -n $NS describe pod $POD | sed -n '1,260p'

4) PDB 확인

kubectl get pdb -A
kubectl -n $NS describe pdb $PDB

5) PVC/PV 확인(LocalPV 여부)

kubectl -n $NS get pvc
kubectl get pv

5) 업그레이드 전에 “드레인 멈춤”을 예방하는 설정 팁

PDB를 레플리카 수와 함께 설계하기

  • 레플리카가 1인 서비스에 minAvailable: 1은 사실상 “축출 금지”입니다.
  • 무중단이 정말 필요하면 레플리카를 늘리고, 그에 맞춰 PDB를 잡아야 합니다.

예시 PDB:

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: api-pdb
spec:
  maxUnavailable: 1
  selector:
    matchLabels:
      app: api

LocalPV 워크로드는 노드그룹을 분리

  • stateless 노드그룹 업그레이드가 LocalPV 때문에 멈추는 연쇄 장애를 줄입니다.

드레인 관찰을 자동화

  • 업그레이드 작업 중 “어떤 파드가 마지막까지 남는지”를 자동으로 수집해두면 다음 장애가 크게 줄어듭니다.
# 5초마다 남은 파드 스냅샷
watch -n 5 "kubectl get pod -A -o wide --field-selector spec.nodeName=$NODE"

6) 자주 나오는 오해 3가지

오해 1: 드레인이 멈췄으니 --force로 밀어버리면 된다

강제 삭제는 애플리케이션 데이터 손상, 리더 선출 꼬임, 트래픽 오류를 유발할 수 있습니다. 특히 Stateful 워크로드와 LocalPV는 강제 종료의 대가가 큽니다.

오해 2: DaemonSet 파드가 남아 있으니 드레인이 실패한 것이다

DaemonSet은 남아도 정상인 경우가 많습니다. “실제로 막는 파드”가 무엇인지 이벤트와 PDB로 확인해야 합니다.

오해 3: PDB는 무조건 넣을수록 좋다

PDB는 무중단을 돕지만, 레플리카/오토스케일/업그레이드 전략과 함께 설계되지 않으면 “업그레이드 불능 장치”가 됩니다.


마무리: 드레인이 멈춘 게 아니라, 안전장치가 작동한 것이다

EKS 노드그룹 업그레이드에서 드레인이 멈추는 상황은 대부분 Kubernetes가 PDB와 스토리지 제약을 지키느라 생깁니다. 해결의 핵심은 “강제로 밀기”가 아니라, 어떤 안전조건이 발목을 잡는지 빠르게 특정하고, 그 조건을 일시적으로 완화하거나(=PDB), 구조적으로 제거(=LocalPV 설계 변경/노드그룹 분리) 하는 것입니다.

관련 운영 이슈로 IAM/OIDC 설정이 꼬여 노드 교체 후 권한 문제가 같이 터지는 경우도 있어, IRSA가 의심되면 아래 글도 함께 점검해보면 좋습니다.

업그레이드가 자주 멈추는 워크로드(특정 네임스페이스/특정 StatefulSet)가 있다면, 댓글/후속 글 주제로 “현재 PDB, 레플리카, PV 타입” 조합을 기준으로 더 구체적인 처방전 형태로도 정리할 수 있습니다.