Chiến lược Deployment — tradeoff giữa các cách
Tài liệu này so sánh các chiến lược deploy (triển khai) phổ biến, để chọn đúng cách cho từng bài toán và để trả lời phỏng vấn có chiều sâu thay vì chỉ nêu tên. Bối cảnh áp dụng: ứng dụng Laravel chạy trong Docker, deploy qua GitLab CI/CD, đứng sau Nginx (theo đúng tech stack trong CV).
Bảng so sánh nhanh
| Chiến lược | Downtime (thời gian gián đoạn) | Chi phí hạ tầng | Tốc độ rollback (quay lại bản cũ) | Độ phức tạp |
|---|---|---|---|---|
| Recreate | Có (vài giây–vài phút) | Thấp (1 bản) | Chậm (deploy lại bản cũ) | Thấp |
| Rolling | Gần như 0 | Thấp–trung bình | Trung bình | Trung bình |
| Blue-Green | 0 | Cao (2 bản chạy song song) | Tức thì (đổi lại traffic) | Trung bình |
| Canary | 0 | Trung bình–cao | Nhanh (cắt traffic canary) | Cao |
1. Recreate (dừng bản cũ, chạy bản mới)
Cách làm: dừng hẳn container/process cũ, deploy bản mới, khởi động lại.
- Downtime (gián đoạn dịch vụ): có, bằng thời gian container mới start + warm up (khởi động và làm nóng — Laravel cache config, autoload, kết nối DB/Redis).
- Ưu điểm: đơn giản nhất, không cần 2 bản chạy song song → không lo schema DB bị 2 version code cùng đọc/ghi khác nhau.
- Khi nào dùng: hệ thống nội bộ, ít traffic, cửa sổ bảo trì (maintenance window) chấp nhận được, hoặc môi trường staging.
- Không hợp: hệ thống production có SLA uptime (cam kết % thời gian hệ thống phải hoạt động), user đang thao tác giữa chừng (mất session, request đang chạy bị cắt ngang).
2. Rolling deployment (cập nhật dần từng instance)
Cách làm: có nhiều instance (nhiều bản container chạy song song, gọi là replica) chạy cùng version. Cập nhật lần lượt từng instance — dừng 1, deploy bản mới, đợi healthcheck (kiểm tra sức khoẻ — endpoint xác nhận app đã sẵn sàng nhận traffic) pass, rồi mới sang instance kế tiếp. Traffic luôn có ít nhất 1 instance phục vụ.
- Downtime: gần như 0 nếu healthcheck đủ tốt và Nginx/load balancer (bộ cân bằng tải) chỉ route (định tuyến) vào instance đã pass.
- Rủi ro chính: trong lúc rolling, 2 version code chạy song song — cùng đọc/ghi 1 database. Nếu migration đổi schema không tương thích ngược (breaking), bản cũ sẽ lỗi. → bắt buộc theo expand-contract pattern (thêm cột mới trước, deploy code đọc được cả 2, xoá cột cũ ở lần deploy sau).
- Chi phí: không cần gấp đôi hạ tầng, chỉ cần ≥2 instance đã có sẵn cho high-availability (tính sẵn sàng cao).
- Khi nào dùng: đây là lựa chọn mặc định hợp lý cho phần lớn ứng dụng Laravel production có từ 2 instance trở lên — cân bằng tốt giữa chi phí và độ an toàn.
- Cần orchestrator hỗ trợ: Kubernetes, Docker Swarm hay ECS mới thay được
lần lượt từng instance. Docker Compose thuần không làm được — nó đọc
deploy.replicasnhưng bỏ quadeploy.update_config, nêndocker compose up -ddừng hết container cũ rồi mới dựng bản mới. Trên 1 VPS chỉ có Compose thì phải chọn Blue-Green (mục 3) thay vì rolling.
3. Blue-Green deployment
Cách làm: giữ 2 môi trường độc lập, giống hệt nhau về hạ tầng — Blue (đang chạy, nhận traffic thật) và Green (bản mới, chưa nhận traffic). Deploy và test kỹ trên Green, sau đó đổi traffic (đổi upstream Nginx, hoặc đổi DNS/load balancer) từ Blue sang Green trong 1 lần.
- Downtime: gần như 0 — thời điểm switch chỉ là đổi con trỏ traffic.
- Rollback: cực nhanh — traffic lỗi thì trỏ ngược lại Blue ngay, không cần deploy lại.
- Chi phí: gấp đôi hạ tầng trong lúc chạy song song (dù chỉ tạm thời). "Hạ tầng" ở đây không bắt buộc là 2 máy: Blue và Green có thể là 2 container trên cùng 1 VPS, chỉ nhân đôi container app còn database/Redis dùng chung. Đổi lại, cách này không bảo vệ khi bản thân VPS chết.
- Rủi ro chính: giống rolling — nếu Blue và Green dùng chung 1 database, migration vẫn phải backward-compatible vì có khoảnh khắc cả 2 bản cùng tồn tại (ít nhất là để rollback được).
- Khi nào dùng: hệ thống cần rollback tức thì, chấp nhận trả thêm chi phí hạ tầng tạm thời — ví dụ release lớn, rủi ro cao, giờ cao điểm không được lỗi.
4. Canary deployment
Cách làm: đưa bản mới ra cho một phần nhỏ traffic thật (vd 5%), theo dõi metrics/error rate (đây là lúc Prometheus + Grafana phát huy tác dụng), nếu ổn thì tăng dần % cho tới 100%. Nếu lỗi, cắt canary về 0% ngay.
- Downtime: 0.
- Ưu điểm lớn nhất: giới hạn blast radius (phạm vi ảnh hưởng khi có lỗi) — chỉ 5% user gặp bug thay vì 100%. Phát hiện lỗi thật (do traffic thật, edge case thật) mà staging không bắt được.
- Độ phức tạp: cao nhất trong 4 cách — cần định tuyến traffic theo % (Nginx split_clients, service mesh, hoặc API gateway), cần dashboard theo dõi so sánh canary vs bản cũ theo thời gian thực.
- Khi nào dùng: hệ thống lớn, traffic cao, đội ngũ đã có sẵn observability (logging, metrics, alerting) để đọc được tín hiệu canary có ổn hay không. Với team nhỏ/hạ tầng đơn giản, chi phí vận hành canary thường không đáng — rolling hoặc blue-green đã đủ.
- Thực hành cụ thể: xem canary-laravel-livewire.md — gồm cả phần khó riêng của Livewire (snapshot state đi kèm mỗi request nên bắt buộc phải ghim user vào đúng một version).
Chọn cách nào?
flowchart TD
A{"Chấp nhận downtime<br/>vài giây–vài phút?"}
A -->|"Có — nội bộ, ít traffic"| R["Recreate<br/>đơn giản nhất, 1 bản"]
A -->|"Không"| B{"Có orchestrator?<br/>(K8s / Swarm, ≥2 instance)"}
B -->|"Không — 1 VPS + Docker Compose"| BG["Blue-Green<br/>rollback tức thì, tốn x2 RAM"]
B -->|"Có"| C{"Có observability đủ tốt?<br/>(metric tách theo pool, alert)"}
C -->|"Chưa"| RO["Rolling<br/>mặc định hợp lý"]
C -->|"Có, và release rủi ro cao"| CA["Canary<br/>rủi ro thấp nhất, vận hành nặng nhất"]
- Team nhỏ, 1 VPS, ứng dụng nội bộ → Recreate + cửa sổ bảo trì ngắn là đủ, không cần over-engineer.
- Ứng dụng Laravel production tiêu chuẩn, đã có ≥2 instance sau load balancer và có orchestrator (K8s/Swarm) → Rolling là mặc định hợp lý.
- 1 VPS chạy Docker Compose, hoặc release rủi ro cao cần rollback tức thì → Blue-Green.
- Traffic lớn, đã có observability, muốn giảm rủi ro release ở mức tối đa → Canary.
Điểm chung của cả 4 cách khi có database dùng chung: migration phải backward-compatible trong lúc có 2 version code cùng tồn tại. Đây là điều hay bị bỏ qua và là nguồn lỗi production phổ biến nhất khi làm zero-downtime deploy.
5. Khó khăn khi vận hành thật (cả 4 cách đều dính)
Chọn được chiến lược mới xong một nửa. Phần còn lại là mấy chỗ hay làm hỏng đêm deploy:
- Healthcheck dối.
/healthtrả200 OKtĩnh thì container chưa sẵn sàng vẫn được nhận traffic → user ăn lỗi trong lúc pipeline báo xanh. Healthcheck phải chạm DB + Redis. Nhưng đừng chạm dịch vụ ngoài (payment gateway, API đối tác): gateway của họ chậm là container của mình bị đánh dấu unhealthy và bị restart vô cớ. - Graceful shutdown (tắt êm — để request đang chạy kịp xong). PHP-FPM nhận
SIGTERMphải cóstop_grace_periodđủ dài hơn request chậm nhất. Queue worker thì cần thời gian chờ dài hơn job lâu nhất, không thì job bị giết giữa chừng và chạy lại từ đầu (nguy hiểm nếu job không idempotent — chạy lại cho ra kết quả khác, vd trừ tiền 2 lần). - Warm-up (làm nóng). Container mới có OPcache trắng, connection pool trống → p95 latency cao hơn hẳn trong 1–3 phút đầu. Với rolling/canary, số liệu đầu tiên luôn xấu. Nếu abort theo con số phút đầu, sẽ không bao giờ deploy được cái gì.
- Scheduler và cron chạy 2 lần khi có 2 version cùng sống (rolling, blue-green, canary). Phải ghim scheduler vào đúng 1 chỗ.
.envdrift (lệch cấu hình giữa các môi trường). Staging pass, production fail chỉ vì thiếu 1 biến. Không có cách nào phát hiện bằng test — chỉ có cách quản lý biến ở một nơi (GitLab CI variables / Vault) và diff trước release.- Rollback code thì dễ, rollback dữ liệu thì không. Đây là ranh giới thật của mọi chiến lược: đổi upstream Nginx mất 1 giây, còn 40 phút bản lỗi đã ghi sai vào DB thì không có nút Undo.
- Yếu tố con người. Ai được bấm deploy, deploy giờ nào (đừng deploy 5h chiều thứ Sáu), ai trực nhìn dashboard 30 phút sau đó. Canary mà không có người đọc số thì chỉ là rolling chậm hơn.
6. Case thực tế có thể gặp và cách xử lý
Case 1 — Rolling deploy đang chạy dở, instance bản cũ đồng loạt 500.
Migration release này RENAME một cột. Instance chưa cập nhật vẫn SELECT
tên cũ.
Xử lý ngay: đẩy nốt rolling cho xong thật nhanh (đi tới nhanh hơn quay lại,
vì rollback lúc này còn phải rollback cả schema). Nếu lỗi lan sang tính năng
tính tiền → bật maintenance mode, hoàn tất migrate, tắt maintenance.
Ngăn lần sau: CI chặn merge nếu migration có RENAME/DROP — bắt tách
thành 2 release theo expand-contract.
Case 2 — Blue-green switch xong, user kêu mất giỏ hàng / bị đăng xuất.
SESSION_DRIVER=file, mỗi màu có filesystem riêng.
Xử lý ngay: switch ngược về màu cũ (session cũ vẫn còn ở đó), thông báo, rồi
sửa cấu hình.
Ngăn lần sau: session + cache bắt buộc lên Redis dùng chung trước khi bật
blue-green. Đây là điều kiện, không phải tối ưu.
Case 3 — Canary 5% chạy 20 phút, error rate hai bên bằng nhau, không kết luận được gì. 5% của traffic thấp = vài chục request, phần lớn là bot và healthcheck. Xử lý ngay: lọc bot/healthcheck khỏi metrics, hoặc nâng lên 20–25% để có đủ mẫu, hoặc chấp nhận đây là bài toán không hợp canary. Ngăn lần sau: trước khi làm canary, tính thử: cần bao nhiêu request/phút để 5% cho ra con số có nghĩa. Không đủ → dùng blue-green + feature flag.
Case 4 — Rollback container xong nhưng queue vẫn fail hàng loạt.
Bản mới đã đẩy job với payload kiểu mới (hoặc job class mới) vào Redis; worker
bản cũ không hiểu.
Xử lý ngay: dừng worker, xem failed_jobs, sửa payload hoặc giữ worker ở
bản mới trong khi web về bản cũ (worker và web không bắt buộc cùng version).
Ngăn lần sau: job class mới phải có mặt ở worker từ release trước khi có
code đẩy job đó. Payload chỉ thêm field, không đổi ý nghĩa field cũ.
Case 5 — Chạy 2 pool xong DB báo too many connections.
Mỗi container mở pool riêng; blue-green/canary nhân đôi số connection trong lúc
chồng lấn.
Xử lý ngay: giảm số worker PHP-FPM của màu idle, hoặc tắt sớm màu cũ.
Ngăn lần sau: tính max_connections theo đỉnh lúc chồng lấn, không theo
lúc chạy bình thường; hoặc đặt PgBouncer (connection pooler — gom nhiều
connection app thành ít connection DB) ở giữa.
Case 6 — Deploy "thành công" nhưng trang trắng, console báo 404 file JS. Vite build ra file có hash mới; user đang mở tab cũ xin file hash cũ đã bị xoá. Xử lý ngay: khôi phục thư mục asset bản cũ (hoặc rollback), báo user tải lại trang. Ngăn lần sau: asset các bản để chung một volume/CDN, chỉ dọn sau vài ngày — đừng xoá theo mỗi lần deploy.
7. Câu hỏi phụ interviewer hay hỏi tiếp
"Zero-downtime nghĩa là user không bao giờ thấy lỗi?" Không. Nó chỉ nói dịch vụ luôn có instance phục vụ. User vẫn có thể thấy lỗi do bug bản mới, do session bay, do asset 404. Zero-downtime là bài toán hạ tầng; "không lỗi" là bài toán migration + state + test.
"Blue-green và canary khác nhau chỗ nào? Nghe cũng giống nhau." Blue-green chuyển 100% traffic một phát — dùng để rollback nhanh. Canary chuyển từng phần — dùng để phát hiện lỗi sớm với ít user bị ảnh hưởng. Blue-green trả lời "quay lại có nhanh không", canary trả lời "có nên đi tiếp không".
"Trên 1 VPS chạy Docker Compose thì rolling được không?"
Không thật sự. Compose đọc deploy.replicas nhưng bỏ qua deploy.update_config
→ up -d dừng hết rồi mới dựng lại. Muốn không gián đoạn trên 1 VPS thì làm
blue-green bằng 2 service + đổi upstream Nginx.
"Rollback mất bao lâu, và có phải lúc nào cũng rollback được không?"
Đổi upstream + nginx -s reload là dưới 1 giây. Nhưng chỉ rollback được khi
schema DB vẫn tương thích với code cũ, và khi bản lỗi chưa ghi dữ liệu hỏng.
Đó là lý do migration phải backward-compatible — nó chính là thứ giữ cho nút
rollback còn hoạt động.
"Đo gì để biết bản mới ổn?" Bốn nhóm: error rate 5xx, latency p95/p99, throughput (request/giây — tụt đột ngột nghĩa là traffic không tới được), và một chỉ số nghiệp vụ (số đơn hàng, số đăng nhập thành công). Ba cái đầu xanh mà đơn hàng về 0 thì vẫn là hỏng.
"Team 3 người, 1 VPS, bạn chọn gì?" Blue-green. Recreate cho test/staging vì đơn giản và rẻ. Canary chỉ khi traffic đủ lớn và đã có Prometheus + Grafana + người trực — nếu không, nó chỉ thêm việc mà không thêm an toàn.
"Feature flag thay được canary không?" Thay được phần lớn nhu cầu, và rẻ hơn nhiều: bật tính năng cho 5% user bằng Laravel Pennant, không cần chia traffic hạ tầng, tắt bằng 1 dòng config. Canary chỉ hơn khi rủi ro nằm ở chính bản build (nâng PHP, đổi thư viện, đổi config), chứ không nằm ở một tính năng cụ thể.
Tóm tắt để trả lời phỏng vấn
"Bốn cách khác nhau ở hai trục: bao nhiêu traffic thấy bản mới cùng lúc, và quay lại nhanh cỡ nào. Recreate rẻ và đơn giản nhưng có downtime. Rolling là mặc định khi đã có orchestrator. Blue-green đổi chi phí gấp đôi tạm thời lấy nút rollback tức thì — đây là cái tôi chọn cho 1 VPS chạy Docker Compose, vì Compose không rolling thật được. Canary an toàn nhất nhưng chỉ đáng khi traffic đủ lớn và có người đọc dashboard. Điểm chung quan trọng nhất: cả bốn cách đều có lúc 2 version code dùng chung 1 database, nên migration phải backward-compatible — không có nó thì nút rollback chỉ là trang trí."
Xem thực hành cụ thể (blue-green cho Laravel + GitLab CI/CD trên 1 VPS) ở laravel-gitlab-cicd.md, và cách dùng branch/tag để đưa code qua các môi trường ở git-workflow.md.