Tập 17 đã dựng pipeline CI/CD: push code lên main là test chạy, rồi deploy tự động lên server. Nhưng deploy tự động chưa chắc đã không downtime — restart app giữa lúc người dùng đang nhập một khoản chi tiêu, request của họ sẽ lỗi 502. Tập này hoàn thiện bước cuối: đưa bản mới lên sóng mà người dùng không hay biết, và rollback trong vài giây.

Vì sao deploy kiểu dừng rồi chạy lại gây downtime

Cách deploy đơn giản nhất là dừng service, kéo code mới, chạy migrations rồi khởi động lại. Trong khoảng trống giữa lúc dừng và lúc app mới sẵn sàng, mọi request đều lỗi — với Gunicorn thường 5–30 giây. Deploy không downtime loại bỏ khoảng trống này bằng cách luôn giữ ít nhất một bản app đang chạy.

Nguyên tắc vàng: migrations trước, code sau

Thứ tự đúng của mọi lần deploy là: kéo code mới, chạy migrations, rồi mới restart app. Migrations phải tương thích ngược — code cũ vẫn chạy được trên schema mới — đúng mẫu tập 6: thêm cột nullable trước, deploy code mới, rồi dọn cột cũ ở lần sau. Nếu làm ngược lại, app mới chạy trên schema cũ sẽ crash ngay.

#!/bin/bash
set -e
cd ~/expense-app
git pull origin main
python manage.py migrate --noinput
python manage.py collectstatic --noinput
sudo systemctl reload gunicorn
curl -fsS http://localhost:8000/health/

Rolling deploy với Gunicorn và systemd

Rolling deploy là cách đơn giản nhất: thay từng worker một thay vì dừng cả app. Gunicorn hỗ trợ reload graceful — gửi tín hiệu HUP, master process khởi động worker mới chạy code mới, còn worker cũ phục vụ nốt request đang dở rồi mới tắt. Cấu hình systemd dưới đây dùng chính cơ chế này, nên deploy chỉ là một lệnh reload — đủ tốt cho app quản lý chi tiêu một server.

[Service]
ExecStart=/home/linh/expense-app/venv/bin/gunicorn expense.wsgi:application --bind 127.0.0.1:8000 --workers 3
ExecReload=/bin/kill -s HUP $MAINPID
KillMode=mixed
TimeoutStopSec=30

Blue-green deploy với Nginx

Khi cần chắc chắn hơn — ví dụ deploy thay đổi lớn hoặc muốn rollback tức thì — dùng blue-green: chạy song song hai bản app trên hai cổng, Nginx chỉ trỏ tới bản đang chạy. Deploy bản mới lên cổng xanh, kiểm tra xong mới đổi upstream sang xanh; bản cũ vẫn chạy nguyên, nên rollback chỉ là đổi upstream về như cũ. Chi phí là gấp đôi tài nguyên, nhưng đổi lại bạn có nút quay lại trong vài giây.

upstream expense_app {
server 127.0.0.1:8001; # blue: ban dang chay
# server 127.0.0.1:8002; # green: ban moi, bat len khi deploy
}
server {
listen 80;
server_name chi-tieu.example.com;
location / {
proxy_pass http://expense_app;
proxy_set_header Host $host;
}
}

Kiểm tra sức khỏe và rollback

Dù rolling hay blue-green, đừng chuyển traffic sang bản mới khi chưa xác nhận nó sống. Sau khi reload hoặc đổi upstream, gọi endpoint /health/ ở tập 8 và chờ tới khi trả 200. Nếu health check thất bại, dừng lại và giữ nguyên bản cũ.

Rollback nhanh là nửa còn lại của deploy không downtime. Với rolling, giữ lại bản code cũ và reload ngược lại là đủ; với blue-green, chỉ cần đổi upstream Nginx về bản cũ rồi reload Nginx. Lưu ý: nếu migration đã xóa cột mà code cũ còn dùng, rollback sẽ lỗi — nên luôn xóa cột ở một lần deploy riêng, sau khi code mới đã chạy ổn.

Áp dụng vào dự án của bạn

Với ứng dụng quản lý chi tiêu, hãy bắt đầu bằng rolling deploy: thêm ExecReload vào service Gunicorn rồi đổi script deploy của tập 17 từ restart sang reload — một dòng thay đổi mà downtime biến mất. Khi lượng người dùng tăng, hãy chuyển sang blue-green với hai cổng 8001 và 8002, và ghi quy trình vào runbook.

Bài viết liên quan

Câu hỏi thường gặp

Rolling deploy khác blue-green ở điểm nào?

Rolling thay từng worker trên cùng một bản app, đơn giản và đủ cho một server. Blue-green chạy song song hai bản hoàn chỉnh rồi đổi traffic bằng Nginx, rollback tức thì nhưng tốn gấp đôi tài nguyên lúc chuyển đổi.

Migrations chạy lúc nào để không downtime?

Chạy trước khi restart hoặc reload, và migrations phải tương thích ngược: code cũ vẫn chạy được trên schema mới. Thêm cột nullable ở lần này, dọn cột cũ ở lần sau.

Health check thất bại thì làm gì?

Dừng deploy ngay và giữ nguyên bản cũ. Với rolling, worker cũ vẫn đang phục vụ nên người dùng không bị ảnh hưởng; với blue-green, chỉ cần không đổi upstream. Sau đó xem log bản mới, sửa lỗi rồi deploy lại.

Nguồn tham khảo