- Published on
EKS 노드 드레인 10분? PDB·Grace·CNI 튜닝
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버리스가 아닌 이상, EKS 운영에서 가장 자주 겪는 "느린 이벤트" 중 하나가 노드 드레인입니다. 노드 교체(Managed Node Group 업데이트), 스팟 중단, 커널 패치, 오토스케일러 스케일다운 같은 상황에서 kubectl drain 이 10분 이상 걸리면 배포/장애 대응 모두가 느려집니다.
이 글은 드레인 지연을 크게 세 갈래로 나눠 원인을 찾고(PDB, Grace, CNI), 재현 가능한 관측 지표와 함께 튜닝 포인트를 제시합니다. 결론부터 말하면, 대부분은 다음 중 하나입니다.
- PDB가 동시에 내려갈 수 있는 파드를 막아 Eviction이 대기 상태로 누적됨
terminationGracePeriodSeconds와 앱의 종료 훅이 길어 실제 종료가 늦음(또는 종료가 안 됨)- VPC CNI에서 IP 할당/회수, ENI detach, conntrack, 노드 네트워크 경로 이슈로 파드 종료가 지연됨
관련해서 EKS에서 원인 파악이 어려운 케이스는 EKS CrashLoopBackOff인데 로그가 0줄? 원인 8가지 도 함께 보면, "컨테이너는 죽었는데 왜 이벤트가 이상하지?" 같은 상황에서 로그/이벤트/프로브 관측이 정리됩니다.
1) 드레인 흐름을 정확히 이해하기
kubectl drain 은 노드를 cordon 한 뒤, 해당 노드의 파드를 Eviction API로 축출합니다. 이때 중요한 점은 삭제(delete pod)가 아니라 Eviction 이며, Eviction은 PDB 제약을 받습니다.
드레인이 오래 걸릴 때의 전형적인 이벤트는 다음 두 가지입니다.
- PDB 때문에 Eviction이 거부되어 재시도
- 파드는 Evict 되었지만 종료(graceful shutdown)가 오래 걸림
먼저 노드에서 무슨 파드가 발목을 잡는지부터 뽑습니다.
# 드레인 대상 노드의 파드 목록
kubectl get pod -A -o wide --field-selector spec.nodeName=$NODE
# 종료가 오래 걸리는 파드(TERM 이후 남아있는지)
kubectl get pod -A --field-selector spec.nodeName=$NODE \
-o jsonpath='{range .items[*]}{.metadata.namespace}{"\t"}{.metadata.name}{"\t"}{.status.phase}{"\t"}{.metadata.deletionTimestamp}{"\n"}{end}'
deletionTimestamp 가 찍혔는데 파드가 오래 남아 있으면, 거의 항상 Grace/종료 훅/프리스트롭/프로브 쪽입니다.
2) PDB: "정상 가용성"이 "드레인 지연"으로 바뀌는 순간
2-1. PDB가 드레인을 막는 메커니즘
PDB는 "동시에 내려가도 되는 파드 수"를 제한합니다. 드레인 과정에서 Eviction이 들어오면, PDB가 허용하지 않으면 Eviction이 실패하고 드레인은 계속 대기합니다.
PDB를 빠르게 점검합니다.
kubectl get pdb -A
kubectl describe pdb -n $NS $PDB
특히 다음 필드를 봅니다.
Allowed disruptions:0이면 현재는 하나도 내릴 수 없음Current/Desired/Total: 복제 수와 가용 조건이 맞는지
2-2. 흔한 실수 3가지
(1) 복제 수가 1인데 minAvailable: 1
이 조합은 사실상 "절대 드레인 불가" 입니다. 노드 교체나 스팟 중단에서 매번 걸립니다.
해결 방향은 둘 중 하나입니다.
- 복제 수를 늘려 PDB가 의미 있게 동작하게 만들기
- PDB를
maxUnavailable: 1로 바꾸거나, 유지가 필요 없다면 PDB 자체를 제거
예시:
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: api-pdb
spec:
maxUnavailable: 1
selector:
matchLabels:
app: api
(2) HPA가 minReplicas 를 낮게 잡아 PDB와 충돌
트래픽이 낮을 때 HPA가 replicas=1 로 내려가면, PDB가 minAvailable: 2 같은 형태일 때 드레인이 막힙니다. PDB는 "희망"이 아니라 "강제" 입니다.
해결:
- HPA
minReplicas를 PDB와 정합되게 올리기 - 또는 PDB 정책을 현실적인 값으로 조정
(3) 노드가 충분하지 않아 스케줄링이 못 따라옴
PDB는 Eviction을 허용하더라도, 새 파드가 다른 노드에 떠야 전체 가용성이 회복됩니다. 클러스터에 여유가 없으면 새 파드가 Pending 으로 남고, 결과적으로 연쇄적으로 드레인이 느려집니다.
이 경우는 PDB 자체가 문제가 아니라 클러스터 용량/리소스 요청 이 문제입니다.
kubectl get pod -A | grep Pending
kubectl describe pod -n $NS $POD | sed -n '1,120p'
Insufficient cpu 나 Insufficient memory 가 보이면 노드 수/인스턴스 타입/리소스 요청을 재조정해야 합니다.
3) Grace: terminationGracePeriodSeconds 와 종료 훅이 10분을 만든다
3-1. 숫자만 줄인다고 해결되지 않는 이유
파드 종료는 대략 다음 순서로 진행됩니다.
preStop훅 실행- 컨테이너에
SIGTERM전달 - 앱이 종료를 마치면 컨테이너 종료
terminationGracePeriodSeconds가 끝나면 강제 종료
따라서 terminationGracePeriodSeconds: 600 같은 값이 있으면, 앱이 종료 신호를 무시하거나 종료가 막히는 순간 드레인은 "최대 10분"으로 늘어납니다.
종료 설정을 확인합니다.
kubectl get deploy -n $NS $DEPLOY -o yaml | sed -n '1,220p'
3-2. 드레인 지연을 만드는 대표 패턴
(1) preStop 에서 외부 의존 호출
예: 외부 API 호출, DB 플러시, S3 업로드, 메시지 큐 drain 등을 preStop 에 넣으면 네트워크 이슈가 있을 때 종료가 무한정 늘어납니다.
preStop 은 "최후"가 아니라 "최소"만 해야 합니다.
- 헬스 체크/리드니스 제거 같은 로컬 작업
- 짧은 sleep(필요할 때만)
(2) 앱이 SIGTERM 을 처리하지 않음
Node.js, Java, Go, Python 모두 graceful shutdown 구현이 필요합니다.
예시(Go):
srv := &http.Server{Addr: ":8080"}
go func() {
_ = srv.ListenAndServe()
}()
stop := make(chan os.Signal, 1)
signal.Notify(stop, syscall.SIGTERM, syscall.SIGINT)
<-stop
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
_ = srv.Shutdown(ctx)
이런 처리가 없으면, 커넥션이 남아 있거나 워커가 블로킹되면서 종료가 지연됩니다.
(3) readiness/liveness 설계가 종료와 충돌
종료 직전에 readiness가 계속 true 면, 서비스가 트래픽을 계속 보내고 커넥션이 끊기지 않아 종료가 늦습니다.
권장 패턴:
- readiness는 "트래픽 받을 준비"만 표현
- 종료 시에는 readiness를 빠르게
false로 만들기(예: 종료 플래그 파일, 엔드포인트 상태 변경)
4) CNI/VPC IP: 파드는 내려갔는데 왜 노드가 안 비나
EKS 기본인 AWS VPC CNI는 파드에 VPC IP를 붙입니다. 이 구조는 성능/가시성 장점이 있지만, 드레인 시점에 다음 문제가 겹치면 지연이 커집니다.
- IP 회수 지연으로 새 파드가 IP를 못 받아
Pending - ENI/IP가 부족해 다른 노드로 재스케줄이 막힘
- 노드 네트워크 경로(NAT, 보안그룹, DNS) 문제가 종료 훅/외부 통신을 지연시킴
특히 네트워크 경로 문제는 드레인뿐 아니라 STS, 이미지 풀, 외부 API 등 전반을 흔듭니다. 프라이빗 서브넷에서 STS/엔드포인트/DNS가 꼬인 케이스는 EKS Pod STS AssumeRole 타임아웃 - NAT·PrivateLink·DNS 가 직접적인 힌트가 됩니다.
4-1. IP 부족 여부부터 확인
노드에 파드가 재스케줄링되지 않고 Pending 이면, 이벤트에 Insufficient pods 또는 CNI 관련 메시지가 뜨는 경우가 많습니다.
kubectl describe pod -n $NS $POD | sed -n '1,200p'
또한 aws-node 로그에서 IP 할당 실패를 확인합니다.
kubectl logs -n kube-system -l k8s-app=aws-node --tail=200
4-2. VPC CNI 튜닝 포인트
아래는 환경에 따라 효과가 큰 항목들입니다.
(1) WARM_IP_TARGET / MINIMUM_IP_TARGET
파드가 급히 이동할 때(드레인, 롤링 업데이트) 노드에 "대기 IP"가 없으면 CNI가 IP를 추가로 붙이는 동안 파드가 Pending 이 될 수 있습니다.
WARM_IP_TARGET: 미리 확보해둘 IP 개수MINIMUM_IP_TARGET: 최소 확보 IP 개수
예시(환경에 맞게 조정):
kubectl set env daemonset aws-node -n kube-system \
WARM_IP_TARGET=5 MINIMUM_IP_TARGET=5
(2) Prefix Delegation 사용 검토
서브넷 IP 압박이 크거나 파드 밀도가 높으면 Prefix Delegation이 도움이 됩니다(지원 인스턴스/버전 조건 확인 필요). 이는 노드가 개별 IP가 아니라 프리픽스를 받아 더 효율적으로 관리하는 방식입니다.
운영 적용 전에는 반드시 테스트 클러스터에서 파드 밀도, 스케일링, 드레인 시간을 비교하세요.
(3) maxPods 와 인스턴스 타입 정합
인스턴스 타입별 ENI/IP 한계가 있는데, maxPods 를 과하게 잡으면 스케줄러는 넣으려 하고 CNI는 IP를 못 만들어서 Pending 이 발생합니다.
- Managed Node Group은 AMI/부트스트랩에서
--max-pods설정이 들어가기도 함 - 커스텀 AMI라면 kubelet 설정을 확인
5) 드레인 시간 단축을 위한 실전 체크리스트
5-1. 관측: "어디서" 멈추는지 3분 안에 분류
- Eviction이 막히는지(PDB)
kubectl get events -A --sort-by=.lastTimestamp | tail -n 50
이벤트에 Cannot evict pod as it would violate the pod's disruption budget 류가 보이면 PDB입니다.
deletionTimestamp이후 오래 남는지(Grace/훅)
kubectl get pod -A -o wide --field-selector spec.nodeName=$NODE
- 재스케줄 파드가
Pending인지(CNI/IP/용량)
kubectl get pod -A | grep Pending
5-2. 정책: 드레인/업데이트 윈도우에 맞는 값으로
- PDB는 "장애"가 아니라 "운영 이벤트"(노드 교체)도 고려해 설계
- 복제 수
1서비스는 PDB를 강하게 걸면 운영이 멈춤 terminationGracePeriodSeconds는 앱 종료 로직과 함께 설계(숫자만 조정 금지)
5-3. 명령어: 드레인 옵션은 "임시 응급"으로만
드레인 시 다음 옵션으로 시간을 줄일 수 있지만, 이는 근본 해결이 아닙니다.
kubectl drain $NODE \
--ignore-daemonsets \
--delete-emptydir-data \
--grace-period=30 \
--timeout=5m
--grace-period를 낮추면 데이터 손상/요청 유실 위험이 커질 수 있습니다.- PDB 위반을 강제로 뚫는 옵션을 습관적으로 쓰면, PDB가 존재하는 의미가 사라집니다.
6) 예시 시나리오: "10분 드레인"을 2분대로 줄이는 접근
다음은 현장에서 자주 보는 조합입니다.
terminationGracePeriodSeconds: 600preStop에서 외부 API 호출 + 30초 sleep- PDB가
minAvailable: 1인데 레플리카가 1
개선 순서(리스크 낮은 것부터):
- PDB를
maxUnavailable: 1로 변경하거나 레플리카를 2로 올림 - 종료 시 readiness를 즉시
false로 만들고,preStop외부 호출 제거 - 앱에서
SIGTERMgraceful shutdown을 15초 내로 수렴 terminationGracePeriodSeconds를 30초 또는 60초로 조정- 드레인 중
Pending발생 시 CNI warm IP와 노드 용량을 함께 조정
이렇게 하면 드레인 시간이 "PDB 대기 + Grace 최대치"에서 "실제 종료 시간" 중심으로 내려옵니다.
7) 마무리: 드레인은 쿠버네티스의 건강검진이다
드레인이 10분 걸린다는 건, 단지 업데이트가 느리다는 문제가 아니라 다음 신호일 가능성이 큽니다.
- PDB/레플리카/오토스케일 정책이 현실 운영 이벤트를 반영하지 못함
- 앱이 종료를 제대로 처리하지 못해 장애 시에도 복구가 느려질 수 있음
- CNI/IP/네트워크 경로가 불안정해 스케줄링과 외부 의존이 흔들림
위의 순서대로 "PDB 이벤트"와 "Grace 잔류"와 "Pending" 을 분리해서 보면, 대부분의 케이스는 30분 내에 원인 범주가 좁혀지고, 재발 방지까지 이어집니다.