0

Hướng dẫn Setup Jenkins cho hệ thống Monolith + Bài học 33 + Sao lưu (Backup) và Phục hồi (Restore) toàn bộ cấu hình Jenkins khi có sự cố Server

Ở Bài 32, chúng ta đã thiết lập thành công quy trình tự động dọn dẹp ổ cứng để giữ cho hệ thống Jenkins luôn vận hành trơn tru. Tuy nhiên, trong thế giới hạ tầng thực tế, không có bất kỳ máy chủ nào là bất tử: Ổ cứng vật lý có thể cháy (RAID failure), máy ảo Cloud có thể bị xóa nhầm, hoặc một bản cập nhật Jenkins Core bị lỗi làm hỏng toàn bộ cấu trúc dữ liệu.

Hãy tưởng tượng một buổi sáng thứ Hai, máy chủ Jenkins Master của bạn bỗng dưng sập hoàn toàn và không thể khởi động lại. Nếu không có bản sao lưu (Backup), bạn sẽ mất sạch toàn bộ danh sách các Jobs, lịch sử phân quyền RBAC (Bài 28), cấu hình kết nối SonarQube, danh sách các Node Agents (Bài 31), và đau đớn nhất là toàn bộ kho lưu trữ khóa bí mật (SSH Keys, Database Passwords, Tokens) đã kỳ công thiết lập suốt hàng tháng trời!

Bài học hôm nay sẽ hướng dẫn bạn giải phẫu cấu trúc thư mục JENKINS_HOME, phân biệt rõ những gì cần sao lưu và xây dựng quy trình Backup & Disaster Recovery (Phục hồi sau thảm họa) tự động hóa hoàn toàn, giúp bạn dựng lại một máy chủ Jenkins mới giống hệt 100% chỉ trong vòng 5 phút.

1. Giải phẫu thư mục $JENKINS_HOME: Cần giữ gì và cần bỏ gì khi Backup?

Một trong những thiết kế tuyệt vời nhất của Jenkins là nó không sử dụng hệ quản trị cơ sở dữ liệu bên ngoài (như MySQL hay PostgreSQL). Toàn bộ cấu hình hệ thống, thông tin người dùng, khóa mã hóa và định nghĩa Pipeline đều được lưu trữ dưới dạng các tệp tin phẳng (Flat XML Files) nằm gọn bên trong một thư mục duy nhất: /var/jenkins_home (hoặc /var/lib/jenkins).

