- Published on
EKS 노드 드레인 멈춤 해결 - PDB·DaemonSet·IRSA
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
EKS에서 노드를 교체하거나(Managed Node Group 롤링 업데이트, AMI 패치, 스케일 인/아웃), 장애 조치를 위해 kubectl drain을 실행했는데 드레인이 끝나지 않고 멈춘 것처럼 보이는 상황이 자주 발생합니다. 콘솔/오토스케일러는 노드를 내리고 싶어 하는데, Kubernetes는 “내보낼 수 없는 파드가 있다”고 판단해 퇴거(eviction)를 계속 재시도합니다.
이 글은 드레인이 멈출 때 거의 항상 등장하는 3대 축인 PDB(파드 중단 예산), DaemonSet(데몬 파드), **IRSA(서비스어카운트 IAM 역할)**를 중심으로, 어떤 신호를 보면 어디부터 봐야 하는지와 실무에서 안전하게 풀어내는 순서를 제공합니다.
관련해서 파드가 계속 재시작하거나 준비 상태가 안 올라와 퇴거가 지연되는 케이스는 아래 글도 함께 보면 원인 추적에 도움이 됩니다.
드레인이 “멈춘 것처럼” 보이는 메커니즘
kubectl drain은 기본적으로 다음을 수행합니다.
- 노드를
cordon해서 새 파드 스케줄링을 막음 - 노드 위 파드들을 eviction API로 퇴거 시도
- 퇴거가 안 되는 파드가 있으면 계속 대기/재시도
여기서 퇴거가 막히는 대표 이유가 아래입니다.
- PDB가 허용하는
disruptionsAllowed가0 - DaemonSet 파드는 기본 옵션으로는 삭제 대상이 아님
emptyDir가 있는 파드가 있고--delete-emptydir-data를 안 줌- 종료가 너무 느림(
terminationGracePeriodSeconds, preStop 훅, 외부 의존성) - 새 노드로 옮겨가야 하는데 새 파드가 뜨지 못함(이미지 풀, CNI, IRSA, 권한 등)
이 글은 그중에서도 실제로 “드레인 단계에서 멈춤”을 유발하는 빈도가 높은 PDB·DaemonSet·IRSA에 집중합니다.
0) 먼저 상태를 한 번에 수집하는 진단 세트
드레인이 걸린 노드를 NODE로 두고, 아래를 순서대로 확인하면 원인 범위를 빠르게 좁힐 수 있습니다.
NODE=my-node-name
# 1) 노드에 어떤 파드가 얹혀 있는지
kubectl get pod -A -o wide --field-selector spec.nodeName=$NODE
# 2) 드레인 이벤트/퇴거 실패 이벤트
kubectl get events -A --sort-by=.lastTimestamp | tail -n 50
# 3) PDB가 막고 있는지(Allowed disruptions)
kubectl get pdb -A
# 4) 노드에 남아있는 파드 중 DaemonSet 여부 확인
kubectl get pod -A -o jsonpath='{range .items[?(@.spec.nodeName=="'$NODE'")]}{.metadata.namespace}{"/"}{.metadata.name}{"\t"}{.metadata.ownerReferences[0].kind}{"\n"}{end}'
이때 이벤트에 아래 같은 문구가 보이면 거의 확정입니다.
- PDB:
Cannot evict pod as it would violate the pod's disruption budget. - DaemonSet:
Cannot delete DaemonSet-managed Pods또는 drain이 DaemonSet 파드에서 멈춤 - IRSA/권한: 드레인은 진행되는데 새 노드에서 파드가 못 떠서(스케줄은 됐지만 준비 실패) 전체 롤링이 지연
1) PDB 때문에 eviction이 막히는 경우
1-1. PDB가 드레인을 막는 원리
PDB는 “자발적 중단(voluntary disruption)” 상황에서 최소 가용성을 보장합니다. 노드 드레인은 자발적 중단에 해당하므로, PDB가 disruptionsAllowed=0이면 eviction이 거부됩니다.
특히 다음 조건에서 자주 0이 됩니다.
minAvailable을 너무 공격적으로 설정(예: 레플리카 1인데minAvailable: 1)- 레플리카는 충분한데, 실제 Ready 파드 수가 부족(이미지 풀 실패, Readiness 미통과)
maxUnavailable: 0또는 사실상 동일한 효과
1-2. PDB 상태 확인
# 전체 PDB를 한눈에
kubectl get pdb -A
# 특정 네임스페이스만
kubectl get pdb -n my-ns
# 자세히(Allowed disruptions, selector 확인)
kubectl describe pdb -n my-ns my-pdb
kubectl describe pdb에서 아래 필드를 봅니다.
Allowed disruptions:값Current:/Desired:/Total:Selector:가 실제 워크로드 라벨과 맞는지
1-3. 해결 전략(안전 순서)
A. “Ready 파드 수가 부족해서” PDB가 0인 경우
이 경우 PDB를 건드리기보다 왜 Ready가 부족한지부터 고치는 게 안전합니다.
- 새 노드에서 파드가 Pending/ContainerCreating이면 이미지, CNI, 스토리지, IRSA 등을 점검
- ReadinessProbe 실패면 애플리케이션/의존성(DB, 외부 API) 문제를 점검
B. 레플리카가 부족한 경우: 먼저 스케일 아웃
kubectl -n my-ns scale deploy my-app --replicas=4
kubectl -n my-ns rollout status deploy my-app
Ready가 충분히 확보되면 disruptionsAllowed가 늘어나고 드레인이 풀립니다.
C. 정말로 유지보수 창에서 PDB를 완화해야 하는 경우
운영 정책상 허용된다면 일시적으로 PDB를 완화합니다.
예시: minAvailable: 2를 minAvailable: 1로 낮추거나, maxUnavailable을 조정합니다.
kubectl -n my-ns edit pdb my-pdb
변경 후 반드시 원복 계획을 남기세요. PDB는 장애 시 “최후의 안전장치”이므로, 완화 상태가 장기 유지되면 실제 장애에서 더 큰 다운타임을 유발합니다.
2) DaemonSet 파드가 남아서 드레인이 끝나지 않는 경우
2-1. DaemonSet은 기본적으로 drain 대상이 아님
노드마다 1개씩 떠야 하는 DaemonSet(예: aws-node, kube-proxy, 로깅/모니터링 에이전트)은 노드가 사라지기 전까지 남아있는 것이 정상입니다. 그래서 kubectl drain은 기본적으로 DaemonSet 파드를 “삭제하지 않고 무시”하도록 옵션을 줘야 합니다.
가장 흔한 실수는 --ignore-daemonsets 없이 drain을 실행하는 것입니다.
2-2. 권장 drain 명령(실무 기본값)
kubectl drain $NODE \
--ignore-daemonsets \
--delete-emptydir-data \
--grace-period=60 \
--timeout=10m
--ignore-daemonsets: DaemonSet 파드를 무시--delete-emptydir-data:emptyDir데이터 삭제를 허용(필요 시)--grace-period: 종료 유예시간을 제한(정책에 맞춰 조정)--timeout: 무한 대기 방지
2-3. DaemonSet이 “무시됐는데도” 멈추는 케이스
- DaemonSet이 아닌데도 owner가 없는 “고아 파드”가 남아있음
- Job/Pod를 직접 띄워 놓고 컨트롤러가 관리하지 않음
- 파드에
finalizers가 걸려 삭제가 안 됨
확인은 아래처럼 합니다.
kubectl get pod -A -o wide --field-selector spec.nodeName=$NODE
# 특정 파드가 왜 안 지워지는지
kubectl -n my-ns describe pod my-pod
# finalizers 확인
kubectl -n my-ns get pod my-pod -o jsonpath='{.metadata.finalizers}'
finalizers가 원인이라면, 해당 리소스를 만든 오퍼레이터/컨트롤러가 정상 동작하는지부터 확인하고(예: 외부 리소스 정리 실패), 정말 불가피한 경우에만 finalizer 제거를 고려합니다.
3) IRSA 문제로 “새 노드에서 파드가 못 떠서” 드레인이 지연되는 경우
엄밀히 말하면 IRSA는 eviction 자체를 직접 막기보다는, 퇴거된 파드가 새 노드에서 정상 기동/Ready가 되지 못해 전체 롤링(노드 교체)이 길어지면서 “드레인이 멈춘 것처럼” 보이게 만드는 경우가 많습니다.
특히 EKS에서 노드 그룹 교체 중 다음 패턴이 자주 나옵니다.
- 기존 노드에서 파드를 내보냄
- 새 노드에 파드가 스케줄됨
- 파드가 AWS API 호출(예: S3, SQS, STS)에서
AccessDenied또는InvalidIdentityToken으로 실패 - 파드가 CrashLoop 또는 Readiness 실패
- PDB가
disruptionsAllowed=0으로 다시 잠김(Ready 수 감소) - 결과적으로 drain/교체가 진행되지 않음
3-1. IRSA 이상 징후(파드 로그/이벤트)
아래 에러가 보이면 IRSA를 의심합니다.
AccessDenied: Not authorized to perform sts:AssumeRoleWithWebIdentityInvalidIdentityTokenNoCredentialProviders
확인 명령:
# 파드 이벤트
kubectl -n my-ns describe pod my-pod
# 앱 로그
kubectl -n my-ns logs my-pod --tail=200
# 서비스어카운트와 어노테이션 확인
kubectl -n my-ns get sa my-sa -o yaml
서비스어카운트에 아래 어노테이션이 있어야 합니다.
eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/my-irsa-role
MDX 렌더링 환경에서 부등호를 피하기 위해 ARN/키는 그대로 두되, 문서 내에서 꺾쇠가 들어가는 예시는 백틱으로 감싸는 습관을 권장합니다.
3-2. 신뢰 정책(trust policy)에서 가장 많이 틀리는 지점
IRSA는 IAM Role의 trust policy에서 OIDC provider와 sub 조건이 정확해야 합니다. 흔한 실수:
- 클러스터 OIDC issuer가 다른데 예전 값을 사용
- 네임스페이스/서비스어카운트명이 바뀌었는데
sub를 수정하지 않음
sub는 보통 아래 형태입니다.
system:serviceaccount:my-ns:my-sa
예시 trust policy(개념 예시):
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::123456789012:oidc-provider/oidc.eks.ap-northeast-2.amazonaws.com/id/EXAMPLED539D4633E53DE1B716D3041E"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"oidc.eks.ap-northeast-2.amazonaws.com/id/EXAMPLED539D4633E53DE1B716D3041E:sub": "system:serviceaccount:my-ns:my-sa"
}
}
}
]
}
여기서 issuer URL과 sub가 실제 클러스터/SA와 1글자라도 다르면 새 노드에서 파드가 자격증명을 못 얻습니다.
3-3. 드레인 관점에서의 해결 순서
- 새 노드에서 동일 파드가 정상 Ready 되는지 먼저 확인
- IRSA 문제면 서비스어카운트 어노테이션, IAM trust policy, 권한(policy attachment)을 수정
- 파드가 정상화되어 Ready 수가 복구되면 PDB가 풀리고 drain이 진행
즉, “드레인 명령을 더 세게” 하기 전에 새 노드에서 파드가 뜰 수 있는 조건을 먼저 복구하는 것이 핵심입니다.
4) 실전 체크리스트: PDB·DaemonSet·IRSA를 한 번에 엮어보기
드레인이 멈춘 상황에서 아래 순서로 보면 시행착오가 줄어듭니다.
4-1. 노드에 남은 파드가 무엇인지 분류
kubectl get pod -A -o wide --field-selector spec.nodeName=$NODE
- DaemonSet 파드만 남았는데 끝나지 않으면
--ignore-daemonsets여부 확인 - 특정 Deployment 파드가 계속 남아 있으면 PDB 또는 종료 지연
4-2. PDB가 막는지 수치로 확인
kubectl get pdb -A
ALLOWED DISRUPTIONS가0인 PDB를 찾고, 해당 selector가 어떤 워크로드를 가리키는지describe로 확인
4-3. 새 노드에서 파드가 정상화되는지 확인(특히 IRSA)
# 스케줄은 됐는데 Ready가 안 되는 파드 찾기
kubectl get pod -A -o wide | grep -E 'Pending|CrashLoopBackOff|Error|ContainerCreating'
# 의심 파드의 SA 확인
kubectl -n my-ns get pod my-pod -o jsonpath='{.spec.serviceAccountName}'
IRSA가 원인이면 파드 로그에 AWS 인증/인가 에러가 강하게 나타납니다.
5) 운영에서 자주 쓰는 “안전한” 드레인/교체 패턴
5-1. 노드 교체 전, 여유 용량 확보
드레인 자체보다 중요한 것은 “파드를 옮겨갈 자리”입니다. 노드 그룹 업데이트 전에 여유 노드를 늘리면 PDB와 충돌할 확률이 크게 줄어듭니다.
- Managed Node Group이면 업데이트 전에 desired를 1~2 늘리기
- Cluster Autoscaler 사용 시 스케줄 불가 파드가 생기지 않도록 리소스 요청량 점검
5-2. PDB는 현실적인 값으로
- 레플리카 1인 서비스에
minAvailable: 1은 사실상 “드레인 금지”에 가깝습니다. - 단일 인스턴스가 불가피하면, 유지보수 시나리오를 문서화하고(점검창, 강제 중단 허용 여부) PDB를 그에 맞게 설계합니다.
5-3. IRSA는 “노드 교체 시나리오”로 사전 테스트
IRSA는 평소에는 멀쩡해 보여도, 노드 교체/재스케줄링에서 문제가 드러납니다.
- 신규 노드(새 AMI, 새 런타임)에서 동일 워크로드가 AWS API 호출 가능한지
- 서비스어카운트 어노테이션이 Helm/ArgoCD 배포 파이프라인에서 항상 유지되는지
6) 결론: 멈춘 drain은 대부분 “정책과 현실의 불일치”
EKS 노드 드레인이 멈출 때, 단순히 kubectl drain 옵션을 바꾸는 것으로 끝나는 경우도 있지만(예: --ignore-daemonsets), 많은 케이스는 아래의 불일치에서 시작합니다.
- PDB가 요구하는 가용성과 실제 Ready 파드 수의 불일치
- DaemonSet의 특성과 drain 기대치의 불일치
- IRSA가 전제하는 신뢰 정책/서비스어카운트 설정과 실제 배포 상태의 불일치
따라서 해결의 핵심은 “강제 삭제”가 아니라, 왜 퇴거가 거부되는지를 이벤트와 PDB 수치로 확인하고, **새 노드에서 파드가 정상화되는 조건(특히 IRSA)**을 먼저 복구하는 것입니다.
추가로, 파드가 비정상 상태라 PDB가 잠기는 전형적인 상황(CrashLoop, readiness 실패)은 아래 글의 진단 흐름을 함께 적용하면 원인 파악이 더 빨라집니다.