Hướng dẫn Setup Jenkins cho hệ thống Monolith - Bài 27: Xây dựng quy trình Rollback tự động khi bản triển khai gặp lỗi nghiêm trọng
Trong vận hành hệ thống Monolith, có một câu nói kinh điển của giới DevOps: "Không quan trọng bạn triển khai nhanh đến mức nào, quan trọng là khi thảm họa xảy ra, bạn khôi phục lại được nhanh trong bao lâu."
Ở Bài 23 và Bài 26, chúng ta đã tích hợp các bước kiểm tra sức khỏe cơ bản (Health Check) trong script deploy. Tuy nhiên, nếu bản build mới được đưa lên Production, vượt qua được bài kiểm tra khởi động ban đầu nhưng sau đó 10 phút lại gây ra lỗi tràn bộ nhớ (Memory Leak), lỗi sập cơ sở dữ liệu hoặc làm hỏng toàn bộ luồng thanh toán của khách hàng, lúc đó hệ thống thực sự lâm nguy.
Bài học hôm nay sẽ hướng dẫn bạn xây dựng Quy trình Rollback tự động và thủ công an toàn tuyệt đối cho hệ thống Monolith, giúp đưa hệ thống quay về trạng thái ổn định trước đó chỉ trong tích tắc.
1. Triết lý Rollback trong kiến trúc Monolith hiện đại
Khác với việc cố gắng chạy lệnh git revert hoặc sửa code trực tiếp trên Production để vá lỗi (vừa lâu vừa rủi ro), quy trình Rollback chuẩn doanh nghiệp phải tuân thủ 2 nguyên tắc:
-
Rollback là một hành động đổi hướng trỏ (Symlink Switch) hoặc đổi phiên bản Docker Image, hoàn toàn không biên dịch lại code.
-
Cơ chế "Fail-Safe": Hệ thống phải tự động kích hoạt Rollback ngay lập tức khi chỉ số lỗi (
HTTP 5xx error rate) vượt ngưỡng cho phép, hoặc cho phép Tech Lead thực hiện lệnh quay đầu khẩn cấp chỉ bằng 1 cú click chuột trên giao diện Jenkins.
2. Nâng cấp Script deploy-docker.sh trên Server để hỗ trợ Rollback thông minh
Để quá trình Rollback diễn ra mượt mà với Docker Monolith, script deploy-docker.sh trên máy chủ đích cần lưu lại thông tin của phiên bản đang chạy ổn định trước đó (PREVIOUS_IMAGE).
Hãy cập nhật lại tệp script deploy trên Staging/Production Server:
Bash
#!/usr/bin/env bash
set -euo pipefail
NEW_IMAGE_URL="${1:-}"
CONTAINER_NAME="monolith-core-app"
NETWORK_NAME="monolith-net"
SHARED_DIR="/var/www/monolith-app/shared"
STATE_FILE="/var/www/monolith-app/current_image.txt"
if [ -z "${NEW_IMAGE_URL}" ]; then
echo "❌ LỖI: Thiếu tham số Docker Image URL!"
exit 1
fi
echo "🐳 Bắt đầu quy trình triển khai Docker Monolith cho Image: [${NEW_IMAGE_URL}]"
# 1. Đọc phiên bản Image hiện tại đang chạy (nếu có) để làm mốc Rollback sau này
PREVIOUS_IMAGE=""
if [ -f "${STATE_FILE}" ]; then
PREVIOUS_IMAGE=$(cat "${STATE_FILE}")
fi
docker network create "${NETWORK_NAME}" 2>/dev/null || true
echo "------------------------------------------------------------------"
echo "[Bước 1/4] Tải xuống Docker Image mới..."
docker pull "${NEW_IMAGE_URL}"
echo "------------------------------------------------------------------"
echo "[Bước 2/4] Dừng container cũ và khởi động container phiên bản mới..."
if [ "$(docker ps -a -q -f name=^/${CONTAINER_NAME}$)" ]; then
docker stop "${CONTAINER_NAME}" || true
docker rm "${CONTAINER_NAME}" || true
fi
# Chạy container mới
if docker run -d \
--name "${CONTAINER_NAME}" \
--restart unless-stopped \
--network "${NETWORK_NAME}" \
-p 8000:9000 \
--env-file "${SHARED_DIR}/.env" \
-v "${SHARED_DIR}/storage:/var/www/html/storage" \
"${NEW_IMAGE_URL}"; then
echo "------------------------------------------------------------------"
echo "[Bước 3/4] Kiểm tra sức khỏe ứng dụng (Health Check tự động)..."
sleep 5
HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:8000/api/health || echo "000")
if [ "${HTTP_STATUS}" -eq 200 ]; then
echo "🎉 HEALTH CHECK THÀNH CÔNG: HTTP Status 200 OK!"
# Lưu lại Image mới thành trạng thái hiện tại thành công
echo "${NEW_IMAGE_URL}" > "${STATE_FILE}"
echo "✅ HOÀN TẤT TRIỂN KHAI THÀNH CÔNG!"
else
echo "❌ CẢNH BÁO: Health Check thất bại với mã HTTP ${HTTP_STATUS}!"
echo "🔄 KÍCH HOẠT TỰ ĐỘNG ROLLBACK VỀ PHIÊN BẢN CŨ..."
if [ -n "${PREVIOUS_IMAGE}" ]; then
docker stop "${CONTAINER_NAME}" || true
docker rm "${CONTAINER_NAME}" || true
docker run -d \
--name "${CONTAINER_NAME}" \
--restart unless-stopped \
--network "${NETWORK_NAME}" \
-p 8000:9000 \
--env-file "${SHARED_DIR}/.env" \
-v "${SHARED_DIR}/storage:/var/www/html/storage" \
"${PREVIOUS_IMAGE}"
echo "🔙 Đã Rollback tự động thành công về Image: ${PREVIOUS_IMAGE}"
exit 1
else
echo "❌ LỖI NGHIÊM TRỌNG: Không tìm thấy Image trước đó để Rollback!"
exit 1
fi
fi
else
echo "❌ LỖI: Không thể khởi động container mới!"
if [ -n "${PREVIOUS_IMAGE}" ]; then
echo "🔄 Đang khôi phục lại bản cũ..."
docker run -d --name "${CONTAINER_NAME}" --restart unless-stopped --network "${NETWORK_NAME}" -p 8000:9000 --env-file "${SHARED_DIR}/.env" -v "${SHARED_DIR}/storage:/var/www/html/storage" "${PREVIOUS_IMAGE}"
fi
exit 1
fi
3. Thiết lập Job Rollback thủ công trên giao diện Jenkins (Parameterized Build)
Ngoài cơ chế tự động rollback khi Health Check thất bại, bạn nên tạo một Jenkins Pipeline riêng biệt chuyên dùng để Rollback khẩn cấp khi có sự cố phát sinh trên Production.
Cấu hình Job monolith-emergency-rollback:
-
Tạo một Pipeline Job mới đặt tên là
monolith-emergency-rollback. -
Kích hoạt tính năng This project is parameterized với 2 tham số:
-
Choice Parameter:
TARGET_ENV(Giá trị:staging,production). -
String Parameter:
IMAGE_TAG_TO_ROLLBACK(Nhập chính xác chuỗi tag version cần quay đầu, ví dụ:v1.2.0-production.b42-f8c3e2a).
-
-
Nội dung
Jenkinsfilecủa Job Rollback cực kỳ gọn nhẹ:
Groovy
pipeline {
agent any
parameters {
choice(name: 'TARGET_ENV', choices: ['staging', 'production'], description: 'Chọn môi trường cần Rollback.')
string(name: 'IMAGE_TAG_TO_ROLLBACK', defaultValue: '', description: 'Nhập đầy đủ Docker Image Tag muốn quay đầu về (Ví dụ: v1.2.0-production.b42-f8c3e2a)')
}
environment {
APP_NAME = "monolith-core-system"
DOCKER_REGISTRY = "registry.yourcompany.com"
DOCKER_REPO = "${DOCKER_REGISTRY}/monolith/${APP_NAME}"
}
stages {
stage('1. Validate & Confirm') {
steps {
script {
if (!params.IMAGE_TAG_TO_ROLLBACK?.trim()) {
error "❌ LỖI: Bạn chưa nhập thông tin Image Tag cần Rollback!"
}
env.TARGET_SERVER = (params.TARGET_ENV == 'production') ? 'Prod-Monolith-Server' : 'Staging-Monolith-Server'
env.TARGET_IMAGE_URL = "${env.DOCKER_REPO}:${params.IMAGE_TAG_TO_ROLLBACK}"
timeout(time: 10, unit: 'MINUTES') {
input message: "🚨 BẠN CÓ CHẮC CHẮN MUỐN ROLLBACK HỆ THỐNG ${params.TARGET_ENV.toUpperCase()} VỀ PHIÊN BẢN [${params.IMAGE_TAG_TO_ROLLBACK}] KHÔNG?",
ok: "Xác nhận Rollback Khẩn Cấp!"
}
}
}
}
stage('2. Execute Emergency Rollback') {
steps {
echo "=== Đang thực hiện Rollback khẩn cấp về Image: ${env.TARGET_IMAGE_URL} trên ${env.TARGET_SERVER} ==="
sshPublisher(
failOnError: true,
publishers: [
sshPublisherDesc(
configName: "${env.TARGET_SERVER}",
verbose: true,
transfers: [
sshTransfer(
sourceFiles: '',
remoteDirectory: '',
execTimeout: 300000,
execCommand: "/var/www/monolith-app/deploy-docker.sh ${env.TARGET_IMAGE_URL}"
)
]
)
]
)
}
}
}
post {
success {
echo "🎉 QUÁ TRÌNH ROLLBACK ĐÃ HOÀN TẤT THÀNH CÔNG!"
}
failure {
echo "❌ THẢM HỌA: Lệnh Rollback gặp lỗi! Cần can thiệp trực tiếp bằng tay trên máy chủ ngay lập tức!"
}
}
}
All rights reserved