Published on

Azure AKS에서 Pod가 Terminating에 멈출 때 해결법

Authors

서버 운영 중 가장 난감한 상황 중 하나가 Pod가 Terminating에서 빠져나오지 못해 롤링 업데이트가 멈추거나, 노드 드레인(kubectl drain)이 끝나지 않거나, 오토스케일이 지연되는 케이스입니다. AKS에서는 특히 Azure Disk/Files 같은 CSI 볼륨, 네트워크 플러그인(Azure CNI), 노드 상태 불량이 겹치면 빈도가 올라갑니다.

이 글에서는 “왜 Terminating이 끝나지 않는지”를 Kubernetes 종료 흐름 관점에서 해부하고, AKS에서 자주 맞닥뜨리는 원인별로 진단 커맨드와 복구 절차를 정리합니다.

참고: 종료 지연이 애플리케이션 레벨의 데드락/스레드 블로킹과 연결되는 경우도 많습니다. 애플리케이션이 SIGTERM을 못 처리해 종료가 길어질 때는 Spring Boot 3·Java 21 가상스레드 데드락/지연 진단도 같이 보면 원인 좁히기에 도움이 됩니다.

Pod Terminating이 오래 걸리는 “정상” 케이스부터 이해하기

Kubernetes에서 Pod 삭제는 대략 다음 순서로 진행됩니다.

  1. API 서버에 삭제 요청
  2. Pod에 deletionTimestamp가 찍히고 상태가 Terminating으로 변경
  3. kubelet이 컨테이너에 종료 시그널을 전달(보통 SIGTERM)
  4. terminationGracePeriodSeconds 동안 정상 종료를 기다림
  5. 종료가 안 되면 SIGKILL
  6. 볼륨 detach/umount, CNI 정리, 엔드포인트/서비스 업데이트
  7. 최종적으로 Pod 오브젝트 삭제

따라서 Terminating이 “몇 초~수십 초” 걸리는 건 정상입니다. 문제는 다음처럼 무한정 남는 경우입니다.

  • kubectl get pod에서 수 분~수 시간 Terminating
  • kubectl delete pod --force --grace-period=0도 안 먹히거나, 오브젝트는 사라졌는데 노드에서 리소스가 계속 남는 느낌
  • 드레인이 멈춰서 노드 교체/업그레이드가 막힘

1단계: 빠른 진단 체크리스트(가장 많이 쓰는 커맨드)

아래 순서로 보면 원인이 빨리 좁혀집니다.

Pod 이벤트와 종료 관련 필드 확인

kubectl -n <ns> describe pod <pod>

특히 다음을 봅니다.

  • EventsFailedKillPod, FailedMount, FailedUnMount, Unable to attach or mount volumes, context deadline exceeded
  • Finalizers 존재 여부
  • terminationGracePeriodSeconds
  • 어떤 컨테이너가 PreStop 훅을 갖고 있는지

Pod 오브젝트에서 finalizer, deletionTimestamp 확인

kubectl -n <ns> get pod <pod> -o json | jq '{name:.metadata.name, deletionTimestamp:.metadata.deletionTimestamp, finalizers:.metadata.finalizers, grace:.spec.terminationGracePeriodSeconds}'

deletionTimestamp는 있는데 finalizers가 남아 있으면, “무언가 정리 작업이 끝나야 삭제된다”는 의미입니다.

노드 상태 확인(노드가 NotReady면 거의 여기서 갈림)

kubectl get node -o wide
kubectl describe node <node>
  • 노드가 NotReady/Unknown
  • DiskPressure, MemoryPressure, PIDPressure
  • kubelet이 멈췄거나 네트워크가 끊긴 상황

해당 Pod가 붙은 PVC/볼륨 확인

kubectl -n <ns> get pod <pod> -o jsonpath='{.spec.volumes[*].persistentVolumeClaim.claimName}'
kubectl -n <ns> get pvc
kubectl get pv

AKS에서 Terminating 고착은 CSI 볼륨 detach/umount 지연이 매우 흔합니다.

원인 1: Finalizer가 남아 Pod 삭제가 막히는 경우

Finalizer는 “리소스를 삭제하기 전에 컨트롤러가 해야 할 정리 작업”을 보장합니다. 대표적으로 Ingress, Service, PVC/PV, 커스텀 컨트롤러(CRD)가 finalizer를 달아두고, 컨트롤러가 죽었거나 권한 문제로 정리를 못 하면 삭제가 멈춥니다.

