Fix Kubernetes Deployment Bị Treo: "Waiting for deployment rollout to finish: 1 old replicas are pending termination"

intermediate☸️ Kubernetes2026-03-25| Kubernetes 1.20+, kubectl CLI, mọi cloud provider (GKE, EKS, AKS) hoặc cluster on-premise

Error Message

Waiting for deployment "my-app" rollout to finish: 1 old replicas are pending termination...
#kubernetes#deployment#rolling-update#rollout#kubectl

Lỗi Gặp Phải

Bạn chạy kubectl rollout status deployment/my-app và terminal cứ đứng yên như vậy:

Waiting for deployment "my-app" rollout to finish: 1 old replicas are pending termination...

Pod mới đã chạy. Health check đã qua. Nhưng pod cũ không chịu chết. Vài phút trôi qua. Deployment không bao giờ đạt trạng thái successfully rolled out.

Nguyên Nhân

  • PodDisruptionBudget (PDB) đang chặn việc termination — budget không cho phép disruption nào, nên Kubernetes từ chối kill pod cũ.
  • Pod cũ bị đóng băng ở trạng thái Terminating — một finalizer, một tiến trình bỏ qua SIGTERM, hoặc quá trình graceful shutdown chậm đang giữ nó lại.
  • Node không đủ capacity — pod mới không thể được schedule ở đâu, nên pod cũ vẫn tồn tại như một placeholder.
  • Cấu hình rolling update bị deadlockmaxUnavailable: 0maxSurge: 0 cùng lúc khiến Kubernetes không thể thực hiện bất kỳ bước nào.
  • Pod mới không bao giờ chuyển sang trạng thái Ready — Kubernetes sẽ không evict replica cũ cho đến khi pod thay thế healthy. Một readiness probe bị lỗi âm thầm làm trì hoãn toàn bộ quá trình rollout.

Chẩn Đoán Trước

Đừng đoán mò. Chạy các lệnh sau để xác định chính xác điều gì đang bị kẹt:

# Xem toàn bộ trạng thái rollout
kubectl rollout status deployment/my-app

# Liệt kê tất cả pod từ deployment này kèm thông tin node
kubectl get pods -l app=my-app -o wide

# Kiểm tra events trên pod bị kẹt
kubectl describe pod <terminating-pod-name>

# Kiểm tra xem PodDisruptionBudget có đang chặn không
kubectl get pdb
kubectl describe pdb <pdb-name>

Nếu pod đã ở trạng thái Terminating hơn 5 phút, gần như chắc chắn nó có finalizer. Kiểm tra:

kubectl get pod <terminating-pod-name> -o json | jq '.metadata.finalizers'

Nếu mảng trả về không rỗng thì đó chính là thủ phạm.

Cách Sửa 1: Force-Delete Pod Bị Kẹt ở Trạng Thái Terminating

Pod đã ở trạng thái Terminating hơn 10 phút? Lệnh xóa thông thường sẽ không có tác dụng. Hãy force xóa:

kubectl delete pod <terminating-pod-name> --grace-period=0 --force

Lệnh này bỏ qua hoàn toàn cửa sổ graceful shutdown. Chỉ dùng sau khi đã xác nhận pod thực sự bị đóng băng — không phải chỉ là một service Java chậm cần 90 giây để drain kết nối.

Nếu finalizer đang chặn việc xóa, hãy gỡ nó ra thủ công:

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

Cách Sửa 2: Điều Chỉnh hoặc Tạm Thời Xóa PodDisruptionBudget

kubectl describe pdb hiển thị Allowed disruptions: 0? Đó chính là nguyên nhân. Nghĩa là minAvailable trong PDB bằng với số replica hiện tại — không pod nào có thể bị tác động. Có ba cách giải quyết:

# Cách A: scale up để tạo thêm chỗ trống (an toàn nhất)
kubectl scale deployment/my-app --replicas=<current+1>

# Cách B: tạm thời nới lỏng giá trị minimum của PDB
kubectl patch pdb <pdb-name> -p '{"spec":{"minAvailable":1}}'

# Cách C: xóa PDB hoàn toàn cho đến khi rollout hoàn tất
kubectl delete pdb <pdb-name>

