Published on

EKS 노드 드레인 멈춤 해결 - PDB·DaemonSet·IRSA

Authors

EKS에서 노드를 교체하거나(Managed Node Group 롤링 업데이트, AMI 패치, 스케일 인/아웃), 장애 조치를 위해 kubectl drain을 실행했는데 드레인이 끝나지 않고 멈춘 것처럼 보이는 상황이 자주 발생합니다. 콘솔/오토스케일러는 노드를 내리고 싶어 하는데, Kubernetes는 “내보낼 수 없는 파드가 있다”고 판단해 퇴거(eviction)를 계속 재시도합니다.

이 글은 드레인이 멈출 때 거의 항상 등장하는 3대 축인 PDB(파드 중단 예산), DaemonSet(데몬 파드), **IRSA(서비스어카운트 IAM 역할)**를 중심으로, 어떤 신호를 보면 어디부터 봐야 하는지와 실무에서 안전하게 풀어내는 순서를 제공합니다.

관련해서 파드가 계속 재시작하거나 준비 상태가 안 올라와 퇴거가 지연되는 케이스는 아래 글도 함께 보면 원인 추적에 도움이 됩니다.


드레인이 “멈춘 것처럼” 보이는 메커니즘

kubectl drain은 기본적으로 다음을 수행합니다.

  1. 노드를 cordon 해서 새 파드 스케줄링을 막음
  2. 노드 위 파드들을 eviction API로 퇴거 시도
  3. 퇴거가 안 되는 파드가 있으면 계속 대기/재시도

여기서 퇴거가 막히는 대표 이유가 아래입니다.

  • PDB가 허용하는 disruptionsAllowed0
  • 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: 2minAvailable: 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:AssumeRoleWithWebIdentity
  • InvalidIdentityToken
  • NoCredentialProviders

확인 명령:

# 파드 이벤트
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. 드레인 관점에서의 해결 순서

  1. 새 노드에서 동일 파드가 정상 Ready 되는지 먼저 확인
  2. IRSA 문제면 서비스어카운트 어노테이션, IAM trust policy, 권한(policy attachment)을 수정
  3. 파드가 정상화되어 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 DISRUPTIONS0인 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 실패)은 아래 글의 진단 흐름을 함께 적용하면 원인 파악이 더 빨라집니다.