Vấn đề thực tế: Jenkins không chạy được dù cài đúng theo docs
Tháng trước mình vừa hoàn thành dựng hệ thống CI/CD cho team dev 8 người. Chọn CentOS Stream 9 vì công ty đang chuẩn hóa toàn bộ server về dòng RHEL-compatible — rồi mới té ngửa ra rằng “cài Jenkins” không đơn giản như copy-paste từ tài liệu chính thức.
Follow đúng Jenkins docs, chạy lệnh cài xong, service start báo active, nhưng mở trình duyệt lên thì:
curl http://localhost:8080
# curl: (7) Failed to connect to localhost port 8080: Connection refused
Log Jenkins đầy những dòng kiểu này:
Nov 08 10:23:41 server jenkins[12345]: java.io.IOException: Failed to bind to 0.0.0.0/0.0.0.0:8080
Rồi thêm cái Nginx reverse proxy phía trước cũng không forward được request dù config nhìn đúng hoàn toàn. Ba vấn đề chồng lên nhau mà docs chính thức không đề cập đủ.
Phân tích nguyên nhân
Sau khoảng 2 tiếng debug, mình xác định được 3 nguyên nhân chính:
1. Java version không tương thích
Jenkins LTS 2.426+ yêu cầu Java 17 hoặc Java 21. CentOS Stream 9 mặc định pull java-11-openjdk từ repo. Mình đã cài đúng Java 17 nhưng system vẫn dùng Java 11 làm default vì chưa chạy alternatives.
# Kiểm tra Java đang active
java -version
# openjdk version "11.0.21" — sai!
# Xem tất cả Java version đang có
alternatives --list | grep java
2. SELinux chặn Jenkins bind port và đọc/ghi file
CentOS Stream 9 mặc định SELinux ở chế độ enforcing. Jenkins cần bind port 8080 và đọc/ghi vào thư mục home — tất cả đều bị deny nếu chưa cấu hình đúng context và port type.
# Xem SELinux denials liên quan Jenkins
grep "avc: denied" /var/log/audit/audit.log | grep jenkins | tail -20
3. Nginx không được phép kết nối ngược lên upstream
Nginx forward request về http://localhost:8080 nhưng SELinux boolean httpd_can_network_connect mặc định tắt — Nginx không được tạo outbound connection dù đang trên cùng một máy.
Các cách giải quyết
Cách 1: Tắt SELinux — nhanh nhưng nguy hiểm
Nhiều bài hướng dẫn trên mạng giải quyết bằng cách set SELINUX=permissive trong /etc/selinux/config. Nhanh thật, nhưng đây là sai lầm nghiêm trọng trên production. Jenkins chạy build script từ developer — bỏ SELinux là cho phép bất kỳ script nào leo quyền tùy ý trên server.
Cách 2: Chạy Jenkins trong Docker
Isolate hoàn toàn, không lo SELinux của host. Nhưng thực tế mình đã thấy 2-3 team gặp bài toán Docker-in-Docker khi build image có mount volume — debug mệt hơn cả cấu hình SELinux. Thêm một lớp phức tạp không cần thiết nếu chỉ cần Jenkins bare-metal cơ bản.
Cách 3: Cấu hình đúng SELinux + Nginx từ đầu
Mất khoảng 30 phút setup nhưng bền vững. Máy chủ của mình chạy ổn định 6 tháng nay không có issue nào liên quan hạ tầng.
Cách tốt nhất: Hướng dẫn từng bước
Bước 1: Chuẩn bị hệ thống
sudo dnf update -y
sudo dnf install -y wget curl policycoreutils-python-utils
Bước 2: Cài Java 17 và set làm default
Bước này hay bị bỏ sót phần “set default” nhất — khiến Jenkins vẫn dùng Java 11 cũ dù đã cài Java 17 đủ rồi.
# Cài Java 17
sudo dnf install -y java-17-openjdk java-17-openjdk-devel
# Chọn Java 17 làm default (interactive — chọn số tương ứng)
sudo alternatives --config java
# Verify
java -version
# openjdk version "17.x.x" — đúng rồi
Bước 3: Cài Jenkins LTS
# Thêm Jenkins repo
sudo wget -O /etc/yum.repos.d/jenkins.repo \
https://pkg.jenkins.io/redhat-stable/jenkins.repo
# Import GPG key
sudo rpm --import https://pkg.jenkins.io/redhat-stable/jenkins.io-2023.key
# Cài Jenkins
sudo dnf install -y jenkins
# Enable và start
sudo systemctl enable jenkins
sudo systemctl start jenkins
sudo systemctl status jenkins
Bước 4: Mở firewall tạm thời để test
# Mở port 8080 tạm thời — sẽ đóng lại sau khi có Nginx
sudo firewall-cmd --permanent --add-port=8080/tcp
sudo firewall-cmd --reload
Bước 5: Xử lý SELinux cho Jenkins
Phần quan trọng nhất mà tài liệu chính thức bỏ qua hoàn toàn.
# Xác nhận SELinux đang enforcing
getenforce
# Enforcing
# Jenkins cần bind port 8080 — thêm vào http_port_t
sudo semanage port -a -t http_port_t -p tcp 8080
# Nếu báo lỗi "already defined", kiểm tra type hiện tại
sudo semanage port -l | grep 8080
# Nếu port đang là type khác (ví dụ webcache_port_t), sửa lại
sudo semanage port -m -t http_port_t -p tcp 8080
# Check context của Jenkins home — RPM cài đúng sẽ tự gán
ls -laZ /var/lib/jenkins
Nếu bạn thay đổi JENKINS_HOME sang thư mục khác, phải relabel tay:
sudo semanage fcontext -a -t jenkins_home_t "/custom/jenkins/home(/.*)?"
sudo restorecon -Rv /custom/jenkins/home
Bước 6: Cài và cấu hình Nginx reverse proxy
sudo dnf install -y nginx
sudo nano /etc/nginx/conf.d/jenkins.conf
Nội dung file /etc/nginx/conf.d/jenkins.conf:
upstream jenkins {
server 127.0.0.1:8080 fail_timeout=0;
}
server {
listen 80;
server_name jenkins.yourdomain.com;
location / {
proxy_pass http://jenkins;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host:$server_port;
# Upload size cho artifact
client_max_body_size 100m;
}
}
Bước 7: Bật SELinux boolean cho Nginx proxy
Lý do Nginx không forward được dù config đúng — boolean này mặc định tắt:
# Bật cho phép Nginx kết nối tới upstream
sudo setsebool -P httpd_can_network_connect 1
# Verify
getsebool httpd_can_network_connect
# httpd_can_network_connect --> on
# Start Nginx
sudo systemctl enable nginx
sudo systemctl start nginx
# Mở port 80
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --reload
# Đóng port 8080 lại — không cần expose nữa
sudo firewall-cmd --permanent --remove-port=8080/tcp
sudo firewall-cmd --reload
Bước 8: Buộc Jenkins chỉ listen trên localhost
Sau khi có Nginx, Jenkins không cần expose ra ngoài nữa. Thêm vào /etc/sysconfig/jenkins:
sudo nano /etc/sysconfig/jenkins
JENKINS_ARGS="--httpListenAddress=127.0.0.1"
JAVA_OPTS="-Xmx2g -Xms512m"
sudo systemctl restart jenkins
Bước 9: Unlock Jenkins lần đầu
# Lấy initial admin password
sudo cat /var/lib/jenkins/secrets/initialAdminPassword
Mở trình duyệt vào http://jenkins.yourdomain.com, nhập password trên, cài suggested plugins, tạo admin user. Tiếp theo vào Manage Jenkins → System → Jenkins URL và điền đúng domain — sai chỗ này là webhook GitHub/GitLab không hoạt động, debug mất cả buổi.
Những điều thực tế mình học sau 6 tháng chạy production
Công ty mình vẫn còn vài con server CentOS 7, và việc migrate sang AlmaLinux là bài toán mình đã xử lý rồi. Với máy chủ Jenkins mới thì mình chọn CentOS Stream 9 — vì đây là upstream trực tiếp của RHEL 9. Những gì chạy ổn ở Stream 9 gần như chắc chắn pass ở RHEL 9 khi upgrade sau, giảm rủi ro chuẩn hóa hạ tầng đáng kể.
Một số điểm thực tế sau 6 tháng vận hành:
- Backup JENKINS_HOME thường xuyên: Toàn bộ job config, build history, credentials đều ở
/var/lib/jenkins. Mình dùng cron job rsync sang S3-compatible storage mỗi đêm. Mất thư mục này là mất cả CI/CD setup. - Audit log trước khi debug code: Khi build job lạ bị fail, check
grep "avc: denied" /var/log/audit/audit.log | tail -30trước. Đã vài lần mình tốn cả tiếng mà nguyên nhân chỉ là SELinux deny một thao tác file nhỏ. - Giới hạn JVM heap: Trên server 4GB RAM trở xuống, mặc định Jenkins có thể ăn gần hết. Dòng
JAVA_OPTS="-Xmx2g -Xms512m"trong bước 8 không phải optional. - Không để Jenkins expose port 8080 ra internet: Nginx phía trước + Jenkins chỉ bind localhost là pattern chuẩn. Jenkins không có rate limiting tốt — để lộ 8080 là mời brute force.
Setup này đang chạy ngon cho team 8 người, trung bình 60-80 build mỗi ngày, không có downtime trong 6 tháng qua. SELinux enforcing, Nginx phía trước — không phải overhead lãng phí, mà là hàng rào cần thiết cho công cụ có quyền chạy code tùy ý như Jenkins.
