Mở đầu — vì sao bài này quan trọng
Hãy tưởng tượng bạn đang vận hành hệ thống bán hàng của một sàn thương mại điện tử Việt Nam. Vào lúc 8 giờ tối — giờ vàng khi lượng người mua đông nhất — đội của bạn cần đẩy một bản vá nhỏ để sửa lỗi hiển thị giá. Câu hỏi sống còn là: làm sao cập nhật phiên bản mới mà khách hàng đang thanh toán không bị văng ra màn hình "502 Bad Gateway"? Nếu bạn tắt toàn bộ dịch vụ cũ rồi bật dịch vụ mới, sẽ có vài giây đến vài phút "downtime" (thời gian ngừng phục vụ), đủ để mất đơn hàng và làm khách bực bội.
Rolling Update (cập nhật cuốn chiếu) chính là câu trả lời phổ biến nhất, và cũng là chiến lược mặc định trong Kubernetes. Ý tưởng rất đời thường: thay vì thay cả dàn đèn trong nhà cùng lúc (nhà tối om một lúc), bạn thay từng bóng một, luôn còn đèn sáng để sinh hoạt. Trong bài này, chúng ta đi sâu vào cơ chế thay pod cũ bằng pod mới không downtime, hiểu tường tận các tham số điều khiển, và tránh những cái bẫy khiến "rolling update" biến thành "rolling outage" (sự cố cuốn chiếu).
Lưu ý phạm vi: bài này tập trung chi tiết kỹ thuật của rolling update. Các chiến lược khác như Blue-Green (Bài 18), Canary (Bài 19), và Rollback (Bài 21) sẽ được nói kỹ ở bài riêng — ở đây tôi chỉ nhắc để bạn thấy sự khác biệt.
Khái niệm cốt lõi
Rolling update là gì
Rolling update là quá trình thay thế dần dần các bản sao (replica) của ứng dụng: tạo pod mới, chờ nó sẵn sàng, rồi mới xóa pod cũ, lặp lại cho đến khi toàn bộ đã lên phiên bản mới. Trong suốt quá trình, luôn có một số lượng pod đủ để phục vụ traffic (lưu lượng), nên người dùng không cảm nhận được gián đoạn.
Điểm mấu chốt: ở giữa quá trình, hệ thống của bạn chạy đồng thời hai phiên bản (v1 và v2). Đây vừa là ưu điểm (không downtime) vừa là ràng buộc bạn phải thiết kế cho hợp: cả hai phiên bản phải tương thích với nhau và với cùng một cơ sở dữ liệu.
Hai tham số linh hồn: maxUnavailable và maxSurge
Trong Kubernetes, hành vi rolling update được điều khiển bởi hai tham số trong strategy.rollingUpdate:
- maxUnavailable: số (hoặc phần trăm) pod được phép thiếu so với số replica mong muốn trong lúc cập nhật. Ví dụ 10 replica,
maxUnavailable: 25%nghĩa là tối thiểu luôn có 8 pod phục vụ (được phép thiếu 2). - maxSurge: số (hoặc phần trăm) pod được phép dôi ra vượt số replica mong muốn.
maxSurge: 25%với 10 replica cho phép tạm thời chạy tới 12–13 pod trong lúc chuyển đổi.
maxSurge cao thì cần nhiều tài nguyên tạm thời hơn nhưng đổi lấy tốc độ; maxUnavailable cao thì cập nhật nhanh nhưng công suất phục vụ tụt xuống trong quá trình.Cấu hình Kubernetes Deployment mẫu
apiVersion: apps/v1
kind: Deployment
metadata:
name: shop-api
spec:
replicas: 10
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1 # luôn giữ tối thiểu 9 pod phục vụ
maxSurge: 2 # cho phép tạm thời chạy tối đa 12 pod
selector:
matchLabels:
app: shop-api
template:
metadata:
labels:
app: shop-api
spec:
containers:
- name: shop-api
image: registry.example.vn/shop-api:v2
ports:
- containerPort: 8080
readinessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 15
periodSeconds: 10
readinessProbe — người gác cổng của rolling update
Đây là chi tiết quan trọng nhất mà nhiều người bỏ qua. Kubernetes chỉ coi một pod mới là "sẵn sàng" khi readinessProbe báo pass. Chỉ khi đó, Service mới định tuyến traffic vào pod đó, và Kubernetes mới dám xóa một pod cũ để đi tiếp.
Nếu bạn không khai báo readinessProbe, Kubernetes coi pod "chạy được là sẵn sàng" ngay khi container khởi động — trong khi ứng dụng có thể còn đang nạp cấu hình, kết nối database, hâm nóng cache. Kết quả: traffic đổ vào một pod chưa thực sự phục vụ được, và người dùng nhận lỗi dù bảng điều khiển hiện "rollout thành công". Nói ngắn gọn: không có readinessProbe thì không có rolling update không downtime, chỉ có ảo giác về nó.
Graceful shutdown — tắt pod cũ một cách lịch sự
Khi Kubernetes xóa pod cũ, nó gửi tín hiệu SIGTERM rồi chờ terminationGracePeriodSeconds (mặc định 30 giây) trước khi buộc dừng. Ứng dụng của bạn cần bắt tín hiệu này để: ngừng nhận request mới, xử lý nốt các request đang dở, đóng kết nối gọn gàng. Nếu app "chết đột ngột", những request đang xử lý dở sẽ bị cắt ngang — vẫn là downtime cục bộ với chính những người dùng xui xẻo đó.
Tình huống thực tế
Ví dụ 1 — Sàn TMĐT "ShopViet" và bài học readinessProbe
ShopViet (tên giả định, quy mô ~2 triệu người dùng hoạt động hàng tháng) chạy checkout-service với 12 replica trên Kubernetes. Đội DevOps đẩy phiên bản mới lúc 20h05, và trong 40 giây, tỷ lệ lỗi HTTP 503 vọt lên 7%. Sau khi soi log, họ phát hiện service mới cần khoảng 8 giây để nạp bảng khuyến mãi vào bộ nhớ, nhưng readinessProbe được đặt initialDelaySeconds: 2. Vậy là pod báo "ready" khi chưa nạp xong, traffic đổ vào và trả lỗi.
Cách khắc phục: họ chuyển readinessProbe trỏ tới endpoint /ready — chỉ trả 200 sau khi bảng khuyến mãi đã sẵn sàng — thay vì /ping chung chung. Đồng thời đặt maxUnavailable: 0 và maxSurge: 25% để không bao giờ giảm công suất phục vụ trong giờ cao điểm. Lần deploy sau, tỷ lệ lỗi về 0.
Bài học: readinessProbe phải phản ánh khả năng phục vụ thật của ứng dụng, không phải chỉ "tiến trình còn sống". Trong giờ cao điểm, ưu tiên maxUnavailable: 0.
Ví dụ 2 — Fintech "PayNhanh" và bẫy tương thích cơ sở dữ liệu
PayNhanh (giả định, ví điện tử) triển khai rolling update cho ledger-service. Phiên bản v2 đổi tên một cột trong database từ amount thành amount_vnd và deploy migration đổi tên trước khi rollout. Vấn đề: trong lúc rolling update kéo dài ~3 phút, các pod v1 vẫn đang chạy và truy vấn cột amount — cột này không còn tồn tại. Hàng loạt giao dịch của pod v1 lỗi cho tới khi rollout xong.
Đây là điểm cốt tử của rolling update: hai phiên bản chạy song song, nên schema database phải tương thích ngược. PayNhanh sửa quy trình theo mô hình "expand and contract": bước 1 thêm cột amount_vnd song song với amount (cả v1 và v2 đều chạy được); bước 2 rolling update lên v2 (chỉ dùng cột mới); bước 3, sau khi chắc chắn không còn pod v1, mới xóa cột amount cũ ở lần release sau. (Chi tiết về migration trong CD pipeline sẽ học kỹ ở Bài 31.)
Bài học: rolling update cấm những thay đổi database "phá vỡ tương thích ngược". Luôn tách thay đổi schema thành các bước cộng dồn an toàn.
Ví dụ 3 — Startup logistics "GiaoTocDo" và câu chuyện tài nguyên
GiaoTocDo chạy cụm Kubernetes nhỏ trên một nhà cung cấp cloud khu vực Đông Nam Á, với node đã dùng gần hết CPU. Họ đặt maxSurge: 100% cho một Deployment 8 replica, nghĩa là lúc cập nhật cần chỗ cho tới 16 pod. Cụm không đủ tài nguyên, các pod mới kẹt ở trạng thái Pending, rollout đứng hình 20 phút. Vì maxUnavailable: 0, Kubernetes không dám xóa pod cũ nếu pod mới chưa ready — nên hệ thống vừa không tiến, vừa vẫn phục vụ nhưng "treo" rollout.
Họ giảm maxSurge xuống 1 và maxUnavailable xuống 1, chấp nhận rollout chậm hơn (thay từng pod) nhưng vừa với tài nguyên. Rollout hoàn tất mượt mà.
Bài học: tham số rolling update phải cân với hạn mức tài nguyên (resource requests/limits và dung lượng node) của cụm. maxSurge cao cần dư tài nguyên tương ứng.
Hướng dẫn từng bước
Dưới đây là quy trình thực hiện một rolling update an toàn trên Kubernetes.
Bước 1 — Chuẩn bị readiness và liveness probe. Đảm bảo ứng dụng có endpoint health (ví dụ /healthz, /ready) phản ánh đúng khả năng phục vụ. Đây là điều kiện tiên quyết.
Bước 2 — Chọn tham số chiến lược. Với dịch vụ quan trọng, đặt maxUnavailable: 0 để không giảm công suất, và maxSurge vừa với tài nguyên (thường 25%). Với batch job ít nhạy cảm, có thể để maxUnavailable cao hơn cho nhanh.
Bước 3 — Cấu hình graceful shutdown. Đặt terminationGracePeriodSeconds đủ dài cho request dài nhất, và code app bắt SIGTERM để dừng nhận request mới rồi xử lý nốt việc đang dở.
Bước 4 — Kích hoạt cập nhật. Thường chỉ cần đổi image tag:
kubectl set image deployment/shop-api shop-api=registry.example.vn/shop-api:v2
Bước 5 — Theo dõi tiến trình rollout.
kubectl rollout status deployment/shop-api
kubectl get pods -l app=shop-api -w
Quan sát pod mới chuyển sang Running và Ready 1/1, pod cũ lần lượt Terminating.
Bước 6 — Kiểm chứng. Xem tỷ lệ lỗi, độ trễ (latency) trên dashboard trong lúc rollout. Đây là "continuous verification" mà Bài 52 sẽ đào sâu.
Bước 7 — Sẵn sàng dừng hoặc quay lui nếu có sự cố.
kubectl rollout pause deployment/shop-api # tạm dừng để điều tra
kubectl rollout undo deployment/shop-api # quay về phiên bản trước
Kubernetes lưu lịch sử ReplicaSet nên undo đưa bạn về bản cũ nhanh chóng (chi tiết chiến lược rollback ở Bài 21).
Lỗi thường gặp & mẹo
Thiếu readinessProbe (hoặc probe sai). Đây là lỗi số một. Không có probe, traffic vào pod chưa sẵn sàng. Probe trỏ vào endpoint quá đơn giản (chỉ kiểm tra tiến trình sống) cũng nguy hiểm không kém. Mẹo: tách riêng liveness (còn sống không) và readiness (phục vụ được chưa) — chúng khác nhau về ý nghĩa.
maxUnavailable quá cao trong giờ cao điểm. Đặt maxUnavailable: 50% nghĩa là nửa công suất biến mất giữa lúc đông khách. Mẹo: dịch vụ nhạy cảm luôn để maxUnavailable: 0, đánh đổi bằng maxSurge để giữ tốc độ.
Quên graceful shutdown. Pod cũ bị SIGKILL cắt ngang request đang xử lý. Mẹo: thêm một preStop hook sleep vài giây để cho load balancer kịp ngừng gửi traffic vào pod sắp chết trước khi app dừng.
Thay đổi database phá vỡ tương thích ngược. Như ví dụ PayNhanh: đổi/xóa cột khiến pod cũ lỗi. Mẹo: luôn dùng mô hình "expand and contract", chỉ xóa cột/API cũ sau khi chắc chắn không còn phiên bản nào dùng.
maxSurge cao mà tài nguyên thiếu. Pod mới kẹt Pending, rollout đứng. Mẹo: kiểm tra dung lượng node và resource requests trước; giảm maxSurge nếu cụm chật.
Không đặt progressDeadlineSeconds. Mặc định 600 giây; nếu rollout kẹt lâu hơn, Deployment bị đánh dấu Failed. Mẹo: theo dõi và cấu hình giá trị này phù hợp để phát hiện rollout "treo" sớm.
Nhầm rolling update với zero-risk. Rolling update không có bước "dừng lại kiểm tra ở tỷ lệ nhỏ" như Canary. Nếu v2 có bug, nó lan dần ra toàn bộ. Mẹo: với thay đổi rủi ro cao, cân nhắc Canary (Bài 19) thay vì rolling update thuần.
Bài tập thực hành
- Cấu hình cơ bản. Tạo một Deployment với 6 replica,
maxUnavailable: 0,maxSurge: 2, kèm readinessProbe trỏ tới/healthz. Tính xem trong lúc rollout, số pod phục vụ tối thiểu và tối đa là bao nhiêu.
- Quan sát hành vi. Deploy một ứng dụng web đơn giản (ví dụ nginx), rồi chạy
kubectl set imageđể cập nhật sang tag mới. Mở song songkubectl get pods -wvàkubectl rollout statusđể quan sát pod cũTerminating, pod mớiReady. Ghi lại thứ tự sự kiện.
- Mô phỏng sự cố readiness. Đặt readinessProbe trỏ tới một endpoint luôn trả lỗi. Chạy rollout và giải thích vì sao nó "treo" mà không xóa pod cũ. Sau đó sửa probe và xác nhận rollout hoàn tất.
- Rollback. Sau khi rollout lên v2, dùng
kubectl rollout undođể quay về v1. Kiểm trakubectl rollout historyvà giải thích ReplicaSet cũ được kích hoạt lại như thế nào.
- Suy nghĩ thiết kế. Với dịch vụ
checkout-servicecủa một sàn TMĐT chạy 20 replica trong giờ cao điểm, hãy đề xuất bộ tham sốmaxUnavailable/maxSurgevà giải thích lựa chọn dựa trên yêu cầu không downtime và tài nguyên cụm.
Tóm tắt
Rolling update là chiến lược thay pod cũ bằng pod mới một cách cuốn chiếu để đạt mục tiêu không downtime, và là chiến lược mặc định của Kubernetes. Bốn điều cần khắc cốt ghi tâm:
- readinessProbe là trái tim: chỉ khi pod mới thật sự sẵn sàng, Kubernetes mới định tuyến traffic và tiếp tục thay pod cũ. Thiếu nó, "không downtime" chỉ là ảo giác.
- maxUnavailable và maxSurge điều khiển độ an toàn và tốc độ; dịch vụ nhạy cảm nên để
maxUnavailable: 0vàmaxSurgevừa với tài nguyên. - Hai phiên bản chạy song song trong lúc cập nhật, nên thay đổi database và API phải tương thích ngược — dùng mô hình "expand and contract".
- Graceful shutdown đảm bảo pod cũ xử lý nốt request đang dở trước khi tắt.