Khi một server không còn đủ dùng
Hồi CentOS 8 EOL, mình phải migrate gấp 5 server sang Rocky Linux trong 1 tuần — đó cũng là lần đầu tiên mình thực sự hiểu tại sao load balancer lại cần thiết đến vậy. Trước đó mình cứ nghĩ “server mạnh là đủ”. Sai hoàn toàn. Phần cứng tốt đến mấy cũng không giúp được gì khi traffic tăng gấp 5 lần chỉ trong vài giờ — hay khi cần deploy mà không thể để downtime dù chỉ 30 giây.
HAProxy (High Availability Proxy) là tool mình quay lại nhiều nhất. Nhẹ, ổn định, free. Nginx hay Apache cũng làm được load balancing, nhưng đó không phải việc chính của chúng — HAProxy được thiết kế từ đầu cho mục đích này, nên xử lý vài chục nghìn request/giây trên hardware tầm trung mà CPU vẫn rảnh.
Layer 7 nghĩa là HAProxy hiểu được nội dung HTTP — nó route traffic dựa trên URL path, header, cookie… Khác với Layer 4 chỉ nhìn vào IP/port, Layer 7 cho phép: /api/* đi server A, /static/* đi server B, hay sticky session theo cookie người dùng. Thực tế hơn: frontend React đi một pool, REST API đi pool khác — tất cả phía sau cùng một IP public.
Cài đặt HAProxy trên CentOS Stream 9
Mô hình lab
- Load Balancer:
192.168.1.10— máy CentOS Stream 9 cài HAProxy - Backend 1:
192.168.1.21— Apache hoặc Nginx đang chạy - Backend 2:
192.168.1.22— Apache hoặc Nginx đang chạy
Cài package
CentOS Stream 9 đã có HAProxy trong repo AppStream, cài thẳng bằng dnf:
sudo dnf install -y haproxy
haproxy -v
Chạy haproxy -v để xác nhận — AppStream thường đi với HAProxy 2.4.x, đủ dùng cho phần lớn production workload.
Xử lý SELinux trước khi cấu hình
Đây là chỗ nhiều người bỏ qua rồi than “sao không kết nối được backend”. SELinux mặc định sẽ block HAProxy kết nối đến port tùy ý. Cần bật boolean sau:
# Cho phép HAProxy kết nối đến backend
sudo setsebool -P haproxy_connect_any 1
# Kiểm tra đã bật chưa
getsebool haproxy_connect_any
Nếu backend dùng port không chuẩn như 8080, cần khai báo thêm vào SELinux policy:
sudo semanage port -a -t http_port_t -p tcp 8080
Mở port với firewalld
Port 80 cho client, port 8404 cho Stats monitoring page — mở một lần, reload là xong:
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-port=8404/tcp
sudo firewall-cmd --reload
# Xác nhận
sudo firewall-cmd --list-all
Cấu hình HAProxy Layer 7
Viết file cấu hình
Backup file gốc rồi tạo cấu hình mới:
sudo cp /etc/haproxy/haproxy.cfg /etc/haproxy/haproxy.cfg.bak
sudo nano /etc/haproxy/haproxy.cfg
#---------------------------------------------------------------------
# Global settings
#---------------------------------------------------------------------
global
log /dev/log local0
log /dev/log local1 notice
chroot /var/lib/haproxy
pidfile /var/run/haproxy.pid
maxconn 4000
user haproxy
group haproxy
daemon
stats socket /var/lib/haproxy/stats
#---------------------------------------------------------------------
# Defaults áp dụng cho tất cả frontend/backend
#---------------------------------------------------------------------
defaults
mode http
log global
option httplog
option dontlognull
option http-server-close
option forwardfor except 127.0.0.0/8
option redispatch
retries 3
timeout http-request 10s
timeout queue 1m
timeout connect 10s
timeout client 1m
timeout server 1m
timeout http-keep-alive 10s
timeout check 10s
maxconn 3000
#---------------------------------------------------------------------
# Stats page — http://192.168.1.10:8404/stats
#---------------------------------------------------------------------
frontend stats
bind *:8404
stats enable
stats uri /stats
stats refresh 10s
stats auth admin:StrongPassword123
stats admin if TRUE
#---------------------------------------------------------------------
# Frontend chính — nhận request từ client
#---------------------------------------------------------------------
frontend http_front
bind *:80
default_backend web_servers
# Layer 7 routing: /api/* đi backend riêng
acl is_api path_beg /api/
use_backend api_servers if is_api
#---------------------------------------------------------------------
# Backend web servers
#---------------------------------------------------------------------
backend web_servers
balance roundrobin
option httpchk GET /health
http-check expect status 200
server web1 192.168.1.21:80 check inter 5s fall 3 rise 2
server web2 192.168.1.22:80 check inter 5s fall 3 rise 2
#---------------------------------------------------------------------
# Backend API servers
#---------------------------------------------------------------------
backend api_servers
balance leastconn
option httpchk GET /api/health
http-check expect status 200
server api1 192.168.1.21:8080 check inter 5s fall 3 rise 2
server api2 192.168.1.22:8080 check inter 5s fall 3 rise 2
Giải thích các tham số quan trọng
- balance roundrobin: Chia đều request theo vòng tròn. Dùng
leastconnnếu muốn gửi đến server ít kết nối nhất — phù hợp với API có xử lý lâu. - option httpchk GET /health: HAProxy gửi HTTP GET đến
/healthđể kiểm tra backend còn sống không. - inter 5s: Check mỗi 5 giây.
- fall 3: Fail 3 lần liên tiếp mới đánh dấu DOWN.
- rise 2: Success 2 lần liên tiếp mới đưa server trở lại UP.
- option forwardfor: Thêm header
X-Forwarded-Forđể backend biết IP thật của client.
Tạo endpoint /health trên backend
Backend cần trả 200 OK để HAProxy xác nhận còn sống. Với Apache, cách nhanh nhất:
# Chạy trên từng backend server
echo "OK" | sudo tee /var/www/html/health
Khởi động HAProxy
# Validate cấu hình trước
sudo haproxy -c -f /etc/haproxy/haproxy.cfg
# Start và enable
sudo systemctl start haproxy
sudo systemctl enable haproxy
# Kiểm tra trạng thái
sudo systemctl status haproxy
Kiểm tra và Monitoring
Truy cập Stats Page
Truy cập http://192.168.1.10:8404/stats trên trình duyệt, đăng nhập bằng admin / StrongPassword123. Dashboard khá đủ dùng cho monitoring hằng ngày — hiển thị trực tiếp:
- Trạng thái từng backend (xanh = UP, đỏ = DOWN)
- Số request, bytes in/out, session hiện tại
- Response time trung bình và tỉ lệ lỗi
Test load balancing bằng curl
Thay vì đoán, cứ curl thẳng 10 request liên tiếp để thấy HAProxy phân phối thế nào:
for i in {1..10}; do
curl -s http://192.168.1.10/ -o /dev/null -w "%{http_code} from: %{url_effective}\n"
done
Nếu mỗi backend trả server name khác nhau trong response header, thấy sự phân phối rõ hơn:
for i in {1..6}; do
curl -si http://192.168.1.10/ | grep -i 'x-served-by\|server:'
done
Kiểm tra trạng thái backend qua socket
Muốn xem chi tiết từng server mà không cần mở browser:
echo "show servers state" | sudo socat stdio /var/lib/haproxy/stats
Xem log real-time
Mở terminal riêng và chạy song song khi test — log HAProxy cực kỳ chi tiết:
sudo journalctl -u haproxy -f
Mỗi dòng log ghi đủ: client IP, request path, response code, thời gian xử lý, và backend nào đã phục vụ request đó.
Thử nghiệm failover
Test thực tế nhất: tắt hẳn một backend, xem HAProxy phản ứng thế nào:
# Trên backend server 192.168.1.21
sudo systemctl stop httpd
# Trên load balancer, theo dõi log
sudo journalctl -u haproxy -f
Trong vòng 15 giây (3 lần check × 5 giây/lần), HAProxy đánh dấu web1 là DOWN và chuyển toàn bộ traffic sang web2. Stats page hiển thị web1 màu đỏ. Khi bật lại httpd, sau 10 giây (2 lần check thành công), web1 tự động trở lại UP.
Bài học từ thực tế
Sau cái vụ migrate CentOS 8 gấp đó, mình rút ra một điều: luôn kiểm tra logic của health check endpoint, không chỉ kiểm tra web server còn sống. Có lần HAProxy báo web1 UP màu xanh lá, nhưng thực ra ứng dụng đang lỗi do database mất kết nối — chỉ là webserver vẫn response 200 cho /health. Từ đó mình thêm logic vào endpoint /health: kiểm tra database ping, cache connection… nếu bất kỳ thứ gì fail thì trả về 500 để HAProxy biết mà tránh server đó.
Tổng kết
Chừng này là đủ để có một load balancer Layer 7 thực chiến trên CentOS Stream 9 — SELinux bật, firewalld đúng chuẩn, không phải tắt thứ gì cho nhanh. HAProxy tự phát hiện backend lỗi và failover, Stats page monitor real-time, ACL Layer 7 xử lý việc route traffic theo URL path.
Muốn đi xa hơn: thêm SSL/TLS termination tại HAProxy với Let’s Encrypt, hoặc cấu hình sticky session cho app stateful. Còn ngay lúc này, khi một backend gặp sự cố, HAProxy tự chuyển traffic sang cái còn lại — người dùng không biết có gì xảy ra.

