Zero-Downtime Deployment — Blue-Green, Canary và Rolling Update

Posted on: 4/18/2026 12:11:02 AM

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.

$5,600 Chi phí trung bình mỗi phút downtime (Gartner)
52 phút Downtime tối đa/năm cho 99.99% SLA
46x Deploy thường xuyên hơn ở nhóm Elite DevOps (DORA)
7,200+ Lần deploy/ngày tại Amazon (trung bình)

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
  
So sánh luồng deploy truyền thống vs zero-downtime

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
  
Quy trình Rolling Update tuần tự qua 3 pods

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
Luồng Blue-Green Deployment qua 3 giai đoạn

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
Luồng Canary Release với traffic splitting và monitoring

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ầ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
Expand-and-Contract pattern cho database migration an toàn

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
Graceful shutdown sequence trong Kubernetes
// .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?

Startup / Team nhỏ (1-5 devs)
→ Rolling Update. Đơn giản, hiệu quả, Kubernetes mặc định. Focus vào viết health check tốt và graceful shutdown. Đừng over-engineer khi chưa có traffic lớn.
Scale-up / Critical systems
→ Blue-Green. Khi downtime không được phép (fintech, healthcare, e-commerce lớn). Chi phí infrastructure tăng nhưng rollback tức thì là trade-off xứng đáng. AWS ECS và Azure Container Apps hỗ trợ natively.
Enterprise / High-traffic (>10K RPM)
→ Canary Release. Khi blast radius nhỏ là ưu tiên số 1. Cần đầu tư vào service mesh (Istio/Linkerd) và observability stack. Flagger + Prometheus tự động hóa toàn bộ pipeline.
Product-led / Data-driven teams
→ A/B + Canary kết hợp. Khi cần đo lường business impact của mỗi deploy. Feature flags (OpenFeature, LaunchDarkly) kết hợp với Canary cho maximum control.

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