Bỏ qua để đến nội dung
Knowledge Hub

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

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ì?

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…

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.

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.

Rolling update 10 Pod checkout: readiness và minReadySeconds chặn từng đợt
Rolling update 10 Pod checkout: readiness và minReadySeconds chặn từng đợtBước 1/6: Trước deploy: 10 Pod v1 ready; Service đưa toàn bộ traffic checkout vào v1.DeploymentcontrollerReplicaSet v110 readyReplicaSet v2ServiceEndpointSlice10 endpoint
Bước 1/6

Trước deploy: 10 Pod v1 ready; Service đưa toàn bộ traffic checkout vào v1.

  1. Controller chỉ scale v1 xuống trong giới hạn còn giữ được 8 Pod available.
  2. Pod mới nhận traffic khi readiness probe pass và mọi điều kiện trong readinessGates là True (ví dụ load balancer controller xác nhận đã đăng ký).
  3. 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.
  4. 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 production
apiVersion: v1
kind: Service
metadata:
name: checkout
spec:
selector:
app: checkout
slot: green # trước đó: blue
ports:
- port: 80
targetPort: 3000

Rollback 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.

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/v1alpha1
kind: Rollout
metadata:
name: checkout
spec:
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/v1alpha1
kind: AnalysisTemplate
metadata:
name: checkout-error-rate
spec:
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.

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 version hoặc track và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.
Chạy demo (cần Docker và kubectl)
Terminal window
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=traefik
sed -i.bak 's#127.0.0.1:6443#127.0.0.1:16443#' k3s/kubeconfig.yaml
export KUBECONFIG=$PWD/k3s/kubeconfig.yaml
kubectl apply -f examples/src/deployment-strategies/checkout-deployment.yaml
kubectl 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=v2

Output, rút gọn (thời gian thay đổi theo lần chạy):

t=0s total/updated/available=10/10/10 | v1=10
t=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=10

Version lỗi, rồi undo:

Terminal window
kubectl set env deploy/checkout VERSION=v3 READY_FAIL=1
kubectl rollout status deploy/checkout
# error: deployment "checkout" exceeded its progress deadline
kubectl rollout undo deploy/checkout # về v2, 6 s ở lần chạy này

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.
  1. 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).
  2. Đặt minReadySeconds để Pod crash ngay sau khi vừa ready sẽ chặn rollout.
  3. Hạ progressDeadlineSeconds về con số thực tế và cho pipeline chạy kubectl rollout undo khi gặp ProgressDeadlineExceeded.
  4. Mọi migration trong bản release đều tương thích ngược; bước phá huỷ để sang release sau.
  5. Metric request có label version/track; dashboard và alert so canary với stable.
  6. Tiêu chí abort (error rate, p99, một metric nghiệp vụ) được viết ra trước khi bắt đầu rollout.
  7. Diễn tập rollback trên staging và bấm giờ, gồm cả việc tắt feature flag.

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

Terminal window
kubectl apply -f checkout.yaml # pipeline báo thành công rồi thoát

Nên viết

Terminal window
kubectl apply -f checkout.yaml
kubectl rollout status deploy/checkout --timeout=5m \
|| kubectl rollout undo deploy/checkout

Vì 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
  1. Với 20 replica và maxSurge/maxUnavailable mặc định, số Pod tối đa và số Pod available tối thiểu trong lúc rollout là bao nhiêu?
  2. 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?
  3. Canary đã phát 4.000 event định dạng mới trước khi bị abort. Rollback Pod không sửa được gì?