Hướng dẫn Setup Jenkins cho hệ thống Monolith - Bài 32: Tự động hóa dọn dẹp không gian đĩa cứng và quản lý Log Rotation nâng cao trong Jenkins
Ở Bài 31, chúng ta đã tách biệt thành công kiến trúc Jenkins Master - Agent để giải phóng CPU và RAM cho máy chủ điều phối chính. Tuy nhiên, có một "sát thủ thầm lặng" luôn rình rập mọi hệ thống CI/CD của dự án Monolith sau khoảng 1 – 2 tháng vận hành: Sự cố đầy ổ cứng 100% (java.io.IOException: No space left on device).
Khi dung lượng đĩa khả dụng trên Jenkins Master hoặc Agent tụt xuống dưới ngưỡng an toàn (mặc định là 1GB), Jenkins sẽ tự động kích hoạt cơ chế tự vệ: Đánh dấu Offline toàn bộ các Node Agent và từ chối chạy mọi bản build mới! Bài học hôm nay sẽ giúp bạn giải phẫu từng GB dung lượng bị rò rỉ trong quá trình build Monolith và xây dựng hệ thống tự động dọn dẹp (Auto-Housekeeping) toàn diện.
1. Giải phẫu 4 "Hố đen" nuốt chửng dung lượng ổ cứng trong dự án Monolith
Khác với Microservices có kích thước nhỏ gọn, một khối mã nguồn Monolith khi chạy qua Pipeline mỗi ngày 20 – 30 lần sẽ xả ra lượng dữ liệu khổng lồ tại 4 vị trí sau:
| Vị trí rò rỉ dung lượng | Đường dẫn thực tế trên Linux | Nguyên nhân gây phình to trong hệ thống Monolith | Tốc độ tăng trưởng trung bình |
|---|---|---|---|
| 1. Lịch sử Builds & Artifacts | /var/jenkins_home/jobs/<job>/builds/ |
Lưu trữ file nén .tar.gz/.jar (100–300MB/bản), báo cáo JUnit XML, Coverage XML và Console Log dài hàng chục MB. |
2GB – 5GB / ngày |
| 2. Thư mục Workspace mồ côi | /var/jenkins/workspace/<job>@tmp hoặc các nhánh feature/* đã xóa |
Mỗi khi tạo nhánh mới trong Multibranch Pipeline, Jenkins clone nguyên một bản Monolith kèm vendor/ và node_modules/ (nặng ~800MB). Khi nhánh Git bị xóa, thư mục trên Agent đôi khi vẫn nằm im. |
1GB – 3GB / nhánh mới |
| 3. Docker BuildKit Cache & Dangling Layers | /var/lib/docker/overlay2 và /var/lib/docker/buildkit |
Lệnh docker image prune -f thông thường không xóa bộ nhớ đệm của BuildKit (DOCKER_BUILDKIT=1 đã bật ở Bài 25). Sau 1 tháng, BuildKit Cache có thể chiếm tới 50GB – 100GB! |
5GB – 15GB / tuần |
| 4. Thư mục Cache thư viện & Trivy DB | /var/jenkins/cache và /tmp |
Các gói .zip của Composer, NPM, Go Modules phiên bản cũ không còn dùng tới và các tệp tạm sinh ra khi chạy Unit Test. |
2GB – 5GB / tháng |
2. Thiết lập chiến lược Log Rotation phân tầng (Tiered Retention Policy)
Ở các bài trước, chúng ta đã làm quen với chỉ thị buildDiscarder(logRotator(...)). Tuy nhiên, trong thực tế vận hành Monolith đa môi trường, bạn không nên áp dụng cùng một con số cố định cho mọi nhánh.
-
Nhánh
developvàfeature/*build hàng chục lần mỗi ngày -> Cần xóa thật nhanh. -
Nhánh
main(Production) gắn liền với việc kiểm toán (Audit) và Rollback -> Cần giữ lịch sử lâu hơn.
Bạn có thể cấu hình logRotator thông minh ngay trong Jenkinsfile như sau:
Groovy
options {
// Cấu hình Log Rotation nâng cao cho hệ thống Monolith:
// - numToKeepStr: Giữ lại tối đa 30 bản ghi lịch sử Build (chứa Console Log & Test Report).
// - daysToKeepStr: Tự động xóa các bản ghi lịch sử quá 30 ngày tuổi.
// - artifactNumToKeepStr: CHỈ GIỮ LẠI ĐÚNG 3 GÓI ARTIFACT (.tar.gz / .jar) gần nhất!
// - artifactDaysToKeepStr: Xóa các gói Artifact nặng nếu đã quá 7 ngày.
buildDiscarder(logRotator(
numToKeepStr: '30',
daysToKeepStr: '30',
artifactNumToKeepStr: '3',
artifactDaysToKeepStr: '7'
))
}
Bí quyết dùng Global Build Discarders (Chốt chặn cho các Job quên cấu hình)
Nếu trong hệ thống Jenkins có hàng chục Job khác nhau và một số lập trình viên quên khai báo buildDiscarder trong Jenkinsfile, bạn hãy thiết lập luật cưỡng chế toàn cục trên Jenkins Master:
-
Vào Manage Jenkins -> System.
-
Tìm mục Global Build Discarders -> Bấm Add -> Chọn Specific Build Discarder.
-
Điền Max # of builds to keep:
25và Max # of builds to keep with artifacts:3. -
Bấm Save. Từ lúc này, Jenkins sẽ tự động quét ngầm và dọn dẹp mọi Job trên toàn hệ thống, kể cả những Job không khai báo trong code!
3. Làm sạch Workspace chuẩn xác với Workspace Cleanup Plugin (cleanWs)
Trong các bài học trước, chúng ta đã dùng lệnh sh 'rm -rf reports/ .env*' ở cuối Pipeline. Tuy nhiên, nếu một thư mục con bên trong Workspace vô tình bị đổi quyền thành root (do một container Docker chạy bên trong tạo ra) hoặc có các tệp ẩn (.phpunit.result.cache, .eslintcache), lệnh rm thông thường có thể bỏ sót hoặc gây treo.
Công cụ chuẩn nhất trong hệ sinh thái Jenkins là plugin Workspace Cleanup với hàm cleanWs():
-
Cài đặt: Vào Manage Jenkins -> Plugins -> Tìm và cài đặt Workspace Cleanup.
-
Cách hoạt động thông minh trong Monolith: Thay vì xóa trắng toàn bộ Workspace (làm mất thời gian
git clonelại ở lần sau), bạn có thể cấu hìnhcleanWschỉ xóa các thư mục rác nặng nề khi bản build Thành công, hoặc xóa sạch sành sanh khi bản build Thất bại:
Groovy
post {
success {
echo "=== Bản build thành công: Dọn dẹp các tệp tạm và báo cáo, giữ lại khung Git ==="
cleanWs(
cleanWhenNotBuilt: false,
deleteDirs: true,
disableDeferredWipeout: true, // Xóa ngay lập tức trên ổ cứng, không chờ xóa ngầm
notFailBuild: true, // Nếu lỗi xóa file cũng không làm đỏ bản build đã thành công
patterns: [
[pattern: 'reports/**', type: 'INCLUDE'],
[pattern: 'node_modules/**', type: 'INCLUDE'],
[pattern: '*.tar.gz*', type: 'INCLUDE'],
[pattern: '.env*', type: 'INCLUDE']
]
)
}
failure {
echo "=== Bản build thất bại: Làm sạch hoàn toàn Workspace để lần chạy sau không bị dính trạng thái lỗi ==="
cleanWs(deleteDirs: true, disableDeferredWipeout: true, notFailBuild: true)
}
}
Giải thích tham số vàng
disableDeferredWipeout: true: Mặc định khi gọicleanWs(), Jenkins đổi tên thư mục thànhworkspace_ws-cleanup_12345rồi đưa vào luồng xóa ngầm để kết thúc Pipeline cho nhanh. Nhưng trên các Agent đang cạn kiệt ổ cứng và chạy nhiều bản build liên tiếp, việc xóa ngầm chậm trễ khiến đĩa cứng bị tràn trước khi luồng xóa kịp chạy. BậtdisableDeferredWipeout: trueép Jenkins giải phóng dung lượng đĩa ngay tức khắc!
4. Xây dựng "Robot Lao Công": Pipeline Bảo trì Định kỳ (jenkins-system-housekeeping)
Các lệnh logRotator và cleanWs() chỉ dọn dẹp trong phạm vi của từng Job đang chạy. Vậy còn Docker BuildKit Cache, các Container mồ côi, các Workspace của những nhánh Git đã bị xóa từ tháng trước, và Cache thư viện quá hạn 30 ngày trên các máy chủ Agent thì ai dọn?
Giải pháp thực chiến của các kỹ sư DevOps là tạo một Maintenance Pipeline chạy tự động vào 02:00 sáng mỗi ngày (thông qua cấu hình triggers { cron(...) } đã học ở Bài 8) để tổng vệ sinh toàn bộ các máy chủ Agent!
Cảnh báo trước khi chạy lệnh Docker Prune tự động
Trên các máy chủ Jenkins Agent dùng riêng cho việc Build/CI, lệnh docker system prune -af hoàn toàn an toàn. Tuy nhiên, nếu bạn chạy chung Agent trên cùng máy chủ đang chứa Database hoặc Redis, tuyệt đối không dùng cờ --volumes trong lệnh docker system prune để tránh xóa nhầm dữ liệu của các container đang tạm dừng!
Dưới đây là toàn bộ mã nguồn của Job bảo trì hệ thống (Jenkinsfile.maintenance):
Groovy
pipeline {
// Chạy trên máy chủ Agent chuyên build Monolith (đã thiết lập ở Bài 31)
agent {
node {
label 'monolith-worker'
}
}
// Tự động kích hoạt vào khoảng 2 giờ sáng mỗi ngày (H 2 * * *)
triggers {
cron('H 2 * * *')
}
options {
buildDiscarder(logRotator(numToKeepStr: '10'))
timeout(time: 30, unit: 'MINUTES')
timestamps()
}
environment {
AGENT_ROOT = "/var/jenkins"
CACHE_ROOT = "/var/jenkins/cache"
// Ngưỡng cảnh báo nếu ổ cứng bị chiếm dụng vượt quá 80%
DISK_ALERT_THRESHOLD = "80"
}
stages {
stage('1. Check Disk Usage (Before Cleanup)') {
steps {
echo "=== Kiểm tra dung lượng ổ cứng trên Agent trước khi dọn dẹp ==="
sh '''
df -h /
echo "--- Chi tiết dung lượng hệ thống Docker hiện tại ---"
docker system df || true
'''
}
}
stage('2. Prune Docker & BuildKit Cache') {
steps {
echo "=== Dọn dẹp các Docker Containers, Images mồ côi và BuildKit Cache quá 72 giờ ==="
sh '''
# 1. Xóa toàn bộ các container đã dừng (Exited) quá 24 giờ
docker container prune -f --filter "until=24h"
# 2. Xóa các Docker Images không được gắn với container nào và tạo quá 72 giờ
docker image prune -a -f --filter "until=72h"
# 3. CỰC KỲ QUAN TRỌNG: Dọn dẹp Docker BuildKit Cache (Chỉ giữ lại tối đa 10GB cache gần nhất)
docker builder prune -f --keep-storage 10GB || docker builder prune -f --filter "until=72h"
'''
}
}
stage('3. Clean Expired Dependency Cache & Stale Workspaces') {
steps {
echo "=== Xóa các file Cache thư viện và Workspace mồ côi không truy cập quá 14 - 30 ngày ==="
sh '''
# 1. Xóa các gói cache Composer/NPM/Go không được đọc tới (atime) trong 30 ngày qua
if [ -d "${CACHE_ROOT}" ]; then
find "${CACHE_ROOT}" -type f -atime +30 -delete
find "${CACHE_ROOT}" -type d -empty -delete
fi
# 2. Xóa các thư mục tạm (@tmp) và Workspace của các nhánh cũ không hoạt động quá 14 ngày
if [ -d "${AGENT_ROOT}/workspace" ]; then
find "${AGENT_ROOT}/workspace" -maxdepth 1 -name "*@tmp" -mtime +1 -exec rm -rf {} +
find "${AGENT_ROOT}/workspace" -mindepth 1 -maxdepth 1 -type d -mtime +14 -exec rm -rf {} +
fi
# 3. Dọn dẹp các tệp rác của PHPUnit / Go test rơi vãi trong /tmp
find /tmp -maxdepth 1 -user jenkins -mtime +2 -exec rm -rf {} + 2>/dev/null || true
'''
}
}
stage('4. Verify Disk Space & Alert') {
steps {
echo "=== Kiểm tra lại dung lượng ổ cứng sau khi tổng vệ sinh ==="
script {
sh '''
df -h /
docker system df || true
'''
// Lấy phần trăm dung lượng ổ cứng đang bị chiếm dụng (Ví dụ: 45)
def usedPercent = sh(
script: "df / | tail -1 | awk '{print \$5}' | sed 's/%//'",
returnStdout: true
).trim().toInteger()
currentBuild.displayName = "#${env.BUILD_NUMBER} - Disk Used: ${usedPercent}%"
if (usedPercent >= env.DISK_ALERT_THRESHOLD.toInteger()) {
unstable "⚠️ CẢNH BÁO: Sau khi dọn dẹp, ổ cứng vẫn chiếm tới ${usedPercent}% (Vượt ngưỡng ${env.DISK_ALERT_THRESHOLD}%)! Cần kiểm tra mở rộng dung lượng đĩa."
} else {
echo "✅ Sức khỏe ổ cứng tuyệt vời: Đang sử dụng ${usedPercent}% (Dưới ngưỡng ${env.DISK_ALERT_THRESHOLD}%)."
}
}
}
}
}
}
5. Mẹo giải cứu khẩn cấp bằng Groovy Script Console khi Jenkins Master bị treo ổ cứng
Trong trường hợp máy chủ Jenkins Master bất ngờ bị đầy ổ cứng do các bản build cũ chưa kịp chạy qua nhánh logRotator, bạn không cần phải SSH vào xóa từng thư mục bằng tay (vì xóa tay trên đĩa sẽ làm lệch bộ nhớ đệm trong RAM của Jenkins).
Bạn có thể truy cập trực tiếp vào Manage Jenkins -> Script Console và chạy đoạn mã Groovy quản trị dưới đây để ép Jenkins xóa ngay toàn bộ các bản build cũ và chỉ giữ lại 5 bản mới nhất cho tất cả các Job trên toàn hệ thống:
Groovy
// Chạy trong Manage Jenkins -> Script Console để giải phóng ổ cứng Jenkins Master tức thì
import jenkins.model.Jenkins
import hudson.model.Job
int maxBuildsToKeep = 5
int deletedCount = 0
Jenkins.instance.getAllItems(Job.class).each { job ->
def builds = job.getBuilds()
if (builds.size() > maxBuildsToKeep) {
builds.drop(maxBuildsToKeep).each { build ->
// Không xóa các bản build đang được đánh dấu "Keep this build forever" hoặc đang chạy
if (!build.isKeepLog() && !build.isBuilding()) {
println "Đang xóa bản build cũ: ${job.fullName} #${build.number}"
build.delete()
deletedCount++
}
}
}
}
println "✅ Hoàn tất! Đã xóa tổng cộng ${deletedCount} bản build cũ để giải phóng ổ cứng."
6. Cấu hình ngưỡng giám sát ổ cứng tự động trên Jenkins UI (Disk Space Monitoring)
Để Jenkins chủ động cảnh báo sớm cho bạn trước khi ổ cứng chạm đáy, hãy tinh chỉnh lại bộ cảm biến dung lượng của các Node:
-
Vào Manage Jenkins -> Nodes -> Bấm vào biểu tượng bánh răng Configure Monitors ở góc trên bên phải.
-
Tại hai dòng Free Disk Space (Kiểm tra thư mục
/var/jenkins) và Free Temp Space (Kiểm tra thư mục/tmp):-
Free Space Threshold (Ngưỡng khóa Node): Đặt là
2GiB(Nếu ổ cứng còn dưới 2GB, tự động ngắt Agent để tránh làm hỏng file đang build). -
Free Space Warning Threshold (Ngưỡng cảnh báo sớm): Đặt là
5GiB(Hiển thị biểu tượng cảnh báo màu vàng ngay trên màn hình quản lý Node khi dung lượng còn dưới 5GB).
-
-
Bấm Save.
All rights reserved