Người ta hay khen sai công lao. Một dây chuyền chạy êm được khen "nhờ băng chuyền mới", trong khi thứ thật sự giữ cho không hỏng hàng là mấy cái cảm biến lặng lẽ chặn sản phẩm lỗi lại — bỏ băng chuyền cũ vào vẫn êm, bỏ cảm biến ra là hỏng ngay. Rolling update của Kubernetes đúng là ca như vậy: ai cũng gán "không gián đoạn" cho RollingUpdate, nhưng đo kỹ thì thứ thật sự làm việc đó là một dòng cấu hình khác. Phần 41 đo một lần đổi ảnh với 0 lỗi; bài này thay từng tham số một để tìm ra thủ phạm thật.
Có readinessProbe: ba cấu hình, không mất yêu cầu nào
6 bản, đổi ảnh, gọi HTTP liên tục trong suốt quá trình:
| Cấu hình | Thời gian | Kết quả |
|---|---|---|
maxUnavailable=1, maxSurge=1 |
11,6 giây | 400 OK / 0 lỗi |
maxUnavailable=0, maxSurge=1 |
24,0 giây | 400 OK / 0 lỗi |
maxUnavailable=3, maxSurge=3 |
4,0 giây | 400 OK / 0 lỗi |
Nhanh gấp sáu lần vẫn không mất yêu cầu nào. Đây là kết quả tôi không chờ đợi. Trực giác nói maxUnavailable=3 — cho phép một nửa số bản biến mất cùng lúc — phải nguy hiểm hơn maxUnavailable=0. Nó không, vì ba bản còn lại vẫn phục vụ đủ.
Cấu hình quyết định tốc độ, không quyết định độ an toàn. Nó chỉ trở thành vấn đề an toàn khi số bản còn lại không gánh nổi tải — và đó là bài toán về năng lực, không phải về Kubernetes.
Bỏ readinessProbe: 8% yêu cầu chết
Cùng cụm, cùng lệnh, nhưng container mất 8 giây mới nghe cổng 80, và không khai readinessProbe:
| Kết quả | ||
|---|---|---|
RollingUpdate 1/1 |
876 OK / 77 lỗi | 8% thất bại |
Recreate |
604 OK / 129 lỗi | 18% thất bại |
Và chi tiết đáng nhớ hơn cả con số: kubectl rollout status báo hoàn tất sau 2,6 giây — trong khi container còn tám giây nữa mới phục vụ được. Không có readinessProbe, Kubernetes coi pod sẵn sàng ngay khi tiến trình khởi động. Nó đưa pod vào Endpoints, kube-proxy bắt đầu chuyển lưu lượng tới, và không ai nghe ở đầu kia.
Thứ tạo ra "không gián đoạn" là
readinessProbe, không phảiRollingUpdate.
readinessProbe:
httpGet: {path: /, port: 80}
initialDelaySeconds: 3
periodSeconds: 1
Pod chỉ vào Endpoints khi probe qua. Trước đó nó tồn tại, chạy, và không nhận lưu lượng. Đó là toàn bộ cơ chế.
Recreate tệ hơn gấp đôi
18% so với 8%. Recreate xoá hết pod cũ rồi mới tạo pod mới — có một khoảng thời gian thật sự không còn bản nào. Nó chỉ đúng khi ứng dụng không chạy hai phiên bản cùng lúc được: dùng chung một volume ReadWriteOnce, hoặc chạy migration cơ sở dữ liệu không tương thích ngược. Với những trường hợp đó, gián đoạn là điều bắt buộc — và bạn nên lên lịch nó thay vì bị bất ngờ.
Ba tham số điều khiển tốc độ
maxUnavailable — bao nhiêu bản được phép thiếu. Mặc định 25%.
maxSurge — bao nhiêu bản thừa được phép tạo tạm. Mặc định 25%.
Hai cái này không được cùng bằng 0 — Kubernetes từ chối, vì khi ấy không có cách nào tiến lên.
minReadySeconds — pod phải Ready liên tục bao lâu mới được tính. Mặc định 0. Đặt 10–30 giây là cách rẻ nhất để chặn một bản hỏng nhanh lan ra toàn bộ: pod khởi động rồi sập sau 5 giây sẽ không bao giờ được tính, và việc triển khai dừng lại.
Khi triển khai bị kẹt
kubectl rollout status deployment/app --timeout=180s
Hết giờ mà chưa xong thường có ba nguyên nhân, và cả ba đều nằm trong describe:
Ảnh không kéo được — ErrImagePull hoặc ImagePullBackOff. Sai tên, sai tag, hoặc thiếu imagePullSecrets.
Probe không bao giờ qua — pod Running nhưng 0/1 READY mãi. Đường dẫn probe sai, cổng sai, hoặc ứng dụng khởi động lâu hơn initialDelaySeconds cho phép.
Không đủ tài nguyên để tạo bản mới — maxSurge cần chỗ trống, và phần 47 đo được bộ lập lịch cộng requests chứ không nhìn mức dùng thật.
kubectl rollout undo deployment/app # lùi lại, 2,3 giây (phần 49)
progressDeadlineSeconds
Mặc định 600 giây. Nếu việc triển khai không tiến triển trong mười phút, Deployment được đánh dấu Progressing=False với lý do ProgressDeadlineExceeded. Quan trọng: nó không tự quay lui. Nó chỉ đánh dấu. Việc quay lui vẫn là của bạn hoặc của quy trình CI/CD. Đó là chỗ đáng cắm cảnh báo — một Deployment kẹt mười phút mà không ai biết là một bản triển khai nửa vời đang chạy trên môi trường thật.
Mẫu số chung: xác minh cơ chế nào thật sự tạo ra thuộc tính, và một tín hiệu chỉ thật bằng cái nó đo
Bài này gói hai bài học quan trọng. Thứ nhất: trước khi tinh chỉnh một cái núm để có được một thuộc tính, hãy xác minh chính cái núm đó tạo ra thuộc tính ấy — chứ đừng gán công cho cái tên nghe kêu nhất. "Không gián đoạn" đến từ readinessProbe, không từ RollingUpdate; còn maxUnavailable/maxSurge chỉ mua tốc độ, và hoá ra tốc độ với an toàn không đánh đổi nhau như trực giác tưởng. Vặn cấu hình rolling để "an toàn hơn" là vặn nhầm núm. Cùng cái bẫy khắp nơi: khen một framework "nhanh" trong khi tốc độ thật đến từ cache; đổ cho "async" cái nhanh mà thực ra là CDN; chỉnh GC để chữa một "rò rỉ" thật ra là một cache không giới hạn. Đo để biết cơ chế nào mới sinh ra tính chất bạn cần, rồi mới chỉnh đúng cái đó.
Thứ hai: một tín hiệu "sẵn sàng / hoàn tất / thành công" chỉ thật bằng đúng cái nó thật sự đo. Không có probe, "ready" nghĩa là "tiến trình đã khởi động" chứ không phải "đã phục vụ được", nên rollout status báo xong tám giây trước khi container thật sự lên — một lời báo thành công nói dối vì nó đo sai thứ. Cùng bản chất với một health endpoint trả 200 mà chẳng kiểm gì, một "build thành công" bỏ qua test, một bộ cân bằng tải đánh dấu node "up" chỉ vì bắt tay TCP được chứ chưa hỏi ứng dụng. Một đèn xanh chỉ khẳng định đúng cái nó đã kiểm — nên phải chắc nó kiểm đúng thứ mình cần bảo đảm.
Và một lưu ý phụ: phát hiện một trạng thái kẹt không phải là xử lý nó — progressDeadlineSeconds chỉ đánh dấu, không tự quay lui; đánh dấu mà không hành động là một sự cố nằm chờ.
Mẫu số chung: đừng gán một thuộc tính cho cái tên nổi bật — đo xem cơ chế nào thật sự tạo ra nó rồi chỉnh đúng chỗ; và đừng tin một tín hiệu xanh quá cái nó thực sự kiểm.
Muốn tự soi, kiểm xem deployment của bạn có thật sự không gián đoạn không:
kubectl get deployment <ten> -o jsonpath='{.spec.template.spec.containers[*].readinessProbe}'
Rỗng nghĩa là mọi lần triển khai đều có một cửa sổ mà lưu lượng đi vào pod chưa sẵn sàng. Với ứng dụng khởi động trong nửa giây thì cửa sổ đó nhỏ; với ứng dụng Java khởi động ba mươi giây thì nó là ba mươi giây lỗi ở mỗi bản.
Phần sau đo Service: cách một cái tên DNS biến thành lưu lượng tới đúng pod.