CD & zero-downtime: rolling, blue-green, canary, GitOps

4 câu hỏi~25 phútTừ khóa: continuous delivery, rolling, blue-green, canary, feature flag, rollback, expand-contract, GitOps, Argo CD, Argo Rollouts

Tiếp đi: CD và deploy không downtime. Mỗi lần deploy order-service là khách đặt hàng bị lỗi 502 mất vài giây.

CI đã cho ta image order-service:3f9c2ab. Bài này đưa image đó lên prod mà khách không nhận ra, và quay lại được nhanh khi hỏng.

1. Bài toán

Hiện tại deploy bằng kubectl set image chạy từ laptop. Lỗi 502 lúc deploy, và tuần trước bản mới có bug tính phí ship, phải 40 phút mới rollback xong vì không ai nhớ bản cũ là tag nào.

2. Vấn đề

3. Giải pháp: chiến lược deploy

Chiến lượcCách làmRollbackChi phí
RollingThay dần pod cũ bằng pod mớiRolling ngược, vài phútThấp (mặc định của Deployment)
Blue-greenDựng đủ bản green song song, chuyển toàn bộ traffic một lầnChuyển lại blue, vài giâyGấp đôi tài nguyên lúc deploy
CanaryCho 5% → 25% → 100% traffic vào bản mới, theo dõi metricĐưa về 0%, ít khách bị ảnh hưởngCần traffic splitting + metric tốt
Feature flagDeploy code nhưng tắt tính năng, bật theo % userTắt flag, không cần deployPhải dọn flag cũ

Cơ chế rolling update (maxSurge, maxUnavailable, kubectl rollout undo) đã có ở bài 17.3. Ở đây tập trung blue-green, canary, feature flag và GitOps.

Deploy ≠ Release: deploy là đưa code lên máy; release là cho người dùng thấy tính năng. Feature flag tách hai việc này ra.

4. Code từng bước

Bước 1: GitOps repo là nguồn sự thật

shop-gitops/
  apps/order-service/
    values-staging.yaml     # image.tag: 3f9c2ab
    values-prod.yaml        # image.tag: 2d1e0aa

CI không kubectl apply vào prod nữa. CI chỉ mở PR đổi image.tag trong repo này.

Bước 2: Argo CD đồng bộ Git → cluster

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: order-service-prod
  namespace: argocd
spec:
  project: shop
  source:
    repoURL: https://github.com/shop/shop-gitops
    path: charts/order-service
    helm:
      valueFiles: [../../apps/order-service/values-prod.yaml]
  destination:
    server: https://kubernetes.default.svc
    namespace: shop
  syncPolicy:
    automated: { prune: true, selfHeal: true }

Bước 3: canary tự động với Argo Rollouts

apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata: { name: order-service }
spec:
  replicas: 6
  strategy:
    canary:
      steps:
        - setWeight: 10
        - pause: { duration: 5m }
        - analysis: { templates: [ { templateName: error-rate } ] }
        - setWeight: 50
        - pause: { duration: 10m }
  # selector, template giống Deployment
kind: AnalysisTemplate
metadata: { name: error-rate }
spec:
  metrics:
    - name: http-5xx
      interval: 1m
      failureLimit: 1
      successCondition: result[0] < 0.01
      provider:
        prometheus:
          address: http://prometheus.monitoring:9090
          query: |
            sum(rate(http_server_requests_seconds_count{app="order-service",status=~"5..",rollouts_pod_template_hash="{{args.canary-hash}}"}[2m]))
            / sum(rate(http_server_requests_seconds_count{app="order-service",rollouts_pod_template_hash="{{args.canary-hash}}"}[2m]))

Tỉ lệ 5xx của canary vượt 1% thì Rollouts tự abort, traffic quay về bản cũ.

Bước 4: feature flag trong Java

@Service
@RequiredArgsConstructor
public class ShippingFeeService {
    private final FeatureFlags flags;           // wrapper quanh Unleash/OpenFeature
    private final LegacyFeeCalculator legacy;
    private final ZoneFeeCalculator zone;

    public Money fee(Order order) {
        return flags.isEnabled("zone-shipping-fee", order.customerId())
                ? zone.calculate(order)
                : legacy.calculate(order);
    }
}
curl -s https://api.shop.example/actuator/info | jq .git.commit.id   # kiểm tra bản đang chạy

5. Cách hoạt động

dev merge ─► CI build image :3f9c2ab ─► CI mở PR vào shop-gitops (tag=3f9c2ab)
                                               │ review, merge
                                               ▼
                         Argo CD (trong cluster) pull repo, thấy khác ─► sync
                                               │
                    Argo Rollouts: 10% ──5m──► analysis OK? ──► 50% ──► 100%
                                               │ không OK
                                               ▼
                                         abort, về bản cũ
