- Published on
Azure AKS에서 Pod가 Terminating에 멈출 때 해결법
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버 운영 중 가장 난감한 상황 중 하나가 Pod가 Terminating에서 빠져나오지 못해 롤링 업데이트가 멈추거나, 노드 드레인(kubectl drain)이 끝나지 않거나, 오토스케일이 지연되는 케이스입니다. AKS에서는 특히 Azure Disk/Files 같은 CSI 볼륨, 네트워크 플러그인(Azure CNI), 노드 상태 불량이 겹치면 빈도가 올라갑니다.
이 글에서는 “왜 Terminating이 끝나지 않는지”를 Kubernetes 종료 흐름 관점에서 해부하고, AKS에서 자주 맞닥뜨리는 원인별로 진단 커맨드와 복구 절차를 정리합니다.
참고: 종료 지연이 애플리케이션 레벨의 데드락/스레드 블로킹과 연결되는 경우도 많습니다. 애플리케이션이 SIGTERM을 못 처리해 종료가 길어질 때는 Spring Boot 3·Java 21 가상스레드 데드락/지연 진단도 같이 보면 원인 좁히기에 도움이 됩니다.
Pod Terminating이 오래 걸리는 “정상” 케이스부터 이해하기
Kubernetes에서 Pod 삭제는 대략 다음 순서로 진행됩니다.
- API 서버에 삭제 요청
- Pod에
deletionTimestamp가 찍히고 상태가Terminating으로 변경 - kubelet이 컨테이너에 종료 시그널을 전달(보통 SIGTERM)
terminationGracePeriodSeconds동안 정상 종료를 기다림- 종료가 안 되면 SIGKILL
- 볼륨 detach/umount, CNI 정리, 엔드포인트/서비스 업데이트
- 최종적으로 Pod 오브젝트 삭제
따라서 Terminating이 “몇 초~수십 초” 걸리는 건 정상입니다. 문제는 다음처럼 무한정 남는 경우입니다.
kubectl get pod에서 수 분~수 시간 Terminatingkubectl delete pod --force --grace-period=0도 안 먹히거나, 오브젝트는 사라졌는데 노드에서 리소스가 계속 남는 느낌- 드레인이 멈춰서 노드 교체/업그레이드가 막힘
1단계: 빠른 진단 체크리스트(가장 많이 쓰는 커맨드)
아래 순서로 보면 원인이 빨리 좁혀집니다.
Pod 이벤트와 종료 관련 필드 확인
kubectl -n <ns> describe pod <pod>
특히 다음을 봅니다.
Events에FailedKillPod,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가 과도하게 큼
권장 설정 패턴
terminationGracePeriodSeconds를 현실적인 값으로(예: 30~120초)- readiness probe를 활용해 트래픽부터 빼고 종료
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
우회/복구 절차(현장에서 자주 쓰는 순서)
- Pod를 강제 삭제하기 전에, 해당 PVC를 쓰는 다른 Pod가 동시에 떠 있는지 확인
- 노드에 붙은 Pod가 많고 스토리지 정리가 꼬인 느낌이면 노드 단위로 격리
kubectl cordon <node>
kubectl drain <node> --ignore-daemonsets --delete-emptydir-data --timeout=10m
- 드레인이 끝까지 안 가고 특정 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 노드 플러그인 로그 수집
실전 트러블슈팅 플로우(요약)
아래 순서로 보면 대부분 케이스가 정리됩니다.
kubectl describe pod로 이벤트 확인finalizers존재 여부 확인 후 컨트롤러 상태 점검- 볼륨(PVC/PV)과 CSI 드라이버 상태 확인
- 노드가
NotReady면 노드 복구 또는 교체 방향으로 전환 - 마지막 수단으로
--force --grace-period=0, 그리고 필요 시 finalizer 제거
마무리
AKS에서 Pod가 Terminating에 stuck 되는 문제는 단순히 “파드 하나가 안 지워진다”가 아니라, 종료 시그널 처리, 스토리지 detach, CNI 정리, 노드 건강성까지 걸친 클러스터 운영 이슈로 봐야 재발을 줄일 수 있습니다.
원인별로 커맨드를 표준화해 두고(특히 describe, finalizer 확인, CSI 로그 확인), 노드/스토리지 계층의 장애를 빠르게 격리하는 운영 루틴을 갖추면 드레인/업그레이드/배포 안정성이 크게 올라갑니다.