0

Hướng dẫn Setup Jenkins cho hệ thống Monolith - Bài 23: Viết Script Bash tự động dừng, backup và khởi động lại dịch vụ Monolith trên Server đích

Ở Bài 22, chúng ta đã tích hợp thành công plugin Publish Over SSH để đẩy gói Artifact từ Jenkins sang Staging Server và chạy một đoạn script ngắn trực tiếp trong tệp Jenkinsfile. Tuy nhiên, nếu bạn nhét toàn bộ logic xử lý phức tạp (giải nén, cấu hình, migration, health check, rollback) vào trong chuỗi lệnh execCommand của Jenkinsfile, tệp mã nguồn CI/CD sẽ trở nên rất khó đọc, khó debug và dễ bị lỗi thoát ký tự cú pháp (escaping).

Bài học hôm nay sẽ nâng tầm quy trình Deploy của bạn lên một chuẩn mực mới của dân chuyên nghiệp: Tách biệt hoàn toàn logic triển khai thành một tệp Bash Script độc lập (deploy.sh) nằm sẵn trên Server đích, và Jenkins chỉ đóng vai trò là "người kích hoạt".

1. Tư duy thiết kế "Smart Server & Dumb CI" trong kiến trúc Deploy

Một nguyên tắc vàng trong DevOps hiện đại là: Jenkins (CI) chỉ nên làm nhiệm vụ Build, Test và Vận chuyển; còn việc Cài đặt, Cấu hình và Vận hành ra sao phải do chính Máy chủ đích (Server) tự quyết định.

  • Lợi ích tuyệt đối:

    • Độc lập và Dễ kiểm thử: Bạn có thể tự SSH vào máy chủ Staging/Production và chạy thử nghiệm tệp deploy.sh bằng tay bất cứ lúc nào mà không cần phải trigger một bản build mới trên Jenkins.

    • Chống đạn (Bulletproof Safety): Tệp script trên server có thể áp dụng các cờ bảo mật nghiêm ngặt của Bash (set -euo pipefail) giúp bắt lỗi ngay lập tức ở từng dòng lệnh nhỏ nhất.

2. Xây dựng tệp Bash Script toàn diện (deploy.sh) trên Server đích

Hãy truy cập vào máy chủ Staging hoặc Production của bạn, tạo một tệp lệnh chuẩn đặt tại thư mục gốc của ứng dụng (ví dụ: /var/www/monolith-app/deploy.sh) và phân quyền thực thi cho nó:

Bash

sudo mkdir -p /var/www/monolith-app
sudo nano /var/www/monolith-app/deploy.sh

Dưới đây là mã nguồn mẫu của một tệp deploy.sh hoàn chỉnh, hỗ trợ chiến lược Atomic Symlink Deployment, tự động chạy Migration, kiểm tra sức khỏe (Health Check) và Tự động Rollback nếu bản mới bị lỗi:

Bash

#!/usr/bin/env bash

# ==============================================================================
# BASH SCRIPT TRIỂN KHAI HỆ THỐNG MONOLITH CHUẨN ATOMIC & AUTO-ROLLBACK
# Tác giả: DevOps Engineering Team
# Cách gọi: ./deploy.sh <BUILD_VERSION>
# ==============================================================================

# 1. Kích hoạt chế độ kiểm soát lỗi nghiêm ngặt của Bash
# -e: Thoát ngay lập tức nếu bất kỳ lệnh nào trả về lỗi (exit code != 0)
# -u: Báo lỗi và dừng nếu phát hiện biến chưa được khai báo
# -o pipefail: Bắt lỗi ở cả các lệnh nằm trong ống dẫn (pipeline |)
set -euo pipefail

# 2. Định nghĩa các biến đường dẫn cốt lõi
APP_NAME="monolith-core-system"
APP_DIR="/var/www/monolith-app"
RELEASES_DIR="${APP_DIR}/releases"
SHARED_DIR="${APP_DIR}/shared"
ARTIFACTS_DIR="${APP_DIR}/artifacts"

# Lấy số phiên bản từ tham số truyền vào khi gọi script (Ví dụ: v1.2.0-b45-a1b2c3d)
VERSION="${1:-}"

if [ -z "${VERSION}" ]; then
    echo "❌ LỖI: Thiếu tham số BUILD_VERSION!"
    echo "Sử dụng đúng cú pháp: ./deploy.sh v1.2.0-b45-a1b2c3d"
    exit 1
fi

