Khi cụm có hàng chục Cloud VPS, số việc vận hành lặp lại hằng ngày rất lớn: tạo site, deploy code, đổi domain, backup, gỡ site, chạy một lệnh artisan trên đúng minion. Nếu mỗi việc là một đoạn script nằm trên máy của một người, hoặc một chuỗi lệnh SSH gõ tay, kết quả phụ thuộc vào trí nhớ của người làm. Tài liệu này hướng dẫn dùng Jenkins như một "control plane": một nơi duy nhất để bấm chạy, có tham số, có lịch, có log, và là nơi duy nhất giữ chìa khoá SSH tới mọi máy. Nội dung gồm khái niệm, cách chọn freestyle hay pipeline, cách tổ chức job theo nhóm, kiểm tra tham số, rủi ro và checklist bảo vệ.
1. Jenkins là gì
Jenkins là một automation server mã nguồn mở. Đơn vị làm việc là job: một tập lệnh kèm cấu hình về tham số, lịch chạy, thông báo. Mỗi lần chạy job là một build có số thứ tự, log riêng, trạng thái thành công hay thất bại, và ghi lại ai đã bấm chạy. Lệnh được thực thi trên agent (với cụm nhỏ, chính máy controller cũng có thể là agent). Job chạy theo ba cách: bấm tay, theo lịch cron, hoặc được gọi qua API.
Sức mạnh của Jenkins nằm ở hệ plugin. Với vai trò control plane cho Cloud VPS, chỉ cần vài plugin: SSH Agent để nạp private key vào build, Credentials để giữ key, Slack Notification để báo kết quả, và Pipeline để viết job nhiều bước. Tổng quan kiến trúc có trong Jenkins User Handbook.
2. Freestyle hay Pipeline
Jenkins có hai kiểu job phổ biến, và một control plane thường dùng cả hai:
| Freestyle | Pipeline (Declarative) | |
|---|---|---|
| Cách viết | Ô "Execute shell" trên giao diện, chạy từ trên xuống | Groovy, chia stages, có khối post |
| Hợp với | Việc ngắn, một mạch: backup theo lịch, restart dịch vụ, chạy một lệnh | Việc nhiều bước cần kiểm tra giữa chừng: tạo site, đổi domain, gỡ site |
| Xử lý lỗi | set -e: lỗi là dừng | Mỗi stage dừng riêng, post { failure { ... } } để báo hoặc dọn dẹp |
| Hiển thị | Một khối log dài | Từng stage có thời gian và trạng thái riêng |
Quy tắc thực dụng: job nào viết được trong khoảng hai chục dòng bash thì để freestyle. Job nào có từ ba bước trở lên mà bước sau phụ thuộc kết quả bước trước thì viết pipeline.
3. Mô hình control plane: Jenkins SSH tới mọi máy
người vận hành (trình duyệt, qua VPN)
│ Build with Parameters
▼
┌─────────────────────────────────────────────────┐
│ JENKINS (mạng private) │
│ credential SSH ── ssh-agent plugin │
│ lịch cron ── tham số ── log ── Slack │
└──────┬───────────────┬───────────────┬──────────┘
│ ssh deploy@...│ │
▼ ▼ ▼
balancer minion máy vận hành ── mysql/mysqldump/rclone
└──▶ máy databaseJenkins không cần cài agent trên từng Cloud VPS. Nó chỉ cần SSH được tới máy bằng tên trong DNS nội bộ (xem bài về DNS nội bộ). Mọi thao tác với MySQL đi qua máy vận hành, đúng nguyên tắc trong bài mô hình ba tầng. Máy mới dựng bằng cloud image và cloud-init chỉ cần thêm public key của Jenkins là thuộc quyền điều khiển của control plane.
Với job freestyle, key được nạp bằng build wrapper của SSH Agent. Với pipeline thì dùng bước sshagent:
pipeline {
agent any
options { disableConcurrentBuilds() }
parameters {
choice(name: 'minion', choices: ['app-php74-1', 'app-php82-1', 'app-php82-2'])
string(name: 'domain', defaultValue: '', description: 'vd: example.com')
}
stages {
stage('Check Web Exists') {
steps {
sshagent(['ssh-ops-key']) {
sh '''
ssh -o ConnectTimeout=10 deploy@"$minion" \
"test -d /var/www/$domain" \
|| { echo "Không thấy /var/www/$domain trên $minion"; exit 1; }
'''
}
}
}
}
post {
failure { slackSend(color: 'danger', message: "FAIL ${env.JOB_NAME} #${env.BUILD_NUMBER}: ${params.domain}") }
}
}Private key chỉ tồn tại trong Credentials store của Jenkins. Nó được nạp vào bộ nhớ của ssh-agent trong thời gian build chạy, không ghi ra workspace, không lộ trong log.
4. Tổ chức job Jenkins theo nhóm chức năng
| Nhóm | Việc làm | Kiểu job | Kích hoạt |
|---|---|---|---|
| Tạo site / deploy | Kiểm tra tham số, kiểm tra DNS đã trỏ về balancer, so phiên bản PHP của minion với yêu cầu, sinh mật khẩu và tạo database, clone code, dựng .env, chạy artisan, sinh cấu hình nginx, xin SSL, cuối cùng curl kiểm tra site trả 200 | Pipeline | Bấm tay |
| Đổi domain | Đọc thông tin database từ .env, backup rồi thay domain trong dữ liệu, sửa cấu hình minion và balancer, kiểm tra HTTP | Pipeline | Bấm tay |
| Backup | Duyệt từng site, dump database qua máy vận hành, nén, đẩy lên object storage | Freestyle | Cron hằng đêm |
| Gỡ site | Backup file và database, đẩy lên storage, xoá code và cấu hình, xoá bản ghi DNS | Pipeline | Bấm tay |
| Chạy lệnh | Chạy một lệnh (git, artisan, composer) trong thư mục của một site, sau đó trả quyền cho user chạy web | Freestyle | Bấm tay |
| Hạ tầng | Tạo swap, cài agent giám sát, thêm SSH public key, restart dịch vụ định kỳ | Freestyle | Bấm tay hoặc cron |
Nên đặt tên job theo quy ước <nhóm>_<phạm-vi>, ví dụ backup_app-php82-1 hay site_create. Ô tìm kiếm của Jenkins khi đó trở thành "menu" vận hành: gõ backup_ là thấy toàn bộ job backup. Có thể gom thêm bằng folder hoặc view theo nhóm.
Lịch cron trong Jenkins nên dùng ký hiệu H, ví dụ H 19 * * *. Jenkins băm tên job ra một phút cố định trong giờ đó, nên hàng chục job backup không cùng lao vào máy vận hành đúng một thời điểm.
5. Tham số hoá: chặn lỗi gõ tay từ đầu vào
Giá trị lớn nhất của một job Jenkins so với một đoạn script là form nhập tham số:
- Choice cho tên balancer và minion: chọn từ danh sách, không gõ, nên không chạy nhầm sang máy không tồn tại.
- String cho domain, repo, branch. Giá trị luôn được kiểm tra lại ngay ở bước đầu tiên.
- Boolean cho các tuỳ chọn như cần cron, cần supervisor, cần xin SSL.
- File để tải lên file SQL khi tạo site từ dữ liệu có sẵn. Job phải kiểm tra file đúng định dạng trước khi dùng.
Domain đi thẳng vào đường dẫn và lệnh shell, nên phải được kiểm tra trước mọi thao tác:
if echo "$domain" | grep -qE '\.\.|[;&|<>`$]'; then
echo "Error: domain chứa ký tự không hợp lệ: $domain"; exit 1
fi
if ! echo "$domain" | grep -qE '^[a-zA-Z0-9.-]+$'; then
echo "Error: chỉ cho phép chữ, số, dấu chấm, gạch ngang"; exit 1
fi
if ! ssh deploy@"$minion" "[ -d /var/www/$domain ]"; then
echo "Error: không có /var/www/$domain trên $minion"; exit 1
fiBa bước kiểm tra này chặn được cả lỗi vô ý (gõ thừa dấu cách, chọn nhầm minion) lẫn chuỗi nguy hiểm như ../ hay ; rm -rf trong ô domain.
6. Rủi ro khi dùng Jenkins làm control plane
Gom quyền về một chỗ thì tiện, nhưng cũng gom luôn rủi ro về chỗ đó. Những rủi ro hay gặp và cách giảm:
- Job không nằm trong Git: script viết thẳng trên giao diện, lưu trong
config.xml, sửa job là sửa trực tiếp trên production, không review, không diff. Nên đểJenkinsfiletrong repo và cho job đọc từ SCM, hoặc tối thiểu định kỳ xuấtconfig.xmlvào Git. - Một key quyền cao cho mọi máy: ai có quyền cấu hình job là có quyền trên toàn cụm. Dùng user deploy có sudo giới hạn thay vì root, phân quyền Jenkins chặt (ai được tạo, sửa, chạy job nào) và giới hạn mạng để chỉ Jenkins SSH được vào máy.
- Job "chạy lệnh tuỳ ý": rất tiện cho lập trình viên, nhưng bản chất là một shell có giao diện web. Giới hạn người được chạy, giới hạn tập lệnh cho phép, và giữ nhật ký build (ai chạy, lệnh gì).
- Danh sách máy nằm rải rác trong từng job: thêm hay gỡ minion phải sửa choice list ở nhiều chỗ, sót một chỗ là job chạy vào máy không còn tồn tại. Đưa danh sách máy vào biến môi trường dùng chung hoặc file cấu hình trong repo.
- Jenkins là điểm lỗi đơn: Jenkins chết thì không backup, không deploy.
JENKINS_HOME(job, credential, lịch sử build) phải được backup như mọi dữ liệu quan trọng. StrictHostKeyChecking=no: tiện khi dựng lại máy liên tục, nhưng bỏ qua kiểm tra danh tính máy đích. Quản lýknown_hostscho Jenkins thay vì tắt kiểm tra.- Hai build cùng đụng một site:
disableConcurrentBuilds()chỉ chặn trong cùng một job. Muốn chặn giữa các job khác nhau (đổi domain trong lúc backup) phải dùng khoá theo tên site, ví dụ plugin Lockable Resources.
7. Checklist cho Jenkins control plane
- Jenkins chỉ truy cập được qua VPN hoặc mạng private, bật xác thực và phân quyền theo vai trò.
- Private key nằm trong Credentials, nạp bằng SSH Agent; máy đích dùng user deploy riêng.
- Mỗi thao tác lặp lại là một job có tham số, kiểm tra đầu vào, log và thông báo.
- Job nhiều bước viết bằng pipeline, có
post { failure }báo lỗi. - Script job nằm trong Git và được review như code ứng dụng.
- Cron dùng ký hiệu
H; có khoá theo site cho các job ghi dữ liệu. JENKINS_HOMEđược backup và đã thử khôi phục.
Control plane không cần phức tạp: Jenkins, một credential SSH, DNS nội bộ và quy ước thư mục thống nhất là đủ để điều khiển hàng chục Cloud VPS. Người mới không cần nhớ lệnh, chỉ cần biết chọn job nào.
Bài tiếp theo
Backup database hằng đêm: mysqldump, rclone và object storage. Job backup trên Jenkins chạy thế nào, giữ bao nhiêu ngày, và những lỗi làm bản backup thất bại mà không ai để ý.