진단 포인트

  • kubectl describe pod 또는 JSON에서 metadata.finalizers가 존재
  • 특정 애드온(예: 서비스 메시, 보안 에이전트, 커스텀 오퍼레이터) 설치 이후 빈발

해결: Finalizer 제거(주의해서)

Pod 자체에 finalizer가 달린 건 흔하진 않지만, 있으면 다음으로 제거할 수 있습니다.

kubectl -n <ns> patch pod <pod> -p '{"metadata":{"finalizers":[]}}' --type=merge

만약 PVC/PV 쪽 finalizer가 문제라면 PVC/PV에 대해 동일하게 확인합니다.

kubectl -n <ns> get pvc <pvc> -o json | jq '.metadata.finalizers'
kubectl -n <ns> patch pvc <pvc> -p '{"metadata":{"finalizers":[]}}' --type=merge

주의사항

  • Finalizer 제거는 “정리 작업을 건너뛰는” 행위입니다. 예를 들어 스토리지 자원이 Azure 쪽에 고아 리소스로 남을 수 있습니다.
  • 먼저 해당 finalizer를 처리하는 컨트롤러가 정상인지(파드가 살아있는지, 권한이 있는지) 확인하고, 마지막 수단으로만 제거하세요.

원인 2: 애플리케이션이 SIGTERM에 반응하지 않아 Grace Period를 넘기는 경우

컨테이너가 SIGTERM을 받고도 종료를 못 하면 kubelet은 grace period가 끝날 때까지 기다립니다. 이때 다음이 겹치면 “삭제가 느리다”를 넘어 “삭제가 안 된다”처럼 보이기도 합니다.

  • preStop 훅이 너무 오래 걸림(외부 API 호출, sleep 등)
  • 종료 시 DB 커넥션 반환/플러시가 무한 대기
  • 스레드 데드락, 이벤트 루프 블로킹
  • JVM/Node.js에서 종료 훅이 길어짐

진단 포인트

  • kubectl logs에서 종료 훅 메시지 이후 더 이상 진행 없음
  • terminationGracePeriodSeconds가 과도하게 큼

권장 설정 패턴

  1. terminationGracePeriodSeconds를 현실적인 값으로(예: 30~120초)
  2. readiness probe를 활용해 트래픽부터 빼고 종료
  3. preStop은 “짧고 실패해도 되는 작업”만

예시 Deployment:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
spec:
  replicas: 3
  template:
    spec:
      terminationGracePeriodSeconds: 60
      containers:
        - name: app
          image: myrepo/api:1.0.0
          lifecycle:
            preStop:
              exec:
                command: ["/bin/sh", "-c", "sleep 5"]
          readinessProbe:
            httpGet:
              path: /health/ready
              port: 8080
            periodSeconds: 5
            failureThreshold: 1

preStop에서 긴 작업을 하면, 노드 드레인/롤링 업데이트 전체가 느려집니다.

원인 3: 볼륨(특히 Azure Disk/Files) detach/umount가 지연되는 경우

AKS에서 Terminating 고착의 단골 원인입니다.

  • Azure Disk(블록) 기반 PVC가 노드에서 정상적으로 unmount/detach되지 않음
  • Azure Files(SMB/NFS) 마운트가 끊기거나 hang
  • CSI 드라이버/노드 플러그인 문제

진단 포인트

  • Pod 이벤트에 FailedUnmount, Unable to attach or mount volumes류가 반복
  • 같은 노드에서 특정 PVC를 쓰는 Pod만 유독 삭제가 안 됨

CSI 관련 컴포넌트 상태 확인

AKS의 스토리지 CSI 드라이버 파드를 확인합니다.

kubectl -n kube-system get pods | grep -E 'csi|disk|file'
kubectl -n kube-system logs <csi-node-pod> --tail=200

우회/복구 절차(현장에서 자주 쓰는 순서)

  1. Pod를 강제 삭제하기 전에, 해당 PVC를 쓰는 다른 Pod가 동시에 떠 있는지 확인
  2. 노드에 붙은 Pod가 많고 스토리지 정리가 꼬인 느낌이면 노드 단위로 격리
kubectl cordon <node>
kubectl drain <node> --ignore-daemonsets --delete-emptydir-data --timeout=10m
  1. 드레인이 끝까지 안 가고 특정 Pod에서 멈추면, 그 Pod만 별도 처리

