Post-upgrade Validation: Xác Nhận Hệ Thống Thực Sự Khỏe Sau Nâng Cấp
Tại sao "Operation: DONE" không phải là điểm kết thúc của runbook
Khi gcloud container operations describe báo trạng thái DONE, điều đó chỉ xác nhận một sự thật hẹp: toàn bộ node đã chạy version đích và đã Ready trở lại. Nó không xác nhận rằng mọi tầng phụ thuộc phía trên (webhook, workload identity, custom resource, autoscaler, node label) vẫn hoạt động đúng như trước upgrade. Đây là khoảng trống mà pre-upgrade validation (file 01) cố gắng thu hẹp trước, nhưng không thể loại bỏ hoàn toàn — vì như đã phân tích, cơ chế phát hiện deprecated API dựa trên lời gọi thực tế trong 30 ngày, có nghĩa một số exposure chỉ lộ ra sau khi upgrade hoàn tất và một luồng nghiệp vụ hiếm gặp thực sự chạy. Post-upgrade validation vì vậy không phải là bước "để chắc chắn thêm" — nó là bước bắt buộc để đóng vòng lặp mà pre-upgrade không thể đóng hoàn toàn.
Internal model: "thành công" cần được xác thực ở đúng bốn lớp riêng biệt
Lớp 1 — Version và node condition: lớp mà GKE tự xác thực, nhưng bạn nên tự kiểm lại
Đây là lớp duy nhất GKE đã verify trước khi báo DONE, nhưng việc tự kiểm lại độc lập là cần thiết vì Operation status phản ánh góc nhìn control plane, không phải góc nhìn Kubernetes API mà workload thực sự tương tác:
kubectl get nodes -o custom-columns=\
NAME:.metadata.name,VERSION:.status.nodeInfo.kubeletVersion,\
KERNEL:.status.nodeInfo.kernelVersion,CONTAINERD:.status.nodeInfo.containerRuntimeVersionĐiểm cần chú ý về cơ chế: kubeletVersion phải khớp version đích trên toàn bộ node trong pool — nếu một số node vẫn còn version cũ, đó là dấu hiệu operation đã "DONE" nhưng thực chất còn node bị treo ở trạng thái retry (đã mô tả ở file 05) mà bạn chưa quan sát kỹ đủ để bắt được.
Lớp 2 — Node label bị xoá âm thầm: một hệ quả cơ chế thường bị quên kiểm tra
Một hành vi ít được biết nhưng có tác động vận hành thật: khi GKE nâng cấp một node pool, các label bạn gán trực tiếp lên từng node bằng kubectl label (không phải label ở cấp node pool) bị xoá, vì node cũ bị thay hoàn toàn bằng node mới — node mới không kế thừa bất kỳ trạng thái runtime nào của node cũ:
"When GKE upgrades a node pool...GKE removes any labels you added to individual nodes using kubectl. Apply labels to node pools instead" (Manually upgrade a cluster's control plane or node pools).
Hệ quả thực tế: nếu bất kỳ nodeSelector, nodeAffinity, hoặc scheduling logic nội bộ nào phụ thuộc vào label thủ công đã gán qua kubectl (một pattern hay gặp khi debug tạm thời hoặc gán label đặc biệt cho một node cụ thể phục vụ một tác vụ), sau upgrade các Pod dựa trên label đó sẽ không tìm được node phù hợp và pending vô thời hạn — không có lỗi hiển thị rõ ràng ngoài FailedScheduling. Post-upgrade validation cần bao gồm việc kiểm tra lại danh sách label thực tế trên node so với kỳ vọng, đặc biệt nếu có bất kỳ workload nào dùng nodeSelector trỏ tới label không phải chuẩn GKE.
Lớp 3 — Webhook và CRD conversion: xác thực bằng hành động thật, không chỉ bằng việc "còn tồn tại"
Đã phân tích ở file 01 rằng webhook có thể "gãy âm thầm" nếu API version nó đăng ký bị gỡ. Điểm quan trọng cần nhấn ở post-upgrade: việc kubectl get validatingwebhookconfigurations vẫn trả về object đó không chứng minh gì cả — object cấu hình vẫn tồn tại trong etcd, nhưng API server có thể đã không còn gọi được nó đúng cách. Cách xác thực đúng là tạo một hành động thật đi qua đúng đường webhook đó (ví dụ tạo một resource test mà webhook validate) và quan sát kết quả thực tế, không chỉ đọc lại cấu hình tĩnh.
Lớp 4 — Workload Identity và token exchange: phụ thuộc âm thầm vào metadata server
Nếu cluster dùng Workload Identity, việc container gọi được Google API phụ thuộc vào một chuỗi: kubelet mount đúng volume projected token → metadata server proxy chạy trên node xử lý đúng request → GKE service tại tầng control plane exchange token. Một upgrade node pool đồng nghĩa toàn bộ chuỗi này được dựng lại từ đầu trên node mới — nếu có một sai lệch cấu hình ẩn (ví dụ annotation service account bị mất do một Helm chart không idempotent, hoặc binding IAM có điều kiện theo node pool cụ thể), lỗi chỉ lộ ra ở đây, không lộ ra ở lớp 1-3. Kiểm tra thực tế cần một lệnh gọi Google API thật từ trong Pod, không chỉ kiểm tra sự tồn tại của annotation.
Constraints, trade-offs & failure modes
Constraint: một số vấn đề chỉ lộ ra dưới traffic thật, không lộ ra ở smoke test
Health check gate (file 05) chỉ xác thực node ở mức hạ tầng (Ready, không pressure) — nó không và không thể xác thực rằng ứng dụng của bạn vẫn hoạt động đúng logic nghiệp vụ trên version Kubernetes mới. Một API đã đổi hành vi mặc định (không hẳn là bị gỡ, chỉ thay đổi semantic — ví dụ thay đổi default trong PodSecurity policy giữa các minor version) có thể khiến ứng dụng chạy nhưng cho kết quả sai một cách tinh vi, không crash rõ ràng. Đây là lý do smoke test hời hợt (chỉ kiểm tra "Pod Running") không đủ — validation cần chạm tới một luồng nghiệp vụ thật có thể so sánh kết quả trước/sau.
Trade-off: chờ đủ lâu để bắt lỗi hiếm gặp so với tốc độ đóng cửa sổ upgrade
Nếu bạn theo mô hình "canary cluster" (file 01) hoặc soak duration của blue-green (file 05), có một câu hỏi thực dụng: bao lâu là "đủ" để coi post-upgrade validation là hoàn tất? Không có câu trả lời tuyệt đối — nó phụ thuộc vào chu kỳ traffic pattern thật của hệ thống (đã nói ở file 05, ví dụ batch job hàng tuần chỉ lộ vấn đề sau 7 ngày). Đánh đổi thực dụng: đóng cửa sổ upgrade quá sớm (coi là "xong" ngay sau khi Operation DONE) tối ưu tốc độ vận hành nhưng tăng rủi ro bỏ sót vấn đề hiếm gặp; giữ cửa sổ mở dài tăng độ tin cậy nhưng làm chậm toàn bộ chu kỳ upgrade định kỳ của platform team.
Failure mode: đo lường "thành công" bằng chỉ số hạ tầng thay vì chỉ số nghiệp vụ
Một sai lầm quan sát phổ biến là dựa hoàn toàn vào dashboard hạ tầng (CPU, memory, số Pod Running) để kết luận upgrade thành công, trong khi các chỉ số này không có khả năng phát hiện lỗi logic tinh vi đã nêu ở trên. Ví dụ cụ thể: nếu một client library nội bộ gọi Kubernetes API để list resource và dựa vào một field đã đổi kiểu dữ liệu ngầm giữa hai minor version (không phải bị gỡ, chỉ đổi định dạng trả về), CPU/memory của toàn hệ thống có thể hoàn toàn bình thường trong khi một luồng nghiệp vụ cụ thể âm thầm trả kết quả sai. Chỉ số đúng để đo "thành công" phải là chỉ số nghiệp vụ — tỷ lệ lỗi ở API endpoint nghiệp vụ, độ chính xác dữ liệu ở một luồng đại diện — không phải chỉ số tài nguyên hạ tầng vốn chỉ phản ánh lớp 1 trong bốn lớp đã phân tích.
Failure mode: coi Recommender insight sau upgrade là "chắc chắn không còn vấn đề gì"
Ngay sau một cluster upgrade, GKE Recommender cần một khoảng thời gian quan sát mới (đã nói ở file 03, đánh giá theo tần suất hàng ngày) trước khi tạo insight phản ánh đúng trạng thái mới. Kiểm tra Recommender ngay sau upgrade và thấy "không có insight mới" không đồng nghĩa hệ thống sạch — nó chỉ đồng nghĩa Recommender chưa có đủ dữ liệu quan sát ở version mới để đánh giá. Đây là cùng loại hiểu lầm cơ chế đã cảnh báo ở file 01 (deprecated API insight), áp dụng lại ở giai đoạn post-upgrade.
Pattern minh họa: checklist bốn lớp áp dụng cho một upgrade thật
Kết hợp bốn lớp đã phân tích thành một trình tự xác thực cụ thể sau mỗi lần upgrade: (1) xác nhận kubeletVersion đồng nhất trên toàn pool; (2) diff danh sách node label thực tế so với snapshot trước upgrade, phát hiện label thủ công bị mất; (3) chạy một request test thật đi qua từng webhook quan trọng (không chỉ đọc cấu hình); (4) gọi thử một Google API thật từ trong một Pod đại diện mỗi namespace có dùng Workload Identity. Bài học kiến thức ở đây: mỗi lớp xác thực một cơ chế hoàn toàn khác nhau (hạ tầng node, scheduling, admission control, identity federation) — bỏ qua một lớp không có nghĩa "ít rủi ro hơn một chút", mà là để trống hoàn toàn một vùng có thể fail độc lập với ba vùng còn lại.
Anti-pattern: dùng kubectl get pods --all-namespaces (tất cả Running) làm tiêu chí "upgrade thành công"
Sai lầm về cơ chế: coi trạng thái Running của Pod là bằng chứng đủ cho việc ứng dụng hoạt động đúng. Running chỉ phản ánh container process đang tồn tại, không phản ánh readiness thực (đã nói ở file 03, readiness probe là điều kiện riêng), và tuyệt đối không phản ánh việc logic nghiệp vụ bên trong vẫn đúng sau khi một API dependency đổi hành vi. Hệ quả ở scale: một sự cố "âm thầm" (ứng dụng trả sai dữ liệu nhưng không crash) có thể tồn tại nhiều ngày trước khi ai đó phát hiện qua báo cáo nghiệp vụ, lúc đó việc truy ngược nguyên nhân về đúng thời điểm upgrade trở nên khó khăn hơn nhiều so với phát hiện ngay trong cửa sổ validation.
GCP-native implementation guidance
So sánh label node trước/sau upgrade (snapshot trước khi bắt đầu, diff sau khi hoàn tất):
kubectl get nodes --show-labels -o custom-columns=NAME:.metadata.name,LABELS:.metadata.labels > post-upgrade-labels.txt
diff pre-upgrade-labels.txt post-upgrade-labels.txtKiểm tra insight mới sau upgrade (bao gồm deprecation và disruption-readiness đã nêu ở file 01/03):
gcloud recommender insights list \
--project=PROJECT_ID --location=LOCATION \
--insight-type=google.container.DeprecationInsight \
--recommender=google.container.DiagnosisRecommender \
--filter="lastRefreshTime>=UPGRADE_TIMESTAMP"Xác thực Workload Identity token exchange thực tế từ một Pod đại diện:
kubectl exec -it POD_NAME -n NAMESPACE -- \
curl -H "Metadata-Flavor: Google" \
"http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token"