Rollback thủ công = git revert commit đổi tag → Argo CD sync lại.

Blue-green trên Kubernetes thực chất là đổi selector của Service từ version: blue sang version: green:

             Service order-service (selector: version=green)
                  │
   blue (3 pod, 2d1e0aa)  ← để nguyên 30 phút để quay lại
   green (3 pod, 3f9c2ab) ← nhận 100% traffic

6. Database migration không downtime

Mọi chiến lược trên đều có lúc bản cũ và bản mới chạy cùng lúc trên cùng một database. Vì vậy migration phải tương thích ngược, theo kiểu expand → migrate → contract, chi tiết ở bài 4.3.12. Tóm tắt luật cho CD:

7. Khi nào dùng gì

Có Argo CD rồi, mà lỡ ai đó kubectl edit sửa trực tiếp replicas trên prod thì chuyện gì xảy ra?

Đúng chỗ mạnh của GitOps. Với selfHeal: true, Argo CD thấy trạng thái cluster lệch Git (OutOfSync) và tự đưa về như Git, thường trong vài phút. Sửa tay sẽ bị ghi đè.

Feature flag nghe hay, vậy cứ bọc mọi thay đổi vào flag cho an toàn được không?

Không nên. Flag là nợ kỹ thuật có hạn dùng:

Dùng flag cho tính năng nghiệp vụ lớn hoặc rủi ro. Mỗi flag có owner và ngày hết hạn, tạo ticket xóa ngay khi bật 100%. Bugfix và refactor nhỏ thì cứ rolling/canary bình thường.

Phỏng vấn hỏi về deploy không downtime thì trả lời sao?

Q1. So sánh rolling, blue-green và canary.

🇻🇳 Tiếng Việt:

Rolling thay dần pod, rẻ, nhưng hai bản cùng chạy và rollback mất vài phút. Blue-green dựng đủ bản mới rồi chuyển toàn bộ traffic, rollback vài giây nhưng tốn gấp đôi tài nguyên. Canary cho một phần nhỏ traffic vào bản mới, đo error rate và latency rồi tăng dần, giới hạn số người dùng bị ảnh hưởng nếu lỗi.

🇬🇧 English:

Rolling replaces pods gradually; it is cheap, but both versions run together and rollback takes minutes. Blue-green brings up a full new environment and switches all traffic at once; rollback takes seconds but needs double capacity. Canary sends a small share of traffic to the new version, watches error rate and latency, then increases it, which limits the blast radius.

Q2. Rollback app thì database xử lý thế nào?

🇻🇳 Tiếng Việt:

Không rollback schema. Em thiết kế migration theo expand-contract để bản cũ và bản mới đều chạy được trên cùng schema. Nhờ vậy rollback chỉ là đổi image về tag cũ. Thay đổi phá vỡ (xóa cột) chỉ làm ở release sau, khi chắc chắn không quay lại nữa.

🇬🇧 English:

We don't roll back the schema. Migrations follow expand and contract, so both old and new versions work on the same schema, and rolling back is just pointing to the previous image tag. Breaking changes such as dropping a column happen in a later release, once we are sure we won't go back.

Q3. GitOps là gì? Lợi ích so với CI push thẳng vào cluster?

🇻🇳 Tiếng Việt:

Git là nguồn sự thật cho trạng thái mong muốn của cluster. Agent như Argo CD trong cluster pull Git và đồng bộ, tự sửa drift. CI không cần credential vào prod, mọi thay đổi có review và lịch sử, rollback bằng git revert.

🇬🇧 English:

Git is the source of truth for the desired cluster state. An in-cluster agent like Argo CD pulls from Git, syncs and corrects drift. CI no longer needs production credentials, every change is reviewed and auditable, and rollback is a git revert.

Cách nhớ ngắn gọn


Tự kiểm tra

Blue-green trên Kubernetes chuyển traffic bằng cách nào đơn giản nhất?

Đổi label selector của Service từ bản blue sang bản green.

Canary tự rollback dựa trên gì?

Metric của bản canary (error rate, latency) so với ngưỡng, qua analysis của Argo Rollouts/Flagger.

Vì sao không được xóa cột trong cùng release với code ngừng dùng cột đó?

Trong lúc deploy bản cũ vẫn chạy và còn đọc cột đó; rollback cũng cần cột đó.

Argo CD và HPA "giành nhau" replicas khi nào, xử lý ra sao?

Khi manifest có field replicas và selfHeal bật. Bỏ replicas khỏi manifest hoặc dùng ignoreDifferences.

Feature flag service sập thì app nên làm gì?

Dùng giá trị mặc định an toàn được cache sẵn, thường là tắt tính năng mới.