PDB Configuration: Đảm Bảo Disruption Budget Trong Lúc Upgrade
Tại sao cấu hình PDB là điểm quyết định thành-bại của một upgrade
Trong toàn bộ chuỗi 5 bước drain đã mô tả ở phần trước (cordon → drain → xoá node), bước "drain" không phải là GKE tự quyết định Pod nào bị xoá theo ý mình — nó đi qua một cơ chế API rất cụ thể của Kubernetes gọi là Eviction API, và PodDisruptionBudget (PDB) là "cổng gác" duy nhất kiểm soát cơ chế này. Nếu bạn không cấu hình PDB, hoặc cấu hình sai cách nó hoạt động bên trong, hai kết cục xấu đều có thể xảy ra: (1) toàn bộ replica của một StatefulSet bị evict cùng lúc gây outage, hoặc (2) PDB quá chặt khiến GKE bị buộc phải force-delete Pod sau một giờ chờ, tạo ra đúng loại gián đoạn mà PDB được thiết kế để ngăn chặn.
Internal model: Eviction API và disruption controller hoạt động như thế nào
Drain không phải là "xoá Pod" — nó là một request lịch sự qua Eviction API
Khi GKE cordon một node và bắt đầu drain, nó không gọi API xoá Pod trực tiếp (DELETE). Nó gửi eviction request — một API riêng biệt đi qua subresource pods/eviction — cho từng Pod trên node đó. Sự khác biệt này là cốt lõi của toàn bộ cơ chế PDB: request DELETE thông thường luôn thành công vô điều kiện; nhưng eviction request bị API server chặn và kiểm tra PDB trước khi cho phép.
"eviction requests are sent to all the Pods running on the node" trong quá trình drain, và các request này phải tôn trọng PodDisruptionBudget đang áp dụng cho Pod đó (Node pool upgrade strategies).
Cơ chế kiểm tra này được thực hiện bởi một component chạy trong control plane gọi là disruption controller. Controller này liên tục theo dõi mọi PDB object và tính toán một trường trạng thái gọi là disruptionsAllowed — số lượng Pod (thuộc tập được PDB đó chọn qua label selector) có thể bị evict ngay bây giờ mà không vi phạm ngưỡng đã khai báo. Khi một eviction request tới, API server tra disruptionsAllowed: nếu > 0, request được chấp nhận và giá trị giảm đi 1; nếu = 0, request bị từ chối với lỗi rõ ràng ("Cannot evict pod as it would violate the pod's disruption budget").
minAvailable và maxUnavailable không đối xứng như tên gọi gợi ý
Hai trường cấu hình của PDB dễ bị nhầm là "hai cách viết khác nhau của cùng một ý tưởng", nhưng chúng có ngữ nghĩa tính toán khác nhau:
minAvailable: PDB tính "còn lại bao nhiêu Pod đang chạy" và so với ngưỡng này. Nếu số Pod hiện tại đang Ready đúng bằngminAvailable,disruptionsAllowed = 0— không Pod nào được evict thêm cho tới khi có Pod khác trở lại Ready.maxUnavailable: PDB tính "đã có bao nhiêu Pod không khả dụng" (do bất kỳ lý do gì, không chỉ do upgrade) và so với ngưỡng cho phép. Nếu số Pod không khả dụng đã đạtmaxUnavailable,disruptionsAllowed = 0.
Sự khác biệt then chốt: minAvailable neo vào tổng số replica mong muốn (nên nếu bạn scale Deployment, ngưỡng bảo vệ tự động theo tỉ lệ nếu dùng percentage), còn maxUnavailable neo vào trạng thái bất khả dụng hiện tại — điều này khiến maxUnavailable nhạy với các sự cố không liên quan tới upgrade: nếu một Pod đã crash vì lý do khác (OOM, node lỗi phần cứng) trước khi upgrade bắt đầu, ngân sách maxUnavailable đã bị "ăn" một phần, khiến drain trong lúc upgrade bị chặn sớm hơn dự kiến mà không liên quan gì tới hành động upgrade.
Ràng buộc bắt buộc: PDB phải luôn cho phép ít nhất một disruption
Một PDB với minAvailable bằng đúng số replica hiện có (ví dụ minAvailable: 3 cho Deployment 3 replica) tạo ra một cấu hình mà disruptionsAllowed luôn bằng 0 trong điều kiện bình thường — về lý thuyết, không Pod nào có thể bị evict qua đường "lịch sự" này:
"a PDB must allow for at least one Pod to be disrupted" để cho phép hoạt động maintenance diễn ra (Ensure workloads are disruption-ready).
Đây chính là điểm giao giữa cơ chế Kubernetes chuẩn và override đặc thù của GKE: Kubernetes gốc, nếu PDB chặn vĩnh viễn, sẽ chặn vĩnh viễn — disruption controller không tự động "phá luật". Nhưng GKE, vì phải đảm bảo hoàn thành nghĩa vụ vận hành nền tảng (upgrade, patch bảo mật), áp một giới hạn thời gian cứng lên phía trên cơ chế PDB gốc.
Giới hạn thời gian cứng 60 phút: GKE "thắng" PDB, không phải ngược lại
Đây là điểm dễ bị hiểu lầm nhất trong toàn bộ chương: nhiều người nghĩ PDB là một cơ chế bảo vệ tuyệt đối. Thực tế, GKE tôn trọng PDB có điều kiện thời gian:
"GKE respects a PDB for up to 60 minutes" — sau khoảng thời gian này, nếu Pod vẫn chưa bị evict thành công qua đường lịch sự, GKE buộc phải tiến hành xoá node, kéo theo force-delete các Pod còn lại (Ensure workloads are disruption-ready).
Nói cách khác: PDB không ngăn cản disruption diễn ra — nó chỉ trì hoãn và tạo áp lực buộc bạn tự điều phối (ví dụ thông qua một controller ứng dụng chủ động giảm số replica trên node đang bị drain để nhường chỗ). Nếu bạn không có cơ chế chủ động đó, và PDB được cấu hình chặt đến mức luôn giữ disruptionsAllowed=0, kết quả cuối cùng sau 60 phút vẫn là force-delete — chính xác là kịch bản mất capacity đột ngột mà PDB được kỳ vọng để tránh, chỉ là bị trì hoãn 60 phút và tạo cảm giác an toàn giả.
PDB dựa vào readiness probe để biết "Pod nào đang thực sự available"
Một chi tiết cơ chế thường bị bỏ sót: disruption controller tính disruptionsAllowed dựa trên trạng thái Ready của Pod — mà trạng thái này lấy trực tiếp từ readiness probe. Nếu một Deployment không khai báo readiness probe, kubelet coi Pod là Ready ngay khi container start (container Running state), không phản ánh việc ứng dụng đã thực sự sẵn sàng xử lý traffic hay chưa:
"a PDB is not as effective in gauging application readiness" khi thiếu readiness probe — GKE khuyến nghị bổ sung cả readiness và liveness probe để PDB phản ánh đúng thực tế (Ensure workloads are disruption-ready).
Hệ quả cụ thể: nếu ứng dụng cần 30 giây để warm-up cache sau khi container start, nhưng không có readiness probe, disruption controller sẽ coi Pod mới này "Ready" ngay lập tức và tự tin cho phép evict thêm Pod khác — dù thực tế capacity đang phục vụ traffic thật thấp hơn số liệu PDB nhìn thấy. PDB, trong trường hợp này, bảo vệ dựa trên một con số sai.
Constraints, trade-offs & failure modes
PDB chỉ bảo vệ voluntary disruption, không bảo vệ involuntary disruption
Đây là ranh giới cơ chế tuyệt đối cần nhớ: PDB chặn được eviction request (voluntary — do con người/hệ thống chủ động khởi tạo, như node drain khi upgrade). Nó hoàn toàn vô dụng với involuntary disruption — node chết vì lỗi phần cứng, kernel panic, hoặc preemption của Spot VM. Trong các trường hợp đó, Pod biến mất ngay lập tức, không đi qua Eviction API, nên không có PDB nào can thiệp được. Điều này có nghĩa PDB là công cụ cho upgrade có kế hoạch, không phải chiến lược resilience tổng thể — resilience thật đến từ số replica, anti-affinity, và multi-zone deployment, PDB chỉ điều phối tốc độ disruption có chủ đích.
Recommender phát hiện 4 nhóm cấu hình sai — nhưng chỉ phát hiện, không tự sửa
GKE Recommender chủ động rà soát và gắn cờ 4 pattern cấu hình sai liên quan tới disruption readiness:
PDB_UNPROTECTED_STATEFULSET— StatefulSet không có PDB nào khớp label selector.PDB_UNPERMISSIVE— PDB cấu hình chặt tới mức không cho phép bất kỳ disruption nào (disruptionsAllowedluôn = 0).PDB_STATEFULSET_WITHOUT_PROBES— StatefulSet được PDB bảo vệ nhưng thiếu readiness probe, khiến số liệu bảo vệ không chính xác như đã phân tích ở trên.DEPLOYMENT_MISSING_PDB— Deployment nhiều hơn 1 replica nhưng không có PDB nào.
Recommendation được đánh giá theo tần suất một lần mỗi ngày, với độ trễ tối đa 24 giờ để phản ánh thay đổi cấu hình mới. Hệ quả vận hành: nếu bạn sửa PDB ngay trước một cửa sổ upgrade trong ngày, đừng kỳ vọng Recommender xác nhận thay đổi kịp thời — đây là công cụ audit định kỳ, không phải validation real-time trước khi upgrade.
Trade-off: PDB chặt kéo dài thời gian upgrade toàn cluster, không chỉ một node
Vì drain của mỗi node có thể tốn tối đa 60 phút khi bị PDB chặn liên tục, và surge upgrade xử lý node tuần tự trong từng zone, một PDB cấu hình sai trên một workload duy nhất có thể kéo dài đáng kể tổng thời gian upgrade của toàn zone — không chỉ ảnh hưởng workload đó. Tài liệu troubleshooting chính thức xác nhận đây là nguyên nhân hàng đầu gây "stuck upgrade":
"A high value...can significantly extend upgrade durations" và một PDB chặn eviction là nguyên nhân cốt lõi khiến GKE phải chờ tối đa một giờ lặp lại trên nhiều node (Troubleshoot cluster upgrades).
Pattern minh họa: kết hợp PDB với PreStop hook để tránh force-delete gây mất request
Bài học kiến thức cần rút ra ở đây là: PDB chỉ trì hoãn thời điểm Pod bị xoá — nó không đảm bảo cách Pod đó tắt có sạch sẽ hay không. Với workload xử lý request đồng bộ (ví dụ HTTP server), cơ chế thực sự chống mất request nằm ở sự kết hợp giữa terminationGracePeriodSeconds đủ dài và một preStop hook chờ load balancer gỡ endpoint khỏi backend pool trước khi container nhận SIGTERM. PDB đảm bảo tổng số Pod bị disrupt đồng thời không vượt ngưỡng; PreStop hook đảm bảo từng Pod bị disrupt tắt đúng cách. Thiếu một trong hai, cấu hình còn lại không đủ để đảm bảo zero-downtime thực sự — đây là lý do hai cơ chế này luôn phải được thiết kế cùng nhau, không tách rời.
Anti-pattern: đặt minAvailable: 100% cho mọi Deployment như một "best practice mặc định"
Sai lầm về cơ chế ở đây là hiểu nhầm PDB như một công cụ "không cho phép downtime tuyệt đối", nên áp minAvailable: 100% (hoặc maxUnavailable: 0) khắp mọi nơi. Theo phân tích ở trên, cấu hình này khiến disruptionsAllowed luôn bằng 0 trong điều kiện vận hành bình thường (không có buffer replica dư), tức là mọi eviction request đều bị từ chối vô thời hạn cho tới khi hết 60 phút và GKE buộc force-delete. Hệ quả ở scale: một cluster với hàng trăm workload cấu hình kiểu này sẽ khiến mọi lần upgrade node pool kéo dài gần tối đa (60 phút/node bị chặn), và kết cục cuối cùng vẫn là force-delete không kiểm soát — tệ hơn so với việc cho phép một mức disruption có kiểm soát ngay từ đầu (ví dụ maxUnavailable: 1 với replica ≥ 3). Cách phòng tránh đúng: đặt ngưỡng PDB dựa trên mức độ chịu tải thực tế của phần replica còn lại (bao nhiêu replica tối thiểu để hệ thống vẫn phục vụ đúng SLA), không phải một con số tuyệt đối lấy từ trực giác "an toàn".
GCP-native implementation guidance
Ví dụ PDB dùng minAvailable cho Deployment cần đảm bảo tối thiểu 2 replica luôn sẵn sàng:
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: web-frontend-pdb
spec:
minAvailable: 2
selector:
matchLabels:
app: web-frontendKiểm tra disruptionsAllowed thực tế trước khi bắt đầu upgrade — đây là con số quyết định liệu drain có bị chặn ngay từ giây đầu tiên hay không:
kubectl get pdb --all-namespaces -o custom-columns=\
NAMESPACE:.metadata.namespace,NAME:.metadata.name,\
MIN-AVAIL:.spec.minAvailable,MAX-UNAVAIL:.spec.maxUnavailable,\
ALLOWED:.status.disruptionsAllowedTìm log lỗi eviction bị chặn trong Cloud Logging khi nghi ngờ PDB gây stuck upgrade:
resource.type="k8s_cluster"
"Cannot evict pod as it would violate the pod's disruption budget"Xem danh sách deprecation/readiness insight do Recommender tạo (bao gồm 4 pattern PDB đã liệt kê ở trên):
gcloud recommender insights list \
--project=PROJECT_ID --location=LOCATION \
--insight-type=google.container.WorkloadDisruptionInsight \
--recommender=google.container.DiagnosisRecommender