Nâng cấp Kubernetes gồm hai phần rất khác nhau: nâng control plane, và thay từng node. Phần thứ hai mới là phần làm gián đoạn dịch vụ, và bài này đo nó.
kubeadm upgrade plan nói thẳng phần nó không làm
Trên một cụm v1.33.1:
Components that must be upgraded manually after you have upgraded
the control plane with 'kubeadm upgrade apply':
kubelet up-control-plane v1.33.1 -> v1.33.13
kubelet up-worker v1.33.1 -> v1.33.13
kubelet up-worker2 v1.33.1 -> v1.33.13
kubeadm upgrade apply lo kube-apiserver, kube-controller-manager, kube-scheduler, kube-proxy, CoreDNS và etcd. kubelet trên từng node là việc của bạn.
Đó là chỗ hay bị hiểu nhầm: chạy xong upgrade apply rồi thấy kubectl get nodes vẫn hiện phiên bản cũ, và tưởng nâng cấp thất bại. Không — cột đó hiện phiên bản kubelet, mà kubelet chưa được đụng tới.
Thứ tự bắt buộc, và không đảo được:
1. control plane (kubeadm upgrade apply)
2. kubelet + kubectl trên control plane
3. từng worker: drain -> nâng kubelet -> uncordon
Lý do thứ tự đó: kubelet được phép cũ hơn API server tối đa ba phiên bản phụ, nhưng không được mới hơn. Nâng kubelet trước là tạo ra một cụm ở trạng thái không được hỗ trợ, và triệu chứng thường là những thứ khó lần — pod không xếp được lịch, trường mới trong spec bị bỏ qua im lặng.
Cũng đừng nhảy cóc: v1.31 lên v1.33 phải qua v1.32. kubeadm từ chối thẳng, nhưng người ta hay quên khi lập kế hoạch.
Đo gián đoạn thật
Phần đáng đo là bước 3: kubectl drain một node trong lúc dịch vụ đang phục vụ.
Tôi bắn liên tục vào một Service trong 70 giây (~1.300 yêu cầu) và drain node giữa chừng, ở ba cấu hình.
| Cấu hình | Số lỗi |
|---|---|
2 bản + PDB + preStop + readinessProbe |
0 |
1 bản, không PDB, có preStop + readinessProbe |
0 |
1 bản, không PDB, không preStop, không probe |
3 / 3 / 3 |
Thời gian drain: 7,1 giây với 2 bản, 6,1 giây với 1 bản.
Dòng thứ hai là chỗ tôi phải dừng lại. Bỏ PodDisruptionBudget, hạ xuống một bản duy nhất, và vẫn không mất yêu cầu nào.
Thứ cứu được không phải PDB
Dòng thứ ba trả lời: bỏ preStop và readinessProbe thì mất 3 yêu cầu, lặp lại ba lần ra đúng 3 mỗi lần.
Cơ chế đã đo ở phần về tín hiệu dừng: preStop: sleep 5 giữ pod cũ phục vụ thêm 5 giây sau khi bị đuổi. Trong 5 giây đó, pod thay thế được tạo trên node còn lại và sẵn sàng — image đã có sẵn trên node nên nó khởi động trong vài giây — đồng thời kube-proxy trên mọi node kịp cập nhật iptables.
Nói cách khác, cái tạo ra khoảng chồng lấn không phải là số bản sao mà là năm giây trì hoãn.
Điều này không có nghĩa PDB vô dụng. Phép thử này không chạm tới việc của nó: PDB chặn việc drain nhiều node cùng lúc làm mất hết bản sao. Ở đây tôi chỉ drain một node, nên PDB không có gì để chặn.
Kết luận đúng là: PDB và preStop bảo vệ hai thứ khác nhau, và hướng dẫn nâng cấp thường chỉ nhắc cái đầu. Có PDB mà thiếu preStop thì vẫn mất yêu cầu ở mỗi node được drain.
Quy trình đủ
# --- control plane ---
kubeadm upgrade plan
kubeadm upgrade apply v1.33.13
kubectl drain <cp> --ignore-daemonsets
apt-get install -y kubelet=1.33.13-* kubectl=1.33.13-*
systemctl daemon-reload && systemctl restart kubelet
kubectl uncordon <cp>
# --- từng worker, một node một lần ---
kubectl drain <node> --ignore-daemonsets --delete-emptydir-data
kubeadm upgrade node
apt-get install -y kubelet=1.33.13-*
systemctl daemon-reload && systemctl restart kubelet
kubectl uncordon <node>
Ba cờ của drain đáng hiểu chứ đừng chép:
--ignore-daemonsetsgần như luôn cần: DaemonSet pod sẽ được tạo lại ngay trên chính node đó, nên không có ý nghĩa gì khi đuổi chúng.--delete-emptydir-dataxoá dữ liệu trongemptyDir. Với bộ đệm thì không sao; với thứ ai đó dùng làm chỗ lưu tạm quan trọng thì mất.--forcexoá cả pod không có bộ điều khiển — tức là pod trần sẽ không bao giờ quay lại. Dùng cờ này là phải biết chắc không có pod nào như vậy.
Và luôn có --timeout: thiếu nó, một PDB quá chặt sẽ làm drain treo vô hạn mà không nói gì.
Trước khi bắt đầu, kiểm ba thứ
Ghi chú nâng cấp của đúng phiên bản đó. API bị gỡ là nguyên nhân vỡ số một. Công cụ kubent hoặc pluto quét manifest tìm API sắp bị gỡ.
Sức chứa còn dư. Drain một node trong cụm ba node nghĩa là mọi pod của nó phải vừa vào hai node còn lại. Cụm chạy 80% thì bước này gây Pending, và nó xảy ra ở giữa quá trình nâng cấp.
Snapshot etcd ngay trước khi bắt đầu. Mất 0,1 giây, như đã đo ở phần 49. Và như phần 50 cho thấy, khôi phục là quay ngược toàn bộ cụm — nên hãy chụp ngay trước bước đầu tiên, đừng chụp từ đêm hôm trước.
Thử ba mươi giây
Kiểm xem cụm có chịu nổi việc mất một node không, mà không cần mất node nào:
for n in $(kubectl get nodes -o name | cut -d/ -f2); do
echo -n "$n: "
kubectl drain "$n" --ignore-daemonsets --delete-emptydir-data \
--dry-run=server 2>&1 | grep -cE "cannot|error|would violate" \
| xargs -I{} echo "{} van de"
done
--dry-run=server chạy đủ mọi kiểm tra của drain — kể cả PDB — nhưng không đuổi pod nào. Node nào báo vấn đề là node sẽ làm bạn kẹt giữa chừng lúc nâng cấp thật, và bây giờ là lúc tốt hơn nhiều để biết điều đó.
Phần sau: nhiều cụm và liên kết giữa chúng — đo chi phí thật của việc chia nhỏ.