ARTIFACT_FILE="${APP_NAME}-${VERSION}.tar.gz"
CHECKSUM_FILE="${ARTIFACT_FILE}.sha256"
NEW_RELEASE_DIR="${RELEASES_DIR}/${VERSION}"
CURRENT_SYMLINK="${APP_DIR}/current"

echo "🚀 Bắt đầu quy trình triển khai phiên bản: [${VERSION}]"

# 3. Kiểm tra tính toàn vẹn của gói Artifact thông qua mã băm SHA256
echo "------------------------------------------------------------------"
echo "[Bước 1/7] Kiểm tra tính toàn vẹn tệp nén (SHA256 Checksum)..."
cd "${ARTIFACTS_DIR}"

if [ ! -f "${ARTIFACT_FILE}" ] || [ ! -f "${CHECKSUM_FILE}" ]; then
    echo "❌ Không tìm thấy file Artifact hoặc file Checksum tại ${ARTIFACTS_DIR}"
    exit 1
fi

sha256sum -c "${CHECKSUM_FILE}"
echo "✅ Checksum toàn vẹn dữ liệu chính xác tuyệt đối!"

# 4. Giải nén mã nguồn vào thư mục release độc lập riêng biệt
echo "------------------------------------------------------------------"
echo "[Bước 2/7] Giải nén mã nguồn vào thư mục: ${NEW_RELEASE_DIR}"
mkdir -p "${NEW_RELEASE_DIR}"
tar -xzf "${ARTIFACT_FILE}" -C "${NEW_RELEASE_DIR}"

# 5. Liên kết (Symlink) các tệp cấu hình động và thư mục dùng chung (Shared Resources)
echo "------------------------------------------------------------------"
echo "[Bước 3/7] Đồng bộ tệp cấu hình .env và thư mục storage dùng chung..."
# Liên kết file .env môi trường Production
if [ -f "${SHARED_DIR}/.env" ]; then
    ln -sfn "${SHARED_DIR}/.env" "${NEW_RELEASE_DIR}/.env"
else
    echo "⚠️ CẢNH BÁO: Không tìm thấy file ${SHARED_DIR}/.env!"
fi

# Liên kết thư mục chứa file user upload (storage) để không bị mất mát dữ liệu khi đổi bản
rm -rf "${NEW_RELEASE_DIR}/storage"
ln -sfn "${SHARED_DIR}/storage" "${NEW_RELEASE_DIR}/storage"

# 6. Chạy Database Migration và tối ưu hóa hệ thống (Dành cho Laravel/Monolith)
echo "------------------------------------------------------------------"
echo "[Bước 4/7] Chạy Database Migration và làm sạch Cache..."
cd "${NEW_RELEASE_DIR}"

# Lưu lại đường dẫn release cũ trước khi chuyển đổi (phục vụ mục đích Rollback khẩn cấp)
PREVIOUS_RELEASE=$(readlink -f "${CURRENT_SYMLINK}" || echo "")

# Thực thi lệnh migration (Tùy thuộc framework của bạn: artisan migrate, alembic, flyway...)
php artisan migrate --force
php artisan config:cache
php artisan route:cache

# 7. Thực hiện phép tráo đổi Atomic Symlink (Chuyển traffic sang bản mới trong 0.001 giây)
echo "------------------------------------------------------------------"
echo "[Bước 5/7] Thực hiện tráo đổi Atomic Symlink 'current'..."
ln -sfn "${NEW_RELEASE_DIR}" "${CURRENT_SYMLINK}"

# Reload tiến trình PHP-FPM và Supervisor (Worker tiến trình nền)
sudo systemctl reload php8.2-fpm
sudo supervisorctl restart all || true
echo "✅ Đã chuyển đổi traffic sang phiên bản ${VERSION} thành công!"

# 8. Kiểm tra sức khỏe hệ thống (Smoke Test / Health Check tự động)
echo "------------------------------------------------------------------"
echo "[Bước 6/7] Kiểm tra sức khỏe ứng dụng (Health Check tự động)..."
# Đợi 3 giây để tiến trình ổn định hoàn toàn
sleep 3

# Gọi nội bộ vào endpoint /api/health của ứng dụng Monolith
HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1/api/health || echo "000")

if [ "${HTTP_STATUS}" -eq 200 ]; then
    echo "🎉 SỨC KHỎE ỔN ĐỊNH: Endpoint /api/health trả về HTTP 200 OK!"
