Product Management
Đăng nhập
ESC

Nhập từ khóa để tìm kiếm

↑↓ Di chuyển
Enter Mở
ESC Đóng

Bài 17 — Deployment — Rolling Update Chi tiết

Đã đọc 0/15 mục · còn khoảng 11 phút
Cách chúng tôi ghi nhận

Một mục được tính là đã đọc khi nó nằm giữa màn hình đủ lâu so với độ dài của nó (bảng và sơ đồ tính một nửa), bạn vẫn đang cuộn hoặc bấm phím, và bạn đã thấy tới cuối mục. Lướt nhanh hoặc để tab mở không được tính. Chúng tôi chỉ lưu thời gian đọc từng mục — không lưu gì khác.

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.
Hai giá trị này quyết định tốc độ và độ an toàn của việc cập nhật. 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 song kubectl get pods -w và kubectl rollout status để quan sát pod cũ Terminating, pod mới Ready. 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 tra kubectl rollout history và 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-service của một sàn TMĐT chạy 20 replica trong giờ cao điểm, hãy đề xuất bộ tham số maxUnavailable/maxSurge và 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: 0 và maxSurge vừ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.
Nắm vững những chi tiết này, bạn biến rolling update từ một câu lệnh may rủi thành một quy trình triển khai đáng tin cậy cho hệ thống production.

Học xong bài này rồi? Tạo tài khoản miễn phí để lưu lại — lần sau vào là biết ngay đang dở ở đâu, và học hết khóa thì có chứng chỉ. Lưu tiến độ của tôi