CD & zero-downtime: rolling, blue-green, canary, GitOps
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 đề
- 502 khi deploy: pod cũ bị kill khi còn request đang xử lý, hoặc pod mới nhận traffic trước khi sẵn sàng. Phần này là graceful shutdown + readiness probe, đã học ở bài 17.2 và bài 16.6.
- Bản mới lỗi thì 100% khách dính cùng lúc.
- Rollback thủ công, không có nguồn sự thật "prod đang chạy gì".
- Database migration đi cùng deploy, rollback code thì DB không rollback theo.
3. Giải pháp: chiến lược deploy
| Chiến lược | Cách làm | Rollback | Chi phí |
|---|---|---|---|
| Rolling | Thay dần pod cũ bằng pod mới | Rolling ngược, vài phút | Thấp (mặc định của Deployment) |
| Blue-green | Dựng đủ bản green song song, chuyển toàn bộ traffic một lần | Chuyển lại blue, vài giây | Gấp đôi tài nguyên lúc deploy |
| Canary | Cho 5% → 25% → 100% traffic vào bản mới, theo dõi metric | Đưa về 0%, ít khách bị ảnh hưởng | Cần traffic splitting + metric tốt |
| Feature flag | Deploy code nhưng tắt tính năng, bật theo % user | Tắt flag, không cần deploy | Phả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:
- Release N: chỉ thêm (cột nullable, bảng mới). Code ghi cả hai.
- Release N+1: đọc cột mới, backfill.
- Release N+2: mới được xóa cột cũ.
- Rollback code chỉ an toàn trong phạm vi một bước expand.
7. Khi nào dùng gì
- Rolling: mặc định cho đa số service stateless, thay đổi nhỏ.
- Canary: service quan trọng (order, payment), có metric đủ tốt để quyết định tự động.
- Blue-green: cần chuyển nguyên khối, kiểm tra smoke test trên green trước khi mở; chấp nhận tốn gấp đôi.
- Feature flag: tính năng nghiệp vụ lớn, cần bật theo nhóm user, hoặc tắt khẩn cấp.
- Không dùng canary khi traffic quá ít (5% của 10 request/phút không nói lên 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 đè.
- Muốn đổi thật thì sửa trong Git, có review, có lịch sử.
- Ngoại lệ hay gặp: HPA tự đổi replicas. Phải bỏ
replicaskhỏi manifest hoặc cấu hìnhignoreDifferencescho field đó, không thì Argo CD và HPA giành nhau. - Kết hợp RBAC: người thường không có quyền ghi vào namespace prod, chỉ Argo CD có.
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:
- Mỗi flag nhân đôi số đường code phải test; 10 flag là 1024 tổ hợp.
- Flag quên dọn tích tụ thành "code chết nhưng vẫn chạy được".
- Flag service sập thì sao? Phải có giá trị mặc định an toàn (thường là tắt).
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
- Rolling rẻ, blue-green nhanh quay lại, canary giới hạn thiệt hại.
- Deploy ≠ release; feature flag tách hai việc, nhưng phải dọn.
- Hai bản cùng chạy → migration expand-contract.
- GitOps: Git là sự thật, Argo CD pull và selfHeal.
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.