원인 4: 노드가 NotReady/네트워크 단절이라 kubelet이 삭제를 마무리 못 하는 경우

Pod 삭제는 최종적으로 kubelet이 컨테이너 런타임에 kill을 요청하고, CNI/볼륨 정리를 수행하면서 완료됩니다. 노드가 NotReady면 API 서버 입장에서는 “삭제 요청은 받았는데, 현장 작업자가 실종된” 상태가 됩니다.

진단 포인트

  • 노드가 NotReady/Unknown
  • 해당 노드의 모든 Pod가 Terminating에 걸리거나, 반대로 이미 죽었는데 API에만 남아 있음

해결 전략

  • 노드가 복구 가능하면 kubelet/네트워크를 먼저 복구(가장 안전)
  • 복구가 어렵다면 노드를 교체하는 방향으로 진행
    • AKS에서는 VMSS 기반 노드풀이라면 스케일 아웃 후 스케일 인으로 교체가 빠른 편입니다.

노드 이슈가 메모리 고갈(OOM)에서 시작되는 경우도 많습니다. 커널 OOM 로그를 추적하는 방법은 Linux OOM Killer 로그 추적과 메모리 누수 진단을 함께 참고해도 좋습니다.

원인 5: kubectl delete --force를 했는데도 잔상이 남는 이유(그리고 올바른 강제 삭제)

강제 삭제는 “API 오브젝트를 지워서 사용자 시야에서 없애는 것”에 가깝습니다. 노드에서 컨테이너/네트워크/볼륨이 실제로 정리되는 것과는 별개일 수 있습니다.

올바른 강제 삭제 커맨드

kubectl -n <ns> delete pod <pod> --grace-period=0 --force

그래도 남아 있으면, 오브젝트가 실제로는 삭제되었는데도 다른 컨트롤러가 재생성하는지(Deployment/ReplicaSet/Job) 확인하세요.

kubectl -n <ns> get rs,deploy,job | grep <app>

AKS에서 재발 방지 체크리스트

운영 관점에서 “Terminating 고착”은 한 번 해결해도 재발하기 쉽습니다. 아래 항목을 기본 점검으로 두면 빈도가 크게 줄어듭니다.

1) PDB와 롤링 업데이트 전략 정리

PodDisruptionBudget이 너무 빡빡하면 드레인이 교착처럼 보일 수 있습니다.

  • maxUnavailable 또는 minAvailable이 현실적인지
  • 노드풀 업그레이드/스케일 인 시나리오에서 드레인이 가능한지

2) 종료 훅/Grace Period 표준화

  • preStop에서 외부 의존 호출 금지(또는 타임아웃 강제)
  • terminationGracePeriodSeconds 상한을 팀 표준으로
  • readiness를 먼저 내리고(lame duck) 트래픽을 빼는 구조로

3) 스토리지 사용 패턴 점검

  • Stateful 워크로드는 노드풀/가용영역 설계를 스토리지 제약과 함께 고려
  • Azure Files 사용 시 SMB 특성상 네트워크 품질에 민감하므로, 마운트 옵션/타임아웃/재시도 정책을 점검

4) 노드 상태 모니터링과 자동 교체

  • NotReady 지속 노드는 자동으로 격리/교체되도록 운영 룰 마련
  • kubelet, containerd, CSI 노드 플러그인 로그 수집

실전 트러블슈팅 플로우(요약)

아래 순서로 보면 대부분 케이스가 정리됩니다.

  1. kubectl describe pod로 이벤트 확인
  2. finalizers 존재 여부 확인 후 컨트롤러 상태 점검
  3. 볼륨(PVC/PV)과 CSI 드라이버 상태 확인
  4. 노드가 NotReady면 노드 복구 또는 교체 방향으로 전환
  5. 마지막 수단으로 --force --grace-period=0, 그리고 필요 시 finalizer 제거

마무리

AKS에서 Pod가 Terminating에 stuck 되는 문제는 단순히 “파드 하나가 안 지워진다”가 아니라, 종료 시그널 처리, 스토리지 detach, CNI 정리, 노드 건강성까지 걸친 클러스터 운영 이슈로 봐야 재발을 줄일 수 있습니다.

원인별로 커맨드를 표준화해 두고(특히 describe, finalizer 확인, CSI 로그 확인), 노드/스토리지 계층의 장애를 빠르게 격리하는 운영 루틴을 갖추면 드레인/업그레이드/배포 안정성이 크게 올라갑니다.