Cách A an toàn nhất cho môi trường production — bạn đang tăng capacity thay vì giảm bảo vệ. Hãy khôi phục cấu hình PDB gốc sau khi rollout hoàn tất.

Cách Sửa 3: Sửa Lỗi Readiness Probe trên Pod Mới

Một rollout bị treo âm thầm không có lỗi rõ ràng hầu như luôn là vấn đề readiness probe. Pod mới khởi động, không bao giờ chuyển sang Ready, và Kubernetes giữ pod cũ sống vô thời hạn.

kubectl describe pod <new-pod-name> | grep -A 10 Readiness
kubectl logs <new-pod-name>

Hai tình huống phổ biến: ứng dụng cần 45 giây để khởi tạo nhưng initialDelaySeconds được đặt là 10, hoặc endpoint /health đã được đổi tên thành /healthz trong image mới. Hãy sửa cấu hình probe:

readinessProbe:
  httpGet:
    path: /healthz
    port: 8080
  initialDelaySeconds: 15
  periodSeconds: 10
  failureThreshold: 3

Cách Sửa 4: Điều Chỉnh Chiến Lược Rolling Update

Đặt cả maxUnavailable: 0maxSurge: 0 cùng lúc sẽ tạo ra deadlock. Kubernetes không thể khởi động pod mới (surge bằng 0) và không thể xóa pod cũ (unavailable bằng 0). Mọi thứ đứng yên.

kubectl get deployment my-app -o yaml | grep -A 5 strategy

Cho phép ít nhất 1 surge pod và rollout sẽ được gỡ kẹt ngay lập tức:

kubectl patch deployment my-app -p '{
  "spec": {
    "strategy": {
      "rollingUpdate": {
        "maxUnavailable": 0,
        "maxSurge": 1
      }
    }
  }
}'

maxUnavailable: 0, maxSurge: 1 là cấu hình zero-downtime mặc định tiêu chuẩn. Nó chạy thêm một pod trong quá trình chuyển tiếp, rồi xóa pod cũ sau khi pod thay thế đã healthy.

Cách Sửa 5: Rollback Nếu Cần

Image mới bị lỗi và bạn cần unblock production ngay? Rollback trước, debug sau:

# Rollback về revision trước
kubectl rollout undo deployment/my-app

# Hoặc chỉ định một revision cụ thể
kubectl rollout history deployment/my-app
kubectl rollout undo deployment/my-app --to-revision=2

Xác Nhận Đã Sửa Xong

# Xác nhận rollout đã hoàn tất
kubectl rollout status deployment/my-app
# Kết quả mong đợi: deployment "my-app" successfully rolled out

# Không còn pod nào bị kẹt ở Terminating
kubectl get pods -l app=my-app

# Số lượng replica khớp với trạng thái mong muốn
kubectl get deployment my-app

Phòng Ngừa

  • Điều chỉnh terminationGracePeriodSeconds theo thời gian shutdown thực tế của ứng dụng. Một ứng dụng Spring Boot đang drain connection pool có thể cần 60–90 giây. Giá trị mặc định 30 giây không phải lúc nào cũng phù hợp.
  • Kiểm tra readiness probe trên môi trường staging trước mỗi lần rollout. Một probe không bao giờ pass là nguyên nhân phổ biến nhất gây ra rollout bị treo âm thầm — và cũng dễ phát hiện sớm nhất.
  • Mặc định dùng maxUnavailable: 0, maxSurge: 1 trừ khi bạn có ràng buộc tài nguyên nghiêm ngặt. Đây là cấu hình zero-downtime an toàn nhất cho hầu hết workload.
  • Giữ minAvailable của PDB thấp hơn số replica. Nếu bạn chạy 3 replica và đặt minAvailable: 3, bạn đã vĩnh viễn chặn mọi disruption — kể cả các lần rollout của chính mình.
  • Xử lý SIGTERM đúng cách trong ứng dụng. Tiến trình của bạn nên bắt tín hiệu này, ngừng nhận request mới, drain các request đang xử lý, rồi thoát sạch sẽ. Bỏ qua SIGTERM chính là nguyên nhân biến một quá trình shutdown 30 giây thành một pod đóng băng 10 phút.

Related Error Notes