Tuy nhiên, bạn tuyệt đối không nên nén toàn bộ 100% thư mục /var/jenkins_home để đem đi backup! Tại sao? Vì bên trong đó chứa cả hàng chục GB thư mục workspace/, .cache/ và builds/*/archive (những thứ có thể tải lại hoặc build lại bất cứ lúc nào). Nếu nén tất cả, file backup sẽ nặng tới 50GB, vừa tốn băng thông vừa làm treo máy khi phục hồi.

Dưới đây là bảng phân loại "Phẫu thuật $JENKINS_HOME" chuẩn thực chiến:

Thành phần trong /var/jenkins_home Mức độ quan trọng Có cần đưa vào gói Backup không? Giải thích chi tiết
config.xml và *.xml ở thư mục gốc Tối quan trọng BẮT BUỘC CÓ Chứa cấu hình hệ thống (System), cấu hình bảo mật RBAC, SonarQube, SSH Server, Global Tools.
credentials.xml, secrets/, secret.key* Sống còn (Trái tim mã hóa) BẮT BUỘC CÓ credentials.xml chứa các mật khẩu đã mã hóa, còn thư mục secrets/ chứa khóa giải mã (Master Key). Lưu ý: Nếu có credentials.xml mà mất thư mục secrets/, toàn bộ mật khẩu sẽ biến thành phế liệu không thể giải mã!
users/ và nodes/ Tối quan trọng BẮT BUỘC CÓ Chứa danh sách tài khoản người dùng và cấu hình kết nối tới các máy chủ Worker Agent (Bài 31).
jobs/<job-name>/config.xml Tối quan trọng BẮT BUỘC CÓ Định nghĩa cấu trúc của từng Pipeline, nhánh Git, tham số đầu vào và lịch chạy Cron.
plugins/*.jpi (hoặc *.hpi) Quan trọng NÊN CÓ Chứa các gói cài đặt Plugin đúng phiên bản đang chạy ổn định (nặng khoảng 200MB – 400MB).
jobs/<job-name>/builds/ Trung bình Chỉ lấy nextBuildNumber (Bỏ qua archive/ và log cũ) Giữ lại tệp nextBuildNumber để khi phục hồi sang server mới, số thứ tự build (BUILD_NUMBER) tiếp tục tăng từ mốc cũ (ví dụ #46) chứ không bị reset về #1!
workspace/, caches/, .cache/, logs/ Không quan trọng (Tạm thời) LOẠI TRỪ HOÀN TOÀN (--exclude) Nặng hàng chục GB nhưng hoàn toàn vô giá trị khi backup vì Jenkins sẽ tự tạo lại ở lần chạy tiếp theo.

Khi loại trừ đúng các thư mục rác ở dòng cuối, một bản Backup toàn bộ cấu hình não bộ của Jenkins Master sẽ giảm từ 40GB xuống chỉ còn khoảng 50MB – 300MB!

2. Phương pháp 1: Sử dụng ThinBackup Plugin (Dành cho quản trị qua Giao diện Web)

Nếu bạn muốn một công cụ trực quan ngay trên giao diện Jenkins, tự động phân biệt giữa Full Backup (Sao lưu toàn bộ hàng tuần) và Differential Backup (Chỉ sao lưu những file cấu hình vừa thay đổi hàng ngày):

Bước 1: Cài đặt ThinBackup

  1. Vào Manage Jenkins -> Plugins -> Available plugins.

  2. Tìm kiếm ThinBackup -> Cài đặt.

Bước 2: Thiết lập lịch trình tự động trong ThinBackup

  1. Vào Manage Jenkins -> Cuộn xuống mục Tools and Actions -> Chọn ThinBackup -> Settings.

  2. Cấu hình các thông số chuẩn Production:

    • Backup directory: /var/jenkins_home/thin-backups (Lưu ý: Thư mục này nên được mount ra một ổ đĩa riêng hoặc đồng bộ lên Cloud).

    • Backup schedule for full backups: H 3 * * 0 (Chạy Full Backup vào 3h sáng Chủ Nhật hàng tuần).

    • Backup schedule for differential backups: H 3 * * 1-6 (Chạy Backup phần thay đổi vào 3h sáng từ Thứ Hai đến Thứ Bảy).

    • Max number of backup sets: 4 (Chỉ giữ lại 4 bộ Full Backup gần nhất, tự động xóa bộ cũ hơn).

    • Files excluded from backup (Regex): ^.*\.(tar\.gz|zip|jar|war|log)$ (Loại trừ tuyệt đối các file nén Artifact nặng).

    • Tích chọn: Wait until Jenkins/Hudson is idle to perform a backup (Đợi đến khi không có Job nào đang chạy mới tiến hành sao lưu để tránh lỗi file XML đang ghi dở).

    • Tích chọn: Backup build results -> Nhưng BỎ TÍCH ô Backup build archive (Giữ lại số thứ tự build nhưng bỏ qua các gói Artifact nặng).

    • Tích chọn: Backup 'userContent' folder và Backup next build number file.

  3. Bấm Save. Bạn có thể bấm nút Backup Now để kiểm tra ngay lập tức!

3. Phương pháp 2 (Chuẩn DevOps Thực Chiến): Script Tự động Nén, Mã hóa GPG và Đẩy lên Cloud (AWS S3 / MinIO / Rclone)

Một nguyên tắc bất di bất dịch trong an toàn thông tin (Quy tắc Backup 3-2-1): Nếu bạn lưu bản Backup nằm ngay trên cùng một ổ cứng với máy chủ Jenkins Master, thì khi ổ cứng đó cháy, cả Jenkins lẫn bản Backup sẽ "ra đi" cùng một lúc!

Hơn thế nữa, bản Backup của Jenkins chứa cả thư mục secrets/ và credentials.xml. Nếu kẻ gian lấy cắp được file .tar.gz này trên Cloud, họ chỉ cần dựng một Jenkins локаl là giải mã được toàn bộ SSH Key của công ty. Do đó, quy trình chuẩn cấp độ Doanh nghiệp phải gồm 3 bước:

  1. Đóng gói thông minh (tar với bộ lọc tinh gọn).

  2. Mã hóa đối xứng AES-256 bằng gpg trước khi rời khỏi máy chủ.

  3. Đẩy tự động sang máy chủ lưu trữ từ xa (Off-site Storage: AWS S3, MinIO, hoặc Google Drive/SFTP qua Rclone).

Xây dựng tệp /usr/local/bin/jenkins-offsite-backup.sh trên máy chủ Host:

Bash

#!/usr/bin/env bash

# ==============================================================================
# KỊCH BẢN SAO LƯU, MÃ HÓA AES-256 VÀ ĐẨY LÊN CLOUD CHO JENKINS MASTER
# Tác giả: DevOps Engineering Team
# ==============================================================================

set -euo pipefail

# 1. Cấu hình đường dẫn (Nếu chạy Jenkins bằng Docker ở Bài 3, trỏ vào thư mục volume trên Host)
JENKINS_HOME="/var/jenkins_home"
BACKUP_STAGING_DIR="/tmp/jenkins-backups"
TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
ARCHIVE_NAME="jenkins-master-backup-${TIMESTAMP}.tar.gz"
ENCRYPTED_FILE="${ARCHIVE_NAME}.gpg"

# Khóa mật khẩu dùng để mã hóa file Backup (Trên thực tế nên truyền từ biến môi trường bảo mật)
BACKUP_PASSPHRASE="${JENKINS_BACKUP_PASSPHRASE:-YourSuperStrongRecoveryPassword2026!}"

# Cấu hình tên Bucket trên AWS S3 hoặc MinIO (Hoặc thư mục Remote)
S3_BUCKET="s3://company-disaster-recovery-backups/jenkins-master"

mkdir -p "${BACKUP_STAGING_DIR}"
cd "${JENKINS_HOME}"

echo "📦 [1/4] Bắt đầu đóng gói cấu hình cốt lõi từ ${JENKINS_HOME}..."

# 2. Thực hiện nén có chọn lọc: Lấy toàn bộ cấu hình XML, Secrets, Users, Nodes, Plugins và nextBuildNumber
# Đồng thời loại trừ tuyệt đối Workspace, Caches, Logs và các gói Artifacts nặng nề
tar -czf "${BACKUP_STAGING_DIR}/${ARCHIVE_NAME}" \
    --exclude='workspace*' \
    --exclude='caches*' \
    --exclude='.cache*' \
    --exclude='logs*' \
    --exclude='thin-backups*' \
    --exclude='war*' \
    --exclude='updates*' \
    --exclude='jobs/*/builds/*/archive*' \
    --exclude='jobs/*/builds/*/jacoco*' \
    --exclude='jobs/*/builds/*/log' \
    --exclude='*.tar.gz' \
    --exclude='*.jar' \
    --warning=no-file-changed \
    ./*.xml \
    ./secret* \
    ./users \
    ./nodes \
    ./plugins/*.jpi \
    ./plugins/*.hpi \
    ./jobs \
    2>/dev/null || true

echo "🔒 [2/4] Mã hóa gói Backup bằng thuật toán chuẩn AES-256 (GPG)..."
gpg --batch --yes --passphrase "${BACKUP_PASSPHRASE}" \
    --cipher-algo AES256 \
    --symmetric \
    --output "${BACKUP_STAGING_DIR}/${ENCRYPTED_FILE}" \
    "${BACKUP_STAGING_DIR}/${ARCHIVE_NAME}"

# Xóa ngay file .tar.gz chưa mã hóa trên ổ cứng
rm -f "${BACKUP_STAGING_DIR}/${ARCHIVE_NAME}"

BACKUP_SIZE=$(du -h "${BACKUP_STAGING_DIR}/${ENCRYPTED_FILE}" | awk '{print $1}')
echo "✅ Đã tạo thành công gói Backup mã hóa: ${ENCRYPTED_FILE} (Dung lượng: ${BACKUP_SIZE})"

echo "☁️ [3/4] Đang tải gói Backup lên kho lưu trữ ngoài (Off-site S3 / MinIO / Remote)..."
# Tùy chọn A: Nếu dùng AWS CLI hoặc MinIO Client
if command -v aws >/dev/null 2>&1; then
    aws s3 cp "${BACKUP_STAGING_DIR}/${ENCRYPTED_FILE}" "${S3_BUCKET}/${ENCRYPTED_FILE}"
fi

# Tùy chọn B: Nếu dùng scp đẩy sang máy chủ Backup nội bộ khác (Ví dụ: 10.0.1.99)
# scp -i /root/.ssh/backup_key "${BACKUP_STAGING_DIR}/${ENCRYPTED_FILE}" backup_user@10.0.1.99:/data/jenkins_backups/

echo "🧹 [4/4] Dọn dẹp các bản lưu cục bộ quá 7 ngày..."
find "${BACKUP_STAGING_DIR}" -name "*.gpg" -mtime +7 -delete

echo "🎉 HOÀN TẤT SAO LƯU AN TOÀN HỆ THỐNG JENKINS MASTER!"

4. Tự động hóa bằng Jenkins Pipeline (jenkins-master-backup-pipeline)

Thay vì phải nhớ vào máy chủ gõ lệnh, bạn có thể tạo một Pipeline Job chạy trực tiếp trên Built-In Node (Master) vào lúc 03:00 sáng mỗi ngày (ngay sau khi Job Housekeeping ở Bài 32 vừa dọn rác xong lúc 02:00 sáng!).

Groovy

pipeline {
    // Chạy trực tiếp trên node Master (built-in) để tiếp cận thư mục /var/jenkins_home
    agent {
        label 'built-in || master'
    }

    triggers {
        cron('H 3 * * *')
    }

    options {
        buildDiscarder(logRotator(numToKeepStr: '14'))
        timeout(time: 20, unit: 'MINUTES')
        timestamps()
    }

    environment {
        BACKUP_DIR = "/var/jenkins_home/thin-backups"
    }

    stages {
        stage('1. Create Encrypted Snapshot') {
            steps {
                echo "=== Đang đóng gói và mã hóa toàn bộ cấu hình Jenkins Master ==="
                withCredentials([string(credentialsId: 'jenkins-backup-gpg-passphrase', variable: 'GPG_PASS')]) {
                    sh '''
                        mkdir -p ${BACKUP_DIR}
                        TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
                        BACKUP_FILE="${BACKUP_DIR}/jenkins-config-${TIMESTAMP}.tar.gz"

                        cd /var/jenkins_home
                        tar -czf "${BACKUP_FILE}" \
                            --exclude='workspace*' \
                            --exclude='.cache*' \
                            --exclude='caches*' \
                            --exclude='thin-backups*' \
                            --exclude='jobs/*/builds/*/archive*' \
                            --exclude='jobs/*/builds/*/log' \
                            --warning=no-file-changed \
                            ./*.xml ./secret* ./users ./nodes ./jobs 2>/dev/null || true

                        # Mã hóa file nén bằng GPG AES-256
                        gpg --batch --yes --passphrase "${GPG_PASS}" --cipher-algo AES256 -c "${BACKUP_FILE}"
                        rm -f "${BACKUP_FILE}"

                        ls -lh "${BACKUP_FILE}.gpg"
                    '''
                }
            }
        }

        stage('2. Sync to Remote Disaster Recovery Server') {
            steps {
                echo "=== Đẩy bản sao lưu đã mã hóa sang máy chủ lưu trữ dự phòng (Off-site) ==="
                sshPublisher(
                    failOnError: true,
                    publishers: [
                        sshPublisherDesc(
                            configName: 'Staging-Monolith-Server', // Hoặc một Backup Server riêng biệt
                            verbose: true,
                            transfers: [
                                sshTransfer(
                                    sourceFiles: 'thin-backups/*.gpg',
                                    removePrefix: 'thin-backups',
                                    remoteDirectory: 'jenkins-dr-backups',
                                    execCommand: '''
                                        # Chỉ giữ lại 14 bản backup gần nhất trên máy chủ dự phòng
                                        cd /var/www/monolith-app/jenkins-dr-backups
                                        ls -t *.gpg | tail -n +15 | xargs -I {} rm -f {}
                                        echo "✅ Đã đồng bộ bản Backup Jenkins Master sang Server dự phòng thành công!"
                                    '''
                                )
                            ]
                        )
                    ]
                )
            }
            post {
                always {
                    // Xóa bản tạm trên Master sau khi đã đẩy sang Server dự phòng thành công
                    sh 'rm -f /var/jenkins_home/thin-backups/*.gpg'
                }
            }
        }
    }
}

5. Kịch bản Diễn tập Phục hồi Thảm họa (Disaster Recovery Drill) trong 5 Bước

Một bản Backup chưa từng được thử nghiệm phục hồi (Restore) thì coi như chưa tồn tại! Giả sử máy chủ Jenkins Master cũ bị hỏng hoàn toàn, bạn thuê một VPS Linux mới tinh và thực hiện đúng 5 bước sau để hồi sinh toàn bộ hệ thống trong 5 phút:

**1.Tải và Giải mã gói Backup trên Server mới:**Khôi phục từ tệp .gpg đã lưu trên S3 hoặc Backup Server.

Tạo thư mục lưu trữ dữ liệu cho Jenkins trên máy chủ mới và giải mã gói sao lưu bằng mật khẩu GPG của bạn:

Bash

sudo mkdir -p /var/jenkins_home

# 1. Giải mã file .gpg về lại file .tar.gz
gpg --batch --yes --passphrase "YourSuperStrongRecoveryPassword2026!" \
    --output /tmp/jenkins-restore.tar.gz \
    --decrypt jenkins-config-20261102_030000.tar.gz.gpg

# 2. Giải nén toàn bộ cấu trúc vào /var/jenkins_home
sudo tar -xzf /tmp/jenkins-restore.tar.gz -C /var/jenkins_home
rm -f /tmp/jenkins-restore.tar.gz

**2.Đồng bộ quyền sở hữu tệp tin (UID/GID 1000:1000)😗*Sai bước này container Jenkins sẽ báo lỗi Permission Denied và tự tắt!.

Container chính thức jenkins/jenkins:lts chạy dưới quyền user jenkins có UID=1000 và GID=1000. Khi bạn dùng tài khoản root để giải nén ở Bước 1, toàn bộ file đang thuộc quyền sở hữu của root. Hãy trao trả lại quyền cho 1000:1000:

Bash

sudo chown -R 1000:1000 /var/jenkins_home

**3.Khởi động lại Container Jenkins Master:**Ánh xạ Volume vào thư mục vừa giải nén.

Khởi chạy lại cụm Docker Compose của Jenkins Master (y hệt cấu hình ở Bài 3):

Bash

docker run -d \
  --name jenkins-master \
  --restart unless-stopped \
  -p 8080:8080 -p 50000:50000 \
  -v /var/jenkins_home:/var/jenkins_home \
  -v /var/run/docker.sock:/var/run/docker.sock \
  jenkins/jenkins:lts-jdk17

**4.Kiểm tra tính toàn vẹn của Master Key và Credentials:**Xác nhận giải mã thành công kho lưu trữ bí mật.

Mở trình duyệt truy cập http://<IP_SERVER_MỚI>:8080. Đăng nhập bằng tài khoản Admin cũ của bạn (không cần qua bước nhập initialAdminPassword nữa!).

  • Vào Manage Jenkins -> Credentials: Kiểm tra xem các khóa jenkins-git-ssh-key, monolith-env-prod, telegram-bot-token có hiển thị đầy đủ và không báo lỗi đỏ Unreadable data hay không.

  • Vào Manage Jenkins -> Nodes: Kiểm tra kết nối tới monolith-worker-01 đã sáng đèn xanh trở lại chưa.

**5.Cập nhật IP mới trên GitHub/GitLab & SonarQube Webhooks (Nếu đổi IP)😗*Bước hay bị bỏ quên nhất sau khi chuyển Server.

Nếu máy chủ mới của bạn sử dụng một địa chỉ IP Public mới (và bạn chưa trỏ qua tên miền trung gian như jenkins.yourcompany.com), hãy nhớ cập nhật lại IP mới tại 2 nơi:

  1. Manage Jenkins -> System -> Jenkins Location (Jenkins URL).

  2. Cấu hình Webhook URL trên GitHub/GitLab (Bài 7) và trên SonarQube Server (Bài 14).

6. Mẹo nạp lại cấu hình không cần Restart (Reload Configuration from Disk)

Trong trường hợp bạn không dựng lại server mới, mà chỉ giải nén đè một vài tệp config.xml của một Job bị ai đó xóa nhầm từ bản Backup vào lại thư mục /var/jenkins_home/jobs/<job-name>/, bạn không cần phải khởi động lại dịch vụ Jenkins (vì khởi động lại sẽ làm đứt các Job khác đang chạy).

Hãy sử dụng tính năng nạp nóng từ ổ cứng vào RAM:

  • Truy cập Manage Jenkins -> Cuộn xuống nhóm Tools and Actions -> Bấm vào Reload Configuration from Disk (Hoặc nếu dùng ThinBackup thì bấm trực tiếp nút Restore trên giao diện). Chỉ mất 2 giây, chiếc Job bị xóa nhầm sẽ xuất hiện trở lại nguyên vẹn trên màn hình Dashboard!

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í