Chiến lược deploy: chọn rolling, blue-green hay canary theo thứ có thể quay lại Trung cấp
Thời gian đọc ước tính: 13 phút
Vấn đề
Phần tiêu đề “Vấn đề”Service checkout viết bằng NestJS chạy 10 Pod trên Kubernetes. Version 2 đổi cách tính khuyến mãi. Mỗi lần checkout thành công, service ghi một dòng đơn hàng, publish event OrderPlaced lên Kafka và gửi email xác nhận.
Một lệnh kubectl apply bình thường là rolling update: nếu v2 tính sai giá, lỗi đến tay mọi khách trong vòng một phút. Undo Deployment đưa v1 trở lại, nhưng giá sai đã nằm trong database, trong event và trong hộp thư khách.
Vậy câu hỏi không phải “chiến lược nào tốt nhất”, mà là: bao nhiêu người dùng được phép thấy bản lỗi, trong bao lâu, và quay lại tốn gì?
Chọn gì? (tóm tắt trước)
Phần tiêu đề “Chọn gì? (tóm tắt trước)”| Tình huống | Chọn | Điều kiện |
|---|---|---|
| Tool nội bộ, chấp nhận gián đoạn ngắn, hoặc hai version tuyệt đối không được chạy cùng lúc | Recreate | Downtime bằng thời gian dừng + thời gian khởi động |
| Đa số service stateless, thay đổi rủi ro thấp | Rolling update (mặc định của Kubernetes) | Readiness probe đúng; bản cũ và mới cùng hiểu một schema và một định dạng message |
| Cần chuyển tức thì và quay lại tức thì, trả được capacity gấp đôi | Blue-green | Database và event tương thích với cả hai màu |
| Thay đổi rủi ro trên luồng nhiều traffic (checkout, thanh toán) | Canary, tốt nhất có phân tích tự động | Metric tách theo version; đủ traffic để canary có ý nghĩa thống kê |
| Thay đổi chủ yếu ở hành vi, không ở binary | Feature flag chồng lên chiến lược nào cũng được | Code path cũ còn cho đến khi gỡ flag |
Đang tải diagram…
Khái niệm cốt lõi
Phần tiêu đề “Khái niệm cốt lõi”| Recreate | Rolling update | Blue-green | Canary | Progressive delivery | |
|---|---|---|---|---|---|
| Downtime | Có | Không, nếu readiness đúng | Không | Không | Không |
| Capacity thêm | Không | maxSurge Pod |
100% (hai stack đầy đủ) | Vài Pod, hoặc không | Như canary |
| Hai version cùng phục vụ | Không | Có, vài phút | Thoáng qua (request đang xử lý) | Có, theo thiết kế | Có, theo thiết kế |
| Phạm vi ảnh hưởng của bug | Mọi người | Tăng theo từng đợt | Mọi người sau khi chuyển | Chỉ phần canary | Phần canary, dừng tự động |
| Rollback | Deploy lại | Rollout mới về ReplicaSet cũ | Lật selector lại | Scale canary về 0 | Controller tự abort |
Deploy là đưa code lên máy; release là cho người dùng chạm tới nó. Feature flag tách hai việc này: v2 lên production với cách tính giá mới đang tắt, rồi flag mở dần mà không đụng tới Pod.
Cách hoạt động
Phần tiêu đề “Cách hoạt động”Rolling update: readiness quyết định nhịp
Phần tiêu đề “Rolling update: readiness quyết định nhịp”Deployment controller tạo ReplicaSet mới và chuyển dần Pod giữa hai ReplicaSet. maxSurge và maxUnavailable cùng mặc định 25%. Surge làm tròn lên, unavailable làm tròn xuống, nên với 10 replica: tối đa 13 Pod, tối thiểu 8 available.
Trước deploy: 10 Pod v1 ready; Service đưa toàn bộ traffic checkout vào v1.
- Controller chỉ scale v1 xuống trong giới hạn còn giữ được 8 Pod available.
- Pod mới nhận traffic khi readiness probe pass và mọi điều kiện trong
readinessGateslàTrue(ví dụ load balancer controller xác nhận đã đăng ký). - Pod chỉ được tính available sau khi ready liên tục
minReadySeconds(mặc định 0). Lúc đó controller mới gỡ thêm Pod v1. - Một v2 không bao giờ ready làm rollout kẹt với 8 Pod v1 phục vụ: capacity giảm 20% cho đến khi có người xử lý.
Kubernetes không tự rollback. Sau progressDeadlineSeconds (mặc định 600), nó chỉ đặt Progressing=False với reason ProgressDeadlineExceeded; pipeline hoặc con người phải chạy kubectl rollout undo.
Blue-green: một lần lật, một database chung
Phần tiêu đề “Blue-green: một lần lật, một database chung”Blue (v1) và green (v2) cùng chạy đủ kích thước. Green được kiểm tra qua một preview Service, rồi selector production được lật:
# Chỉ là ví dụ: Service productionapiVersion: v1kind: Servicemetadata: name: checkoutspec: selector: app: checkout slot: green # trước đó: blue ports: - port: 80 targetPort: 3000Rollback là lật ngược lại, khi blue vẫn còn chạy. Điểm vướng: hai màu dùng chung một database và một topic Kafka. Thay đổi schema của green phải đọc được bởi blue, nên migration phá huỷ cần expand–contract dù chọn chiến lược nào.
Canary: theo replica hay theo phần trăm
Phần tiêu đề “Canary: theo replica hay theo phần trăm”Kubernetes thuần chia traffic theo số Pod: một Deployment track: canary và một Deployment track: stable sau cùng một Service. Một Pod canary cạnh 9 Pod stable nhận khoảng 10%, và bước nhỏ nhất là một Pod.
Muốn phần trăm chính xác cần thứ định tuyến theo trọng số: service mesh, ingress controller, hoặc HTTPRoute của Gateway API với backendRefs[].weight (weight là tỉ lệ, không phải phần trăm). Khi đó 1% vẫn làm được với 2 Pod canary.
Progressive delivery: controller tự đọc metric
Phần tiêu đề “Progressive delivery: controller tự đọc metric”Argo Rollouts và Flagger tự động hoá các bước canary và chấm từng bước bằng query. Trong Argo Rollouts, một AnalysisRun thất bại sẽ abort rollout và đưa weight của canary về 0:
# Chỉ là ví dụ (Argo Rollouts v1.10). Không có trafficRouting:# setWeight được xấp xỉ bằng số replica (10% của 10 = 1 Pod).apiVersion: argoproj.io/v1alpha1kind: Rolloutmetadata: name: checkoutspec: replicas: 10 selector: matchLabels: { app: checkout } template: { ... } # giống Deployment strategy: canary: analysis: # background analysis chạy suốt các bước templates: - templateName: checkout-error-rate startingStep: 1 steps: - setWeight: 10 - pause: { duration: 10m } - setWeight: 30 - pause: { duration: 10m } - setWeight: 50 - pause: {} # chờ người promote---apiVersion: argoproj.io/v1alpha1kind: AnalysisTemplatemetadata: name: checkout-error-ratespec: metrics: - name: error-rate interval: 1m failureLimit: 2 successCondition: result[0] < 0.005 provider: prometheus: address: http://prometheus.monitoring:9090 query: | sum(rate(http_requests_total{service="checkout",track="canary",code=~"5.."}[5m])) / sum(rate(http_requests_total{service="checkout",track="canary"}[5m]))Flagger dùng stepWeight, maxWeight và threshold (số lần check thất bại trước khi rollback), kèm metric dựng sẵn request-success-rate và request-duration.
Chi tiết vận hành
Phần tiêu đề “Chi tiết vận hành”Thứ rollback không gỡ được. Chiến lược nào cũng rollback được Pod; không chiến lược nào rollback được thế giới bên ngoài Pod:
| Side effect của v2 | Sau rollback | Cần làm trước khi release |
|---|---|---|
| Migration phá huỷ (drop, rename) | v1 lỗi trên schema mới | Expand–contract; chỉ contract sau khi hết cửa sổ rollback |
Event OrderPlaced định dạng mới |
Đã nằm trong Kafka; consumer v1 có thể nghẹn | Schema event chỉ thêm trường, có version |
| Email xác nhận với giá sai | Đã gửi | Canary nhỏ; email đính chính; flag bọc cách tính mới |
| Dòng dữ liệu v2 đã ghi | Vẫn còn, có thể sai | Query đối soát theo version |
Theo dõi gì trong lúc rollout
- Tách mọi SLI theo version: thêm label
versionhoặctrackvào metric request, nếu không 5% lỗi của canary chỉ hiện thành 0,5% tổng. - Chấm bằng burn rate, không bằng số lỗi thô. Với SLO 99,9%, điểm xuất phát của SRE Workbook là page khi burn rate 14,4 trong 1 giờ (xác nhận bằng cửa sổ 5 phút), tức đã tiêu 2% budget 30 ngày.
- Một bước canary chỉ vài phút, ngắn hơn cửa sổ đó: tiêu chí abort so canary với stable theo từng bước; alert burn rate là lưới an toàn.
- Theo dõi cả tín hiệu nghiệp vụ: tỉ lệ chuyển đổi và mức giảm giá trung bình mỗi đơn bắt được bug tính giá vẫn trả HTTP 200.
Ví dụ thực tế
Phần tiêu đề “Ví dụ thực tế”Chạy demo (cần Docker và kubectl)
docker run -d --name k3s --privileged -p 16443:6443 \ -e K3S_KUBECONFIG_OUTPUT=/output/kubeconfig.yaml -e K3S_KUBECONFIG_MODE=666 \ -v "$PWD/k3s:/output" rancher/k3s:v1.33.5-k3s1 server --disable=traefiksed -i.bak 's#127.0.0.1:6443#127.0.0.1:16443#' k3s/kubeconfig.yamlexport KUBECONFIG=$PWD/k3s/kubeconfig.yaml
kubectl apply -f examples/src/deployment-strategies/checkout-deployment.yamlkubectl get deploy checkout -o jsonpath='{.spec.strategy}'# {"rollingUpdate":{"maxSurge":"25%","maxUnavailable":"25%"},"type":"RollingUpdate"}
examples/src/deployment-strategies/watch-rs.sh 45 &kubectl set env deploy/checkout VERSION=v2Output, rút gọn (thời gian thay đổi theo lần chạy):
t=0s total/updated/available=10/10/10 | v1=10t=1s total/updated/available=13/5/8 | v1=8 v2=5 (not ready)t=6s total/updated/available=13/10/8 | v1=3 v2=10 (5 available)t=13s total/updated/available=10/10/10 | v1=0 v2=10Version lỗi, rồi undo:
kubectl set env deploy/checkout VERSION=v3 READY_FAIL=1kubectl rollout status deploy/checkout# error: deployment "checkout" exceeded its progress deadlinekubectl rollout undo deploy/checkout # về v2, 6 s ở lần chạy nàyTrade-offs
Phần tiêu đề “Trade-offs”Canary / progressive delivery mang lại
- Bug chỉ chạm phần canary, và phân tích tự động dừng được nó mà không cần người.
- Thử thay đổi bằng traffic production thật, không phải đoán trên staging.
- Rollback là scale canary về 0, thường mất vài giây.
Và phải trả
- Cần metric tách theo version và đủ traffic ở mỗi bước.
- Hai version sống chung lâu hơn, nên schema và event phải tương thích lâu hơn.
- Thêm controller và CRD phải vận hành; weight chính xác cần mesh hoặc Gateway.
Checklist production
Phần tiêu đề “Checklist production”- Readiness probe phản ánh đúng “phục vụ được checkout” (health probe), và lúc shutdown có drain request đang xử lý (graceful shutdown).
- Đặt
minReadySecondsđể Pod crash ngay sau khi vừa ready sẽ chặn rollout. - Hạ
progressDeadlineSecondsvề con số thực tế và cho pipeline chạykubectl rollout undokhi gặpProgressDeadlineExceeded. - Mọi migration trong bản release đều tương thích ngược; bước phá huỷ để sang release sau.
- Metric request có label
version/track; dashboard và alert so canary với stable. - Tiêu chí abort (error rate, p99, một metric nghiệp vụ) được viết ra trước khi bắt đầu rollout.
- Diễn tập rollback trên staging và bấm giờ, gồm cả việc tắt feature flag.
Sai lầm thường gặp
Phần tiêu đề “Sai lầm thường gặp”Sai lầm hay gặp nhất là chờ nền tảng tự gỡ bản release lỗi:
Chờ Kubernetes tự rollback
Hay gặp
kubectl apply -f checkout.yaml # pipeline báo thành công rồi thoátNên viết
kubectl apply -f checkout.yamlkubectl rollout status deploy/checkout --timeout=5m \ || kubectl rollout undo deploy/checkoutVì sao
apply trả về khi object đã được lưu, không phải khi Pod đã ready. Rollout bị kẹt chỉ đặt một condition; phải có thứ gì đó đọc nó và hành động.
Sai lầm thứ hai nằm trong blue-green, vì bản thân thao tác lật luôn trông gọn gàng:
| Cách hiểu sai | Hệ quả | Cần làm |
|---|---|---|
| “Canary nhìn ổn trên dashboard tổng” | 5% lỗi ở canary chỉ hiện khoảng 0,5% tổng | Tách SLI theo version |
setWeight: 5 với 10 replica nghĩa là 5% |
Không có traffic router, 1 Pod = 10% | Dùng mesh hoặc Gateway, hoặc thêm replica |
| Feature flag thay được deploy an toàn | Crash lúc khởi động không quan tâm flag | Canary cho binary, flag cho hành vi |
Tự kiểm tra
Phần tiêu đề “Tự kiểm tra”- Với 20 replica và
maxSurge/maxUnavailablemặc định, số Pod tối đa và số Pod available tối thiểu trong lúc rollout là bao nhiêu? - Một Pod v2 qua readiness rồi crash sau 3 giây. Thiết lập nào khiến controller không đi tiếp?
- Canary đã phát 4.000 event định dạng mới trước khi bị abort. Rollback Pod không sửa được gì?
Tóm tắt
Phần tiêu đề “Tóm tắt”Tham khảo
Phần tiêu đề “Tham khảo”- Deployments — Kubernetes: default và cách làm tròn của strategy,
minReadySeconds,progressDeadlineSeconds. - Canary deployments — Kubernetes: chia traffic theo tỉ lệ replica.
- Pod readiness — Kubernetes:
readinessGates. - HTTP traffic splitting — Gateway API:
backendRefscó weight. - Canary, Analysis và BlueGreen — Argo Rollouts: các bước, xấp xỉ theo replica, abort.
- Deployment strategies — Flagger: các chiến lược và trường analysis.
- Alerting on SLOs — Google SRE Workbook: tham số alert theo burn rate.
- Canarying Releases — Google SRE Workbook: kích thước, thời lượng và metric của canary.