Zero-Downtime Deployment — Blue-Green, Canary và Rolling Update
Posted on: 4/18/2026 12:11:02 AM
Table of contents
- 1. Tại sao cần Zero-Downtime Deployment?
- 2. Rolling Update — Đơn giản và phổ biến nhất
- 3. Blue-Green Deployment — Chuyển đổi tức thì, rollback 1 giây
- 4. Canary Release — Deploy an toàn với kiểm soát % traffic
- 5. A/B Testing Deployment — Deploy dựa trên user segments
- 6. So sánh tổng hợp
- 7. Database Migration trong Zero-Downtime
- 8. Health Check — Nền tảng của mọi chiến lược
- 9. Graceful Shutdown — Yếu tố thường bị bỏ quên
- 10. Chiến lược nào cho team bạn?
- Tham khảo
Mỗi lần deploy là một lần hồi hộp. Hệ thống sập 5 phút có thể mất hàng nghìn đơn hàng, hàng triệu đồng doanh thu, và quan trọng hơn — mất niềm tin của người dùng. Trong thế giới mà 99.99% uptime (tương đương chỉ ~52 phút downtime/năm) là kỳ vọng tối thiểu, hiểu và áp dụng đúng chiến lược zero-downtime deployment không còn là "nice to have" mà là yêu cầu bắt buộc.
Bài viết này sẽ phân tích sâu 4 chiến lược deploy phổ biến nhất: Rolling Update, Blue-Green Deployment, Canary Release, và A/B Testing Deployment. Mỗi chiến lược phù hợp với hoàn cảnh khác nhau — không có "one size fits all".
1. Tại sao cần Zero-Downtime Deployment?
Trước khi đi vào từng chiến lược, hãy hiểu rõ vì sao traditional deployment (stop → deploy → start) không còn phù hợp:
graph LR
A[🔴 Traditional Deploy] --> B[Stop Server]
B --> C[Copy Files]
C --> D[Run Migrations]
D --> E[Start Server]
E --> F[Health Check]
G[🟢 Zero-Downtime] --> H[Deploy New Version]
H --> I[Health Check New]
I --> J[Switch Traffic]
J --> K[Drain Old Connections]
style A fill:#e94560,stroke:#fff,color:#fff
style G fill:#4CAF50,stroke:#fff,color:#fff
style B fill:#f8f9fa,stroke:#e0e0e0,color:#2c3e50
style C fill:#f8f9fa,stroke:#e0e0e0,color:#2c3e50
style D fill:#f8f9fa,stroke:#e0e0e0,color:#2c3e50
style E fill:#f8f9fa,stroke:#e0e0e0,color:#2c3e50
style F fill:#f8f9fa,stroke:#e0e0e0,color:#2c3e50
style H fill:#f8f9fa,stroke:#e0e0e0,color:#2c3e50
style I fill:#f8f9fa,stroke:#e0e0e0,color:#2c3e50
style J fill:#f8f9fa,stroke:#e0e0e0,color:#2c3e50
style K fill:#f8f9fa,stroke:#e0e0e0,color:#2c3e50
Với traditional deployment, khoảng thời gian từ bước "Stop Server" đến "Health Check" hoàn tất có thể kéo dài từ vài giây đến vài phút. Trong khoảng đó, mọi request đều thất bại. Zero-downtime đảm bảo luôn có ít nhất một phiên bản healthy đang phục vụ traffic.
Điều kiện tiên quyết
Để áp dụng bất kỳ chiến lược zero-downtime nào, ứng dụng cần đáp ứng: (1) Backward-compatible database migrations — version mới và cũ phải cùng đọc/ghi được DB, (2) Health check endpoint — load balancer cần biết instance nào healthy, (3) Graceful shutdown — xử lý xong request đang chạy trước khi tắt, (4) Stateless application — hoặc sticky sessions nếu bắt buộc stateful.
2. Rolling Update — Đơn giản và phổ biến nhất
Cách hoạt động
Rolling Update thay thế từng instance cũ bằng instance mới, tuần tự hoặc theo batch. Tại mọi thời điểm, luôn có đủ số instance phục vụ traffic (trừ số đang được cập nhật).
sequenceDiagram
participant LB as Load Balancer
participant P1 as Pod 1 (v1)
participant P2 as Pod 2 (v1)
participant P3 as Pod 3 (v1)
Note over LB,P3: Bước 1: Bắt đầu Rolling Update
LB->>P1: Drain connections
Note over P1: Terminate v1, Start v2
P1-->>LB: Health check OK (v2)
Note over LB,P3: Bước 2: Tiếp tục pod tiếp theo
LB->>P2: Drain connections
Note over P2: Terminate v1, Start v2
P2-->>LB: Health check OK (v2)
Note over LB,P3: Bước 3: Pod cuối cùng
LB->>P3: Drain connections
Note over P3: Terminate v1, Start v2
P3-->>LB: Health check OK (v2)
Note over LB,P3: ✅ Hoàn tất - tất cả đều v2
Cấu hình Kubernetes
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-api
spec:
replicas: 4
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1 # Tối đa thêm 1 pod mới cùng lúc
maxUnavailable: 1 # Tối đa 1 pod không khả dụng
template:
spec:
containers:
- name: api
image: myapp:2.0
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 15"]
terminationGracePeriodSeconds: 30
Mẹo quan trọng: maxSurge vs maxUnavailable
maxSurge: 25% + maxUnavailable: 0 là cấu hình an toàn nhất — luôn đảm bảo 100% capacity. Tuy nhiên cần thêm resource overhead cho surge pods. Nếu cluster có resource constraints, dùng maxSurge: 0 + maxUnavailable: 1 để tiết kiệm nhưng giảm capacity tạm thời.
✅ Ưu điểm
- Cấu hình đơn giản, mặc định trong Kubernetes
- Không cần gấp đôi infrastructure
- Tự động rollback nếu health check fail
- Phù hợp hầu hết ứng dụng stateless
⚠️ Nhược điểm
- Trong quá trình update, 2 version chạy song song
- Không thể rollback tức thì — phải rolling lại
- Khó kiểm soát % traffic cho version mới
- Database migration phải backward-compatible
3. Blue-Green Deployment — Chuyển đổi tức thì, rollback 1 giây
Cách hoạt động
Duy trì 2 môi trường production giống hệt nhau: Blue (đang phục vụ) và Green (idle). Deploy version mới lên Green, test kỹ, rồi chuyển 100% traffic sang Green. Blue trở thành backup — rollback chỉ cần chuyển lại traffic.
graph TB
subgraph "Trước khi deploy"
U1[Users] --> LB1[Load Balancer]
LB1 --> B1[🔵 Blue - v1.0
ACTIVE]
G1[🟢 Green - v0.9
IDLE]
end
subgraph "Deploy v2.0 lên Green"
U2[Users] --> LB2[Load Balancer]
LB2 --> B2[🔵 Blue - v1.0
ACTIVE]
G2[🟢 Green - v2.0
TESTING]
end
subgraph "Switch traffic"
U3[Users] --> LB3[Load Balancer]
B3[🔵 Blue - v1.0
STANDBY]
LB3 --> G3[🟢 Green - v2.0
ACTIVE]
end
style B1 fill:#2196F3,stroke:#fff,color:#fff
style G1 fill:#f8f9fa,stroke:#e0e0e0,color:#888
style B2 fill:#2196F3,stroke:#fff,color:#fff
style G2 fill:#4CAF50,stroke:#fff,color:#fff
style B3 fill:#f8f9fa,stroke:#e0e0e0,color:#888
style G3 fill:#4CAF50,stroke:#fff,color:#fff
style LB1 fill:#2c3e50,stroke:#fff,color:#fff
style LB2 fill:#2c3e50,stroke:#fff,color:#fff
style LB3 fill:#2c3e50,stroke:#fff,color:#fff
style U1 fill:#e94560,stroke:#fff,color:#fff
style U2 fill:#e94560,stroke:#fff,color:#fff
style U3 fill:#e94560,stroke:#fff,color:#fff
Triển khai với AWS ECS
{
"deploymentController": {
"type": "CODE_DEPLOY"
},
"deploymentConfiguration": {
"deploymentCircuitBreaker": {
"enable": true,
"rollback": true
}
}
}
AWS CodeDeploy với ECS hỗ trợ Blue-Green natively. Cấu hình trong appspec.yaml:
version: 0.0
Resources:
- TargetService:
Type: AWS::ECS::Service
Properties:
TaskDefinition: "arn:aws:ecs:region:account:task-def/myapp:2"
LoadBalancerInfo:
ContainerName: "web-api"
ContainerPort: 8080
Hooks:
- BeforeAllowTraffic: "LambdaFunctionForValidation"
- AfterAllowTraffic: "LambdaFunctionForSmokeTest"
Triển khai với Nginx (self-managed)
upstream blue {
server 10.0.1.10:8080;
server 10.0.1.11:8080;
}
upstream green {
server 10.0.2.10:8080;
server 10.0.2.11:8080;
}
# Sử dụng map để chuyển traffic
map $cookie_deployment $backend {
default blue; # ← Đổi thành "green" khi switch
"green" green;
}
server {
listen 80;
location / {
proxy_pass http://$backend;
proxy_set_header X-Deployment-Target $backend;
}
}
Chi phí gấp đôi infrastructure
Blue-Green yêu cầu duy trì 2 bộ infrastructure giống hệt nhau. Với cloud, có thể tối ưu bằng cách chỉ spin up Green environment khi cần deploy, rồi tear down sau khi xác nhận ổn định (thường 1-24 giờ). AWS ECS, Azure Container Apps đều hỗ trợ mô hình này natively.
✅ Ưu điểm
- Rollback tức thì (chuyển lại traffic)
- Test production-like trước khi switch
- Không có mixed-version trong production
- Đơn giản về mặt concept
⚠️ Nhược điểm
- Chi phí infrastructure gấp đôi
- Database migration phức tạp (cả 2 env cùng DB)
- Không có gradual rollout — 100% traffic chuyển cùng lúc
- Session persistence cần xử lý cẩn thận
4. Canary Release — Deploy an toàn với kiểm soát % traffic
Cách hoạt động
Thuật ngữ "canary" lấy từ việc thợ mỏ mang theo chim hoàng yến để phát hiện khí độc sớm. Tương tự, Canary Release gửi một phần nhỏ traffic (1-5%) đến version mới. Nếu metrics tốt → tăng dần. Nếu có vấn đề → rollback ngay, chỉ ảnh hưởng số ít người dùng.
graph LR
U[Users
100%] --> LB[Load Balancer
Traffic Split]
LB -->|95%| V1[Version 1.0
Stable]
LB -->|5%| V2[Version 2.0
Canary]
V2 --> M[Metrics
Monitoring]
M -->|OK| INC[Tăng lên 25%
→ 50% → 100%]
M -->|Lỗi| RB[Rollback
0% canary]
style U fill:#e94560,stroke:#fff,color:#fff
style LB fill:#2c3e50,stroke:#fff,color:#fff
style V1 fill:#2196F3,stroke:#fff,color:#fff
style V2 fill:#4CAF50,stroke:#fff,color:#fff
style M fill:#f8f9fa,stroke:#e94560,color:#2c3e50
style INC fill:#4CAF50,stroke:#fff,color:#fff
style RB fill:#e94560,stroke:#fff,color:#fff
Cấu hình Canary trên Kubernetes với Istio
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: web-api
spec:
hosts:
- web-api.example.com
http:
- route:
- destination:
host: web-api
subset: stable
weight: 95
- destination:
host: web-api
subset: canary
weight: 5
---
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: web-api
spec:
host: web-api
subsets:
- name: stable
labels:
version: v1
- name: canary
labels:
version: v2
Automated Canary với Flagger
Flagger là công cụ progressive delivery cho Kubernetes, tự động hóa toàn bộ quy trình canary dựa trên metrics:
apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
name: web-api
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: web-api
service:
port: 8080
analysis:
# Tăng 10% mỗi interval nếu metrics đạt
interval: 1m
threshold: 5 # Số lần fail trước khi rollback
maxWeight: 50 # Tối đa 50% traffic cho canary
stepWeight: 10 # Tăng 10% mỗi bước
metrics:
- name: request-success-rate
thresholdRange:
min: 99 # Yêu cầu 99%+ success rate
interval: 1m
- name: request-duration
thresholdRange:
max: 500 # P99 latency < 500ms
interval: 1m
webhooks:
- name: smoke-test
type: pre-rollout
url: http://flagger-loadtester/
metadata:
cmd: "curl -s http://web-api-canary:8080/health"
Canary Metrics cần theo dõi
Bốn metrics quan trọng nhất cho canary analysis (theo DORA framework): Error Rate — tỷ lệ 5xx/4xx so với stable, Latency P99 — phải tương đương hoặc tốt hơn stable, Throughput — canary xử lý được tương đương traffic/instance, Saturation — CPU/Memory không spike bất thường. Nếu bất kỳ metric nào vượt ngưỡng → rollback tự động.
✅ Ưu điểm
- Rủi ro thấp — chỉ % nhỏ user bị ảnh hưởng
- Phát hiện lỗi production sớm với real traffic
- Có thể tự động rollback dựa trên metrics
- Phù hợp cho large-scale systems
⚠️ Nhược điểm
- Cần service mesh hoặc advanced load balancer
- Setup monitoring pipeline phức tạp
- Deploy chậm hơn (phải chờ qua từng bước)
- 2 version chạy song song — DB migration phải compatible
5. A/B Testing Deployment — Deploy dựa trên user segments
Cách hoạt động
Khác với Canary (random traffic split), A/B Testing Deployment route traffic dựa trên user attributes: location, device, user ID, subscription tier... Mục đích vừa deploy an toàn, vừa đo lường impact của feature mới.
# Istio VirtualService với header-based routing
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: web-api
spec:
hosts:
- web-api.example.com
http:
# Route internal team đến version mới
- match:
- headers:
x-user-tier:
exact: "internal"
route:
- destination:
host: web-api
subset: v2
# Route premium users
- match:
- headers:
x-user-tier:
exact: "premium"
route:
- destination:
host: web-api
subset: v2
# Còn lại → stable
- route:
- destination:
host: web-api
subset: v1
6. So sánh tổng hợp
| Tiêu chí | Rolling Update | Blue-Green | Canary Release | A/B Testing |
|---|---|---|---|---|
| Độ phức tạp setup | ⭐ Thấp | ⭐⭐ Trung bình | ⭐⭐⭐ Cao | ⭐⭐⭐ Cao |
| Tốc độ rollback | Chậm (phút) | Tức thì (giây) | Nhanh (giây) | Nhanh (giây) |
| Chi phí infrastructure | Thấp nhất | Gấp đôi | +10-50% | +10-50% |
| Blast radius khi lỗi | ~25-50% users | 100% hoặc 0% | 1-10% users | Segment cụ thể |
| Mixed versions | Có (tạm thời) | Không | Có | Có |
| Cần Service Mesh | Không | Không | Nên có | Bắt buộc |
| Phù hợp nhất cho | Hầu hết ứng dụng | Critical systems, ít deploy | Large-scale, deploy thường xuyên | Feature experiments |
7. Database Migration trong Zero-Downtime
Đây là phần khó nhất. Khi 2 version chạy song song (Rolling, Canary), cả v1 và v2 phải tương thích với cùng một database schema. Quy tắc vàng: Expand-and-Contract pattern.
graph TB
subgraph "Phase 1: Expand"
E1[Deploy v2 code] --> E2[Add new column
nullable/default]
E2 --> E3[v1 và v2 đều chạy OK
v2 bắt đầu ghi column mới]
end
subgraph "Phase 2: Migrate"
M1[Backfill data
cho column mới] --> M2[v2 đọc/ghi column mới]
M2 --> M3[Retire v1 hoàn toàn]
end
subgraph "Phase 3: Contract"
C1[Drop column cũ] --> C2[Remove code xử lý cũ]
C2 --> C3[Schema clean]
end
E3 --> M1
M3 --> C1
style E1 fill:#2196F3,stroke:#fff,color:#fff
style E2 fill:#2196F3,stroke:#fff,color:#fff
style E3 fill:#2196F3,stroke:#fff,color:#fff
style M1 fill:#4CAF50,stroke:#fff,color:#fff
style M2 fill:#4CAF50,stroke:#fff,color:#fff
style M3 fill:#4CAF50,stroke:#fff,color:#fff
style C1 fill:#e94560,stroke:#fff,color:#fff
style C2 fill:#e94560,stroke:#fff,color:#fff
style C3 fill:#e94560,stroke:#fff,color:#fff
Ví dụ: Đổi tên column
Muốn đổi user_name thành display_name? Không thể đơn giản ALTER TABLE RENAME COLUMN — v1 sẽ crash ngay. Thay vào đó:
-- Phase 1: Expand (deploy trước code change)
ALTER TABLE users ADD COLUMN display_name VARCHAR(255);
-- Trigger để sync 2 columns trong transition period
CREATE TRIGGER sync_display_name
BEFORE INSERT OR UPDATE ON users
FOR EACH ROW
BEGIN
IF NEW.display_name IS NULL THEN
SET NEW.display_name = NEW.user_name;
END IF;
IF NEW.user_name IS NULL THEN
SET NEW.user_name = NEW.display_name;
END IF;
END;
-- Phase 2: Backfill existing data
UPDATE users SET display_name = user_name WHERE display_name IS NULL;
-- Phase 3: Sau khi v1 retire hoàn toàn
ALTER TABLE users DROP COLUMN user_name;
DROP TRIGGER sync_display_name;
Không bao giờ trong cùng 1 deploy
Mỗi phase là một lần deploy riêng biệt. Phase 1 deploy trước, chạy ổn định 1-2 ngày. Phase 2 backfill xong. Phase 3 mới deploy code bỏ column cũ. Nhiều team sai lầm khi gộp cả 3 phase vào 1 migration script — đây là nguồn gốc của outage.
8. Health Check — Nền tảng của mọi chiến lược
Không có health check tốt, mọi chiến lược deploy đều vô nghĩa. Health check cần phân biệt 3 trạng thái:
// .NET Minimal API health check example
app.MapHealthChecks("/health/live", new HealthCheckOptions
{
// Liveness: app có đang chạy không?
Predicate = check => check.Tags.Contains("live")
});
app.MapHealthChecks("/health/ready", new HealthCheckOptions
{
// Readiness: app đã sẵn sàng nhận traffic chưa?
Predicate = check => check.Tags.Contains("ready")
});
app.MapHealthChecks("/health/startup", new HealthCheckOptions
{
// Startup: dùng cho slow-starting containers
Predicate = check => check.Tags.Contains("startup")
});
// Register checks
builder.Services.AddHealthChecks()
.AddCheck("self", () => HealthCheckResult.Healthy(), tags: new[] { "live" })
.AddSqlServer(connectionString, tags: new[] { "ready" })
.AddRedis(redisConnection, tags: new[] { "ready" })
.AddCheck<WarmupCheck>("warmup", tags: new[] { "startup" });
# Kubernetes probe configuration
containers:
- name: api
livenessProbe:
httpGet:
path: /health/live
port: 8080
initialDelaySeconds: 10
periodSeconds: 15
failureThreshold: 3 # 3 lần fail → restart container
readinessProbe:
httpGet:
path: /health/ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
failureThreshold: 2 # 2 lần fail → remove khỏi Service
startupProbe:
httpGet:
path: /health/startup
port: 8080
initialDelaySeconds: 0
periodSeconds: 5
failureThreshold: 30 # Cho phép 150s để warmup
9. Graceful Shutdown — Yếu tố thường bị bỏ quên
Khi Kubernetes gửi SIGTERM đến pod, ứng dụng cần:
sequenceDiagram
participant K8s as Kubernetes
participant Pod as Application Pod
participant LB as Service/Endpoint
K8s->>Pod: SIGTERM
K8s->>LB: Remove Pod from Endpoints
Note over Pod: Bắt đầu graceful shutdown
Pod->>Pod: Stop accepting NEW requests
Pod->>Pod: Finish in-flight requests (max 30s)
Pod->>Pod: Close DB connections
Pod->>Pod: Flush logs/metrics
Pod-->>K8s: Exit 0
Note over K8s: Nếu không exit sau
terminationGracePeriodSeconds
→ SIGKILL
// .NET graceful shutdown
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
var lifetime = app.Services.GetRequiredService<IHostApplicationLifetime>();
lifetime.ApplicationStopping.Register(() =>
{
// Đợi in-flight requests hoàn thành
// preStop hook sleep 15s đảm bảo endpoint đã bị remove
Log.Information("Shutting down gracefully...");
});
lifetime.ApplicationStopped.Register(() =>
{
Log.CloseAndFlush();
});
preStop hook: Tại sao cần sleep?
Kubernetes gửi SIGTERM và remove endpoint đồng thời, không tuần tự. Nghĩa là trong vài giây sau SIGTERM, load balancer vẫn có thể gửi traffic đến pod đang shutdown. preStop: sleep 15 cho phép đủ thời gian để endpoint propagation hoàn tất trước khi app thực sự bắt đầu shutdown.
10. Chiến lược nào cho team bạn?
Kết hợp thực tế
Hầu hết hệ thống production không chỉ dùng 1 chiến lược. Pattern phổ biến: Rolling Update cho config changes nhỏ + Canary cho feature releases lớn + Blue-Green cho infrastructure upgrades (Kubernetes version, database engine). Chọn chiến lược phù hợp với mức độ rủi ro của từng thay đổi, không phải áp một công thức cho tất cả.
Tham khảo
- Kubernetes Documentation — Rolling Update Deployment
- Flagger — Progressive Delivery with Istio
- AWS CodeDeploy — Blue/Green Deployments on ECS
- Azure Container Apps — Blue-Green Deployment
- Google Cloud — Canary Deployment Strategy
- Martin Fowler — Blue-Green Deployment
- DORA — Accelerate State of DevOps Report
HTTP/3 và QUIC — Giao thức mạng thế hệ mới tăng tốc Web 2026
Database Sharding — Chiến Lược Phân Mảnh Dữ Liệu Khi Hệ Thống Vượt Ngưỡng
Disclaimer: The opinions expressed in this blog are solely my own and do not reflect the views or opinions of my employer or any affiliated organizations. The content provided is for informational and educational purposes only and should not be taken as professional advice. While I strive to provide accurate and up-to-date information, I make no warranties or guarantees about the completeness, reliability, or accuracy of the content. Readers are encouraged to verify the information and seek independent advice as needed. I disclaim any liability for decisions or actions taken based on the content of this blog.