Khi vận hành website trên nền tảng Docker trong môi trường production, tình trạng người dùng gặp lỗi 502 Bad Gateway mỗi khi cập nhật phiên bản mới sẽ gây gián đoạn trực tiếp đến trải nghiệm và tính liên tục của dịch vụ.
Nếu bạn vẫn đang áp dụng quy trình phát hành truyền thống theo kiểu triển khai Docker Compose cơ bản: chạy lệnh docker compose pull && docker compose down && docker compose up -d, hệ thống sẽ xuất hiện khoảng gián đoạn dịch vụ (Downtime Window) kéo dài từ 5 đến 30 giây. Đó là thời điểm container cũ đã dừng nhưng container mới vẫn đang trong giai đoạn nạp framework, kết nối cơ sở dữ liệu hoặc khởi tạo bộ đệm. Trường hợp bản phát hành mới phát sinh lỗi cấu hình hoặc thiếu biến môi trường khiến container không thể khởi động, dịch vụ sẽ gián đoạn cho đến khi người vận hành thực hiện quy trình rollback khẩn cấp.
Trong kiến trúc cụm máy chủ Multi-Server với bộ cân bằng tải Nginx và kho lưu trữ tệp tin phi trạng thái, giải pháp tiêu chuẩn là triển khai Zero-Downtime Deployment (phát hành không gián đoạn). Mục tiêu là hạn chế tối đa mã lỗi HTTP 502/503 trong suốt quá trình cập nhật, giữ cho các phiên làm việc của người dùng diễn ra liền mạch, đồng thời hỗ trợ khôi phục phiên bản cũ an toàn nếu bản cập nhật mới gặp sự cố.
Bài viết này hướng dẫn hai chiến lược phát hành phù hợp với hệ thống Docker: Rolling Update trên cụm Multi-Server và Blue/Green Deployment trên từng máy chủ, kết hợp kỹ thuật thay đổi cấu trúc cơ sở dữ liệu hai pha (Expand and Contract) và cơ chế dừng tiến trình an toàn (Graceful Shutdown).
Chiến lược 1: Rolling Update trên cụm Multi-Server
Khi hệ thống vận hành từ 2 Web Node trở lên phía sau Nginx Load Balancer, giải pháp trực diện và hiệu quả nhất là Rolling Update (cập nhật cuốn chiếu từng máy chủ).
Nguyên tắc cơ bản: không cập nhật đồng loạt mọi máy chủ cùng lúc. Thay vào đó, bạn tạm ngắt lưu lượng đến từng máy chủ, cập nhật phiên bản mới, kiểm tra trạng thái hoạt động rồi mới đưa trở lại cụm để phục vụ người dùng.
Tiến trình Rolling Update:
Thời điểm T0: LB ──> [ Web-01 (v1) ] + [ Web-02 (v1) ]
Thời điểm T1: LB ──> [ Web-02 (v1) ] | Web-01 tạm rút để nâng cấp lên v2
Thời điểm T2: LB ──> [ Web-01 (v2) ] | Web-02 tạm rút để nâng cấp lên v2
Thời điểm T3: LB ──> [ Web-01 (v2) ] + [ Web-02 (v2) ] (Hoàn tất)Quy trình 4 bước triển khai
Bước 1: Rút Web-01 khỏi khối upstream (drain connection)
Trên máy chủ Nginx Load Balancer, mở tệp cấu hình upstream và thêm cờ down cho Web-01:
upstream web_cluster {
least_conn;
server 10.0.0.11:80 down; # Tạm thời ngừng nhận request mới
server 10.0.0.12:80;
keepalive 64;
}Nạp lại cấu hình Nginx bằng lệnh:
sudo nginx -s reloadBước 2: Cập nhật container trên Web-01
Đăng nhập SSH vào máy chủ Web-01, kéo bản dựng (image) mới và tái tạo container:
docker compose pull app
docker compose up -d --no-deps app(Tham số --no-deps giúp Docker tái tạo riêng dịch vụ app mà không khởi động lại các container phụ trợ khác như Redis hay dịch vụ thu thập log).
Bước 3: Kiểm tra healthcheck nội bộ
Trước khi định tuyến lưu lượng ngoài trở lại Web-01, bạn cần kiểm tra xem ứng dụng đã sẵn sàng xử lý yêu cầu hay chưa qua endpoint kiểm tra sức khỏe:
curl -sf http://127.0.0.1:80/healthz > /dev/nullNếu lệnh trả về mã trạng thái 200 OK, ứng dụng đã khởi động thành công, thiết lập kết nối cơ sở dữ liệu ổn định và sẵn sàng tiếp nhận yêu cầu.
Điều kiện an toàn: Nếu lệnh curl không thành công hoặc trả về mã lỗi HTTP 5xx, bạn cần tạm dừng quá trình triển khai, giữ nguyên Web-02 đang phục vụ lưu lượng và tiến hành rollback Web-01 về phiên bản trước đó để kiểm tra nguyên nhân. Người dùng bên ngoài vẫn tiếp tục truy cập bình thường qua Web-02.
Bước 4: Đưa Web-01 trở lại cụm và lặp lại với Web-02
Sau khi Web-01 vượt qua bước kiểm tra, bạn quay lại Nginx Load Balancer, bỏ cờ down của Web-01, nạp lại cấu hình Nginx (sudo nginx -s reload), sau đó lặp lại quy trình tương tự cho máy chủ Web-02.
Chiến lược 2: Blue/Green Deployment trên từng máy chủ
Khi cần xây dựng quy trình phát hành tự động khép kín trên từng máy chủ mà không muốn can thiệp vào cụm Nginx Load Balancer trung tâm, bạn có thể triển khai mô hình Blue/Green cục bộ ngay trên từng node.
Kiến trúc môi trường song song
Trên từng máy chủ Web Node, bạn duy trì hai nhóm container có cấu hình tương đương:
- Môi trường Blue: Lắng nghe trên cổng nội bộ
8001(phiên bản v1 đang phục vụ lưu lượng thực tế). - Môi trường Green: Lắng nghe trên cổng nội bộ
8002(phiên bản v2 chuẩn bị phát hành). - Một dịch vụ Nginx cục bộ (Local Reverse Proxy) lắng nghe cổng
80/443và chuyển tiếp lưu lượng vào một trong hai cổng trên qua tệp cấu hình upstream.
Cấu hình Docker Compose
Tệp docker-compose.yml:
version: '3.8'
services:
# Phiên bản Blue
app-blue:
image: ${APP_IMAGE_BLUE}
container_name: web_app_blue
restart: always
environment:
- PORT=8001
- DB_HOST=10.0.0.20
ports:
- "127.0.0.1:8001:8001"
stop_signal: SIGQUIT
stop_grace_period: 30s
# Phiên bản Green
app-green:
image: ${APP_IMAGE_GREEN}
container_name: web_app_green
restart: always
environment:
- PORT=8002
- DB_HOST=10.0.0.20
ports:
- "127.0.0.1:8002:8002"
stop_signal: SIGQUIT
stop_grace_period: 30sTệp định tuyến Nginx cục bộ /etc/nginx/conf.d/app-upstream.conf:
upstream current_backend {
server 127.0.0.1:8001; # Trỏ vào Blue hoặc Green
keepalive 32;
}
server {
listen 80;
server_name _;
location / {
proxy_pass http://current_backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}Kịch bản tự động hóa chuyển đổi
Kịch bản deploy.sh dưới đây hỗ trợ tự động hóa các bước khởi chạy, kiểm tra sức khỏe và đảo cổng upstream trên Nginx:
#!/usr/bin/env bash
set -euo pipefail
# 1. Xác định môi trường đang chạy và môi trường mục tiêu
CURRENT_PORT=$(grep -oP '(?<=server 127.0.0.1:)[0-9]+' /etc/nginx/conf.d/app-upstream.conf)
if [ "$CURRENT_PORT" = "8001" ]; then
TARGET_COLOR="green"
TARGET_PORT="8002"
OLD_COLOR="blue"
else
TARGET_COLOR="blue"
TARGET_PORT="8001"
OLD_COLOR="green"
fi
echo "Dang phat hanh phien ban moi len moi truong: $TARGET_COLOR (Port $TARGET_PORT)..."
# 2. Khoi chay container moi tren moi truong muc tieu
docker compose pull "app-$TARGET_COLOR"
docker compose up -d "app-$TARGET_COLOR"
# 3. Kiem tra suc khoe (Pre-flight Healthcheck) toi da 5 lan
echo "Dang kiem tra tinh trang hoat dong cua container moi..."
HEALTHY=false
for i in {1..5}; do
if curl -sf "http://127.0.0.1:$TARGET_PORT/healthz" > /dev/null; then
HEALTHY=true
break
fi
echo "Thu lai lan $i sau 2 giay..."
sleep 2
done
# 4. Kiem tra ket qua truoc khi chuyen luu luong
if [ "$HEALTHY" = false ]; then
echo "Loi nghiem trong: Container $TARGET_COLOR khong vuot qua kiem tra suc khoe!"
echo "Huy bo phat hanh. Dung container loi..."
docker compose stop "app-$TARGET_COLOR"
exit 1
fi
# 5. Dao cong tren Nginx (Zero-Downtime Switch)
echo "Container moi hoat dong hoan hao. Tien hanh dao luu luong sang $TARGET_COLOR..."
sudo sed -i "s/server 127.0.0.1:$CURRENT_PORT;/server 127.0.0.1:$TARGET_PORT;/" /etc/nginx/conf.d/app-upstream.conf
sudo nginx -s reload
echo "Chuyen doi thanh cong! Luu luong hien tai dang do vao: $TARGET_COLOR."
# 6. Cho doi xu ly not request cu va dung container cu
sleep 15
echo "Dung container cu: app-$OLD_COLOR..."
docker compose stop "app-$OLD_COLOR"
echo "Phat hanh hoan tat khong gián doan!"Di chuyển cơ sở dữ liệu hai pha
Một nguyên nhân phổ biến khiến việc phát hành gặp sự cố gián đoạn là sự bất tương thích giữa mã nguồn và cấu trúc cơ sở dữ liệu (Database Schema Migration).
Trong quá trình thực hiện Rolling Update hoặc Blue/Green Deployment, cả hai phiên bản ứng dụng (v1 và v2) đều chạy song song trong vài phút và cùng đọc ghi vào một cơ sở dữ liệu chung.
Nếu câu lệnh migration làm thay đổi cấu trúc bảng theo hướng không tương thích ngược (chẳng hạn đổi tên cột user_name thành username, hoặc xóa một cột mà phiên bản v1 vẫn đang dùng), các container v1 chưa kịp nâng cấp sẽ báo lỗi ngay lập tức (thường gặp là lỗi Column not found).
Để duy trì tính toàn vẹn và không làm gián đoạn dịch vụ, bạn nên áp dụng mẫu hình Mở rộng và Thu hẹp (Expand and Contract):
Pha 1: Expand (Mở rộng)
- Thêm cột mới `username` cho phép NULL.
- Cả code v1 và code v2 đều hoạt động bình thường.
Pha 2: Deploy Code mới
- Code v2 đọc/ghi đồng thời cả 2 cột hoặc ưu tiên cột mới.
- Quá trình Rolling Update diễn ra an toàn.
Pha 3: Contract (Thu hẹp - thực hiện sau khi hệ thống ổn định)
- Chạy script đồng bộ dữ liệu cũ sang cột mới.
- Chạy migration xóa bỏ cột cũ `user_name`.Cơ chế tắt tiến trình an toàn trong Docker
Khi container phiên bản cũ nhận lệnh dừng, nếu tiến trình bị ngắt cưỡng bức bằng tín hiệu SIGKILL, các kết nối của người dùng đang truyền dữ liệu dở dang (như phiên thanh toán hoặc tiến trình tải tệp) sẽ bị ngắt đột ngột, dễ dẫn đến sai lệch dữ liệu.
Để xử lý vấn đề này, bạn nên cấu hình cơ chế tắt tiến trình an toàn (Graceful Shutdown) bằng hai tham số trong docker-compose.yml:
services:
app:
# 1. Chi dinh tin hieu tat nhe nhang phu hop voi framework
stop_signal: SIGQUIT
# 2. Cho phep tien trinh xu ly not request toi da 30 giay truoc khi kill
stop_grace_period: 30s- Với Nginx và PHP-FPM: Sử dụng tín hiệu
SIGQUIT. Khi nhận tín hiệu này, tiến trình cha ngừng tiếp nhận request mới, chờ các worker xử lý nốt các phiên hiện có rồi mới giải phóng tài nguyên. - Với Node.js / Go / Python: Đón bắt tín hiệu
SIGTERMtrực tiếp trong mã nguồn ứng dụng:
// Vi du xu ly Graceful Shutdown trong Node.js (Express)
process.on('SIGTERM', () => {
console.log('Nhan tin hieu SIGTERM. Dang dong HTTP server an toan...');
server.close(() => {
console.log('HTTP server da dong. Ngat ket noi Database...');
db.disconnect();
process.exit(0);
});
});Kiểm tra và xác nhận quá trình phát hành
Để xác nhận quy trình triển khai không làm gián đoạn dịch vụ thực tế, bạn có thể thực hiện bài kiểm tra tải:
Từ một máy bên ngoài, sử dụng Apache Benchmark (ab) để gửi liên tục 1,000 yêu cầu tới website trong khi chạy kịch bản cập nhật:
ab -n 1000 -c 10 https://example.com/Trong lúc lệnh ab đang chạy, bạn thực thi script phát hành trên máy chủ:
./deploy.shKết quả kiểm tra đạt yêu cầu khi:
Complete requests: 1000
Failed requests: 0
Non-2xx responses: 0
Requests per second: 142.50 [#/sec] (mean)
Time per request: 70.174 [ms] (mean)Chỉ số Failed requests duy trì ở mức 0 và toàn bộ phản hồi trả về mã thành công cho thấy hệ thống đã chuyển giao phiên bản mới mà không làm rớt kết nối nào của người dùng.
Sau khi đã hoàn thiện chu trình triển khai ứng dụng trên cụm Multi-Server, bước tiếp theo để đưa hệ thống vào vận hành là rà soát lại các ranh giới bảo mật, kế hoạch cắt chuyển DNS và phương án dự phòng khẩn cấp tại bài viết checklist kiểm tra an toàn trước khi chuyển website sang Multi-Server.



