- Published on
EKS 노드 드레인 무한대기 원인 7가지와 해결
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
EKS에서 노드를 교체(업그레이드, 스케일인, Spot 중단, AMI 패치)할 때 kubectl drain 또는 관리형 노드그룹 롤링 업데이트가 “끝나지 않는” 상황을 자주 만납니다. 표면적으로는 파드가 내려가지 않는 문제처럼 보이지만, 실제 원인은 PDB, 로컬 스토리지, DaemonSet, 종료 지연, 스케줄링 불가, 볼륨 detach 지연, 컨트롤러/오토스케일러 상호작용까지 다양합니다.
이 글은 “왜 무한 대기처럼 보이는지”를 **관측 포인트(어디를 봐야 하는지)**부터 즉시 적용 가능한 커맨드와 근본 해결까지 7가지로 정리합니다. EKS 장애 진단 흐름은 EKS에서 503 Service Unavailable 원인 10분 진단 글의 “증상-관측-가설-검증” 접근과 동일하게 가져가면 훨씬 빨리 좁힐 수 있습니다.
드레인이 멈췄는지 먼저 확인하기
드레인은 보통 다음 단계에서 멈춰 보입니다.
- 노드
cordon성공 - 파드 eviction 시작
- 일부 파드가 Pending으로 재스케줄 실패 또는 Terminating에서 멈춤
- 노드에 파드가 남아 drain 종료 불가
아래 커맨드로 “어떤 파드가 남아 있는지”부터 확정합니다.
# 드레인 대상 노드에 남아 있는 파드
NODE_NAME=ip-10-0-12-34.ap-northeast-2.compute.internal
kubectl get pod -A -o wide --field-selector spec.nodeName=$NODE_NAME
# 드레인 이벤트(특히 eviction 실패 사유)
kubectl get events -A --sort-by=.lastTimestamp | tail -n 50
# 특정 파드가 왜 안 내려가는지
kubectl describe pod -n <NAMESPACE> <POD_NAME>
주의: 본문에서 <NAMESPACE>, <POD_NAME> 같은 표기는 MDX에서 JSX로 오인될 수 있으니 실제 문서/노트에도 인라인 코드로 유지하는 습관이 안전합니다.
원인 1) PDB로 인해 eviction이 차단됨
전형적 증상
kubectl drain출력에Cannot evict pod as it would violate the pod's disruption budget유사 메시지- 이벤트에
Eviction blocked by PDB계열 사유 - 레플리카 수가 작거나(1개) 가용 영역 분산이 안 된 워크로드에서 자주 발생
확인
# 전체 PDB 확인
kubectl get pdb -A
# 특정 네임스페이스 PDB 상세
kubectl describe pdb -n <NAMESPACE> <PDB_NAME>
# PDB가 보호하는 파드 수/허용 disruption 확인
kubectl get pdb -n <NAMESPACE> <PDB_NAME> -o yaml
해결
- 일시 조치(긴급 드레인): PDB의
minAvailable을 낮추거나maxUnavailable을 늘려 eviction을 허용 - 근본 조치: 레플리카를 늘리고,
topologySpreadConstraints또는podAntiAffinity로 단일 노드/단일 AZ 쏠림을 방지
예: maxUnavailable로 운영 친화적으로 변경
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: api-pdb
namespace: myns
spec:
maxUnavailable: 1
selector:
matchLabels:
app: api
운영 팁: PDB는 “안전장치”지만, 노드 교체를 정기적으로 하는 클러스터라면 **드레인 가능한 여유(레플리카, AZ 분산)**를 설계에 포함해야 합니다.
원인 2) DaemonSet 파드가 남아 있다고 착각하거나, 예외 없는 드레인 옵션 사용
전형적 증상
- 노드에
aws-node,kube-proxy,ebs-csi-node같은 DaemonSet 파드가 남아 drain이 끝나지 않는 것처럼 보임 - 또는 DaemonSet을 무시하지 않아 드레인이 실패
확인
kubectl get pod -A -o wide --field-selector spec.nodeName=$NODE_NAME
kubectl get ds -A
해결
대부분의 경우 DaemonSet은 드레인 시 무시해야 합니다.
kubectl drain $NODE_NAME \
--ignore-daemonsets \
--delete-emptydir-data \
--grace-period=60 \
--timeout=10m
--ignore-daemonsets: 필수--delete-emptydir-data:emptyDir데이터 삭제를 허용(원인 3과 연결)--timeout: “무한 대기처럼 보이는 상황”을 운영적으로 끊어낼 수 있음
주의: --force는 마지막 수단입니다. 특히 싱글톤 파드/상태ful 파드에 무분별 적용하면 가용성/데이터 손상 리스크가 커집니다.
원인 3) emptyDir/로컬 디스크 의존 파드가 eviction에서 멈춤
전형적 증상
- 드레인 로그에
cannot delete Pods with local storage유사 메시지 - 빌드 캐시, 임시 파일, 모델 다운로드 캐시를
emptyDir에 크게 쌓는 워크로드에서 자주 발생
확인
kubectl get pod -n <NAMESPACE> <POD_NAME> -o jsonpath='{.spec.volumes}'
해결
- 드레인 커맨드에
--delete-emptydir-data포함 - 근본적으로는
emptyDir에 중요한 상태를 두지 말고, 필요 시 EBS/EFS 같은 퍼시스턴트 볼륨 또는 외부 캐시로 이동
예: 임시 캐시는 유지하되 재시작 가능하게 설계
- 캐시 미스 비용이 큰 경우: S3 프리페치, 이미지 레이어 캐시, 애플리케이션 레벨 캐시(예: Redis)로 대체
원인 4) 파드가 Terminating에서 멈춤: 종료 훅, finalizer, 사이드카, SIGTERM 미처리
전형적 증상
- 파드가
Terminating상태로 오래 지속 preStop훅이 오래 걸리거나 무한 대기- 애플리케이션이 SIGTERM을 무시하거나, 사이드카(프록시/로그 에이전트)가 종료를 막음
- 리소스 부족/노드 네트워크 이슈로 종료 API 호출이 지연
확인
kubectl describe pod -n <NAMESPACE> <POD_NAME>
# 종료 유예 시간
kubectl get pod -n <NAMESPACE> <POD_NAME> -o jsonpath='{.spec.terminationGracePeriodSeconds}'
# finalizers 존재 여부
kubectl get pod -n <NAMESPACE> <POD_NAME> -o jsonpath='{.metadata.finalizers}'
해결
terminationGracePeriodSeconds를 현실적인 값으로 조정(예: 30~90초)preStop훅은 짧고 실패 가능하게(타임아웃 포함) 작성- 애플리케이션에서 SIGTERM 처리(HTTP 서버 graceful shutdown, 워커 drain) 구현
Node drain과 유사하게 “종료가 안 되는 이유를 7가지로 쪼개” 접근하는 방식은 systemd 서비스가 자동재시작 안 될 때 7가지 원인에서 다룬 것과 동일한 진단 습관입니다.
긴급 조치(권장도 낮음): finalizer가 잘못 남아있는 리소스는 원인을 찾아 제거해야 하지만, 운영 중 막혀서 복구가 우선이면 아래처럼 패치로 제거하는 경우도 있습니다.
kubectl patch pod -n <NAMESPACE> <POD_NAME> -p '{"metadata":{"finalizers":[]}}' --type=merge
주의: finalizer 제거는 컨트롤러가 수행해야 할 정리 작업을 건너뛸 수 있으니, 사후 원인 분석과 재발 방지가 필수입니다.
원인 5) 재스케줄이 안 되어 Pending이 지속: 용량 부족, (Anti)Affinity, 토폴로지 제약
전형적 증상
- 드레인으로 파드는 내려갔지만, 대체 파드가 다른 노드에 올라가지 못해 PDB가 계속 막힘
- 이벤트에
0/NN nodes are available및Insufficient cpu/Insufficient memory/node(s) didn't match Pod's node affinity
확인
kubectl get pod -n <NAMESPACE> <POD_NAME> -o wide
kubectl describe pod -n <NAMESPACE> <POD_NAME>
# 노드 리소스 현황
kubectl top nodes
kubectl describe node $NODE_NAME
해결
- 클러스터 용량 확보: 노드그룹 스케일아웃, Cluster Autoscaler 또는 Karpenter 설정 점검
- 스케줄 제약 완화: 과도한
requiredDuringSchedulingIgnoredDuringExecutionaffinity, 빡빡한topologySpreadConstraints재검토 - 요청 리소스 재조정: 실제 사용량 대비 과도한
requests로 인해 스케줄 불가가 발생하는지 점검
실무 팁: “드레인이 안 된다”의 절반은 사실 “내릴 수는 있는데 다시 올릴 곳이 없다”입니다. 드레인 전에 해당 워크로드가 다른 노드에서 스케줄 가능한지를 먼저 확인하면 야간 장애를 크게 줄일 수 있습니다.
원인 6) EBS/PV detach 지연 또는 CSI 드라이버 이슈로 종료가 지연
전형적 증상
- StatefulSet/Deployment가 PVC를 사용
- 파드 종료 후 새 파드가 다른 노드에 붙지 못하고
Multi-Attach error또는AttachVolume.Attach failed이벤트 - 노드가 내려가면서 볼륨 detach가 늦어져 전체 드레인이 길어짐
확인
# PVC/PV 상태
kubectl get pvc -A
kubectl get pv
# 볼륨 attach/detach 이벤트
kubectl get events -A --sort-by=.lastTimestamp | grep -i -E 'attach|detach|multi-attach|volume'
# EBS CSI 관련 파드 상태
kubectl get pod -n kube-system | grep -i ebs
해결
- EBS CSI 드라이버 버전/권한(IRSA) 점검
- 단일 AZ EBS를 쓰는 워크로드는 노드가 다른 AZ로 이동하지 않도록
nodeAffinity또는 StorageClass의volumeBindingMode: WaitForFirstConsumer를 올바르게 사용 terminationGracePeriodSeconds와 함께 애플리케이션의 종료 시간을 줄여 “볼륨 정리”가 빨리 시작되게 유도
운영 팁: PV 기반 워크로드는 드레인 전에 “대체 노드가 같은 AZ에 존재하는지”가 핵심입니다. 없으면 드레인이 아니라 설계 제약입니다.
원인 7) 노드 종료/드레인과 오토스케일러·업데이트 전략이 충돌
전형적 증상
- 관리형 노드그룹 업데이트가 반복 재시도하며 오래 걸림
- Cluster Autoscaler가 노드 축소를 시도하는데 PDB/스케줄 제약 때문에 계속 실패
- Karpenter가 노드를 교체하려는데
consolidation/ttlSecondsAfterEmpty정책과 워크로드 제약이 충돌
확인
# 노드가 왜 삭제/유지되는지 힌트는 이벤트에 자주 남음
kubectl get events -A --sort-by=.lastTimestamp | tail -n 200
# Cluster Autoscaler 로그(설치된 경우)
kubectl -n kube-system logs deploy/cluster-autoscaler | tail -n 200
# Karpenter 로그(설치된 경우)
kubectl -n karpenter logs deploy/karpenter | tail -n 200
해결
- 노드그룹 롤링 업데이트 시
maxUnavailable/maxSurge(또는 EKS 콘솔의 업데이트 설정)로 한 번에 빠지는 노드 수를 제한 - 오토스케일러의 축소 조건을 보수적으로 조정(특히 PDB 많은 환경)
- “업데이트 윈도우”에는 축소를 잠깐 꺼서 드레인과 스케일인이 싸우지 않게 만들기
이 이슈는 단일 원인이라기보다 “컨트롤 루프끼리의 경쟁”입니다. 증상이 복잡하게 보일 때는 KServe vLLM 배포 503·HPA 미작동 원인 7가지처럼 컨트롤러/오토스케일링 관점에서 로그와 이벤트를 분리해 보면 원인 분해가 쉬워집니다.
실전용 체크리스트: 10분 안에 좁히는 순서
- 드레인 대상 노드에 남은 파드 목록 확보
kubectl get pod -A -o wide --field-selector spec.nodeName=...
- 남은 파드가 DaemonSet인지 확인
- DaemonSet이면
--ignore-daemonsets적용 여부 점검
- DaemonSet이면
- 이벤트에서 eviction 실패 사유 확인
- PDB 위반, 로컬 스토리지, 종료 지연, 볼륨 attach/detach
- Terminating 파드
describe로 finalizer/훅/유예시간 확인 - Pending 파드가 있다면 스케줄 불가 원인 확인
- 리소스 부족, affinity, 토폴로지
- PVC 사용 워크로드면 attach/detach 이벤트 확인
- 마지막으로 오토스케일러/노드그룹 업데이트 정책 충돌 확인
부록: 드레인 명령 템플릿(운영 안전 버전)
NODE_NAME=ip-10-0-12-34.ap-northeast-2.compute.internal
kubectl drain $NODE_NAME \
--ignore-daemonsets \
--delete-emptydir-data \
--grace-period=60 \
--timeout=10m
# 드레인 후 노드 삭제(관리형 노드그룹/ASG 환경에 따라 방식 다름)
# kubectl delete node $NODE_NAME
--timeout을 두면 “무한 대기”가 아니라 “타임아웃으로 실패”가 되어, 실패 원인을 이벤트/로그로 다시 추적하기 쉬워집니다.
마무리
EKS 노드 드레인이 무한 대기처럼 보일 때는 “드레인 커맨드가 멈췄다”가 아니라, 대개 eviction이 정책적으로 막혔거나(PDB), 종료가 애플리케이션/스토리지 레벨에서 지연되었거나, 재스케줄이 물리적으로 불가능한 상태입니다. 위 7가지를 순서대로 체크하면 원인을 빠르게 분해할 수 있고, 임시 조치와 근본 조치를 분리해서 적용할 수 있습니다.
원하시면 사용 중인 구성(Managed Node Group인지, Karpenter/Cluster Autoscaler 사용 여부, PVC 종류, 문제 파드 describe 출력 일부)을 기준으로 “당신의 케이스에서 가장 가능성 높은 1~2개 원인”을 먼저 찍어 진단 플로우로 정리해드릴 수 있습니다.