else
    echo "❌ BÁO ĐỘNG ĐỎ: Health Check thất bại! Nhận mã HTTP: ${HTTP_STATUS}"
    echo "🔄 Đang tiến hành KỊCH BẢN ROLLBACK KHẨN CẤP về bản trước đó..."
    
    if [ -n "${PREVIOUS_RELEASE}" ] && [ -d "${PREVIOUS_RELEASE}" ]; then
        ln -sfn "${PREVIOUS_RELEASE}" "${CURRENT_SYMLINK}"
        sudo systemctl reload php8.2-fpm
        sudo supervisorctl restart all || true
        echo "🔙 Đã Rollback thành công về phiên bản cũ!"
    else
        echo "❌ LỖI NGHIÊM TRỌNG: Không tìm thấy phiên bản cũ để Rollback!"
    fi
    
    # Dọn dẹp thư mục release lỗi vừa tạo
    rm -rf "${NEW_RELEASE_DIR}"
    exit 1
fi

# 9. Dọn dẹp các tệp tạm và giữ lại tối đa 5 bản release gần nhất trên ổ cứng
echo "------------------------------------------------------------------"
echo "[Bước 7/7] Dọn dẹp tệp rác và giới hạn lịch sử Release..."
rm -f "${ARTIFACTS_DIR}/${ARTIFACT_FILE}"
rm -f "${ARTIFACTS_DIR}/${CHECKSUM_FILE}"

# Giữ lại đúng 5 bản release mới nhất, xóa các bản cũ hơn để tiết kiệm ổ cứng
cd "${RELEASES_DIR}"
ls -t | tail -n +6 | xargs -I {} rm -rf {}

echo "🎉 HOÀN TẤT TOÀN BỘ QUY TRÌNH TRIỂN KHAI CHO PHIÊN BẢN: ${VERSION}"

Phân quyền thực thi cho tệp script này trên server:

Bash

sudo chmod +x /var/www/monolith-app/deploy.sh
sudo chown deploy:www-data /var/www/monolith-app/deploy.sh

3. Rút gọn Stage Deploy trong Jenkinsfile trở nên siêu sạch sẽ

Bây giờ, quay trở lại tệp Jenkinsfile trên Jenkins của bạn, khối lệnh sshPublisher ở Bài 22 sẽ được thu gọn lại một cách cực kỳ tinh tế và chuyên nghiệp. Jenkins không cần phải ôm đồm hàng chục dòng lệnh phức tạp nữa, nó chỉ việc đẩy file và gọi một dòng lệnh duy nhất ./deploy.sh <VERSION>:

Groovy

        stage('6. Deploy to Staging Server') {
            when {
                anyOf {
                    branch 'develop'
                    branch 'main'
                }
            }
            steps {
                echo "=== Đang đẩy gói ${env.ARTIFACT_NAME} và kích hoạt Script deploy tự động trên Staging ==="
                sshPublisher(
                    failOnError: true,
                    publishers: [
                        sshPublisherDesc(
                            configName: 'Staging-Monolith-Server',
                            verbose: true,
                            transfers: [
                                sshTransfer(
                                    sourceFiles: "${env.ARTIFACT_NAME}, ${env.ARTIFACT_NAME}.sha256",
                                    remoteDirectory: 'artifacts',
                                    execTimeout: 300000,
                                    // Cực kỳ gọn gàng: Chỉ gọi tệp deploy.sh đã dựng sẵn kèm theo biến version
                                    execCommand: "/var/www/monolith-app/deploy.sh ${env.BUILD_VERSION}"
                                )
                            ]
                        )
                    ]
                )
            }
        }

4. Kiểm chứng cơ chế Tự động Rollback thực tế

Nhìn vào cấu trúc của tệp deploy.sh bên trên, bạn đã sở hữu một cơ chế bảo vệ cực kỳ mạnh mẽ:

  1. Kiểm tra Checksum: Ngăn chặn việc đưa file lỗi lên server.

  2. Atomic Symlink: Đảm bảo chuyển đổi không có độ trễ giữa các bản build.

  3. Smoke Test (/api/health): Ngay sau khi switch symlink xong, script tự động gọi kiểm tra ứng dụng. Nếu ứng dụng bắn ra lỗi 500 hoặc kết nối Database thất bại, lệnh if [ "${HTTP_STATUS}" -eq 200 ] sẽ kích hoạt nhánh else, lập tức trỏ ngược biến current về PREVIOUS_RELEASE chỉ trong chưa đầy 1 giây.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí