Hướng dẫn thiết lập Load Balancer với HAProxy trên Linux

Network tutorial - IT technology blog
Network tutorial - IT technology blog

Làm ngay trong 15 phút: HAProxy cơ bản

Server nginx duy nhất của công ty bắt đầu response time lên 8–12 giây vào giờ cao điểm — CPU chạm 95%, không còn chỗ để scale vertical. Thay vì nâng phần cứng, mình dựng thêm 2 backend server và đặt HAProxy ở trước. Từ lúc bắt đầu cài đến khi traffic phân tải đều, mất khoảng 15–20 phút.

Phần này giúp bạn dựng được stack tương tự từ con số 0.

Topology cần chuẩn bị

Internet
    ↓
[HAProxy] 192.168.1.10:80
    ↓           ↓
[Backend1]  [Backend2]
192.168.1.11  192.168.1.12
    (nginx)      (nginx)

Cài đặt HAProxy

# Ubuntu/Debian
sudo apt update && sudo apt install -y haproxy

# CentOS/RHEL
sudo dnf install -y haproxy

# Kiểm tra version
haproxy -v

File cấu hình tối thiểu để chạy ngay

Sửa file /etc/haproxy/haproxy.cfg:

global
    log /dev/log local0
    maxconn 4096
    user haproxy
    group haproxy
    daemon

defaults
    log     global
    mode    http
    option  httplog
    option  dontlognull
    timeout connect 5s
    timeout client  30s
    timeout server  30s

frontend web_frontend
    bind *:80
    default_backend web_servers

backend web_servers
    balance roundrobin
    server backend1 192.168.1.11:80 check
    server backend2 192.168.1.12:80 check
# Kiểm tra config trước khi restart
sudo haproxy -c -f /etc/haproxy/haproxy.cfg

# Restart service
sudo systemctl restart haproxy
sudo systemctl enable haproxy

Trỏ browser vào IP của HAProxy server — request sẽ luân phiên sang backend1 và backend2. Xong phần cơ bản.

Giải thích chi tiết: Các thuật toán load balancing

Không cần biết hết — 3 thuật toán sau là đủ cho 90% trường hợp. Mình chọn cái nào phụ thuộc vào đặc tính request của từng service:

roundrobin — Mặc định, phù hợp hầu hết trường hợp

Request được phân đều theo vòng: 1→2→1→2. Tốt khi các backend server có cùng cấu hình phần cứng.

backend web_servers
    balance roundrobin
    server backend1 192.168.1.11:80 check weight 1
    server backend2 192.168.1.12:80 check weight 1

leastconn — Dùng khi request có thời gian xử lý không đều

Request mới luôn đi đến server đang rảnh nhất — tức server có ít connection nhất tại thời điểm đó. Phù hợp cho API backend có cả endpoint upload file lẫn endpoint trả JSON nhẹ chạy chung một pool.

backend api_servers
    balance leastconn
    server api1 192.168.1.21:8080 check
    server api2 192.168.1.22:8080 check

source — Sticky session theo IP client

Cùng một IP luôn được route đến cùng một backend. Dùng khi ứng dụng lưu session local trên từng server mà chưa migrate sang Redis.

backend legacy_app
    balance source
    hash-type consistent
    server app1 192.168.1.31:80 check
    server app2 192.168.1.32:80 check

Health check nâng cao

Mặc định, check chỉ kiểm tra TCP connection. Để kiểm tra HTTP response thực sự:

backend web_servers
    balance roundrobin
    option httpchk GET /health HTTP/1.1\r\nHost:\ example.com
    http-check expect status 200
    server backend1 192.168.1.11:80 check inter 5s rise 2 fall 3
    server backend2 192.168.1.12:80 check inter 5s rise 2 fall 3

Ý nghĩa các tham số:

  • inter 5s — kiểm tra mỗi 5 giây
  • rise 2 — cần 2 lần check thành công liên tiếp để server được coi là UP
  • fall 3 — 3 lần check thất bại liên tiếp → server bị đánh dấu DOWN

Mình deploy endpoint /health trả 200 OK trên mỗi backend — không phụ thuộc vào database hay cache, chỉ trả về status của chính process đó. Cách này giúp HAProxy phân biệt được backend thực sự chết với backend đang bận xử lý nặng.

Nâng cao: Stats page, SSL termination và ACL

Bật Stats Dashboard

HAProxy có built-in dashboard web — mở ra là thấy ngay từng backend đang UP hay DOWN, số connection hiện tại, error rate, response time trung bình. Không cần cài thêm gì:

frontend stats
    bind *:8404
    stats enable
    stats uri /stats
    stats refresh 10s
    stats auth admin:matkhau_manh
    stats hide-version

Truy cập http://192.168.1.10:8404/stats để thấy toàn bộ backend status, số kết nối, response time.

SSL Termination — Xử lý HTTPS tại HAProxy

Để HAProxy nhận HTTPS và forward HTTP đến backend (backend không cần cài SSL):

# Gộp cert và key vào 1 file .pem
cat /etc/ssl/certs/example.com.crt /etc/ssl/private/example.com.key \
    > /etc/haproxy/certs/example.com.pem
chmod 600 /etc/haproxy/certs/example.com.pem
frontend web_frontend
    bind *:80
    bind *:443 ssl crt /etc/haproxy/certs/example.com.pem
    # Redirect HTTP → HTTPS
    http-request redirect scheme https unless { ssl_fc }
    # Forward header để backend biết client dùng HTTPS
    http-request set-header X-Forwarded-Proto https if { ssl_fc }
    default_backend web_servers

ACL — Routing theo path hoặc domain

ACL cho phép HAProxy phân loại request và route đến backend pool khác nhau. Ví dụ điển hình: /api/ đến API server, static files đến CDN cache, còn lại đến web server:

frontend web_frontend
    bind *:80
    acl is_api path_beg /api/
    acl is_static path_end .jpg .png .css .js
    use_backend api_servers if is_api
    use_backend static_servers if is_static
    default_backend web_servers

backend api_servers
    balance leastconn
    server api1 192.168.1.21:8080 check

backend static_servers
    balance roundrobin
    server cdn1 192.168.1.31:80 check

backend web_servers
    balance roundrobin
    server web1 192.168.1.11:80 check
    server web2 192.168.1.12:80 check

Tips thực tế từ môi trường production

Reload config không downtime

Đây là điểm mình thích nhất ở HAProxy — reload cấu hình mà không drop connection đang có:

# Kiểm tra config trước
sudo haproxy -c -f /etc/haproxy/haproxy.cfg

# Reload graceful (không drop connection)
sudo systemctl reload haproxy
# hoặc
sudo kill -USR2 $(cat /var/run/haproxy.pid)

Drain server trước khi maintenance

Trước khi update backend, mình luôn drain traffic thay vì tắt server thẳng tay. Dùng weight 0 qua HAProxy runtime socket:

# Kết nối vào HAProxy runtime API
echo "set weight web_servers/backend1 0" | \
    sudo socat stdio /var/run/haproxy/admin.sock

# Xem trạng thái
echo "show servers state" | \
    sudo socat stdio /var/run/haproxy/admin.sock

# Sau khi update xong, restore weight
echo "set weight web_servers/backend1 100" | \
    sudo socat stdio /var/run/haproxy/admin.sock

Nhớ thêm dòng này vào section global — thiếu thì socat sẽ báo lỗi không kết nối được:

stats socket /var/run/haproxy/admin.sock mode 660 level admin

Log chi tiết để debug

502 từ client, không biết backend nào gây ra — đây là cách mình setup log format để trace xuống tận server cụ thể:

frontend web_frontend
    bind *:80
    option httplog
    log-format "%ci:%cp [%tr] %ft %b/%s %TR/%Tw/%Tc/%Tr/%Ta %ST %B %tsc %ac/%fc/%bc/%sc/%rc %{+Q}r"
    default_backend web_servers
# Xem log realtime
sudo tail -f /var/log/haproxy.log | grep "502"

Field %b/%s trong log format là tên backend và server cụ thể xử lý request đó. Chính nhờ cái này mình phát hiện backend1 đang trả 502 vì PHP-FPM bị đầy worker — trong khi backend2 vẫn hoàn toàn bình thường.

Giới hạn connection per IP để chống abuse

frontend web_frontend
    bind *:80
    # Giới hạn 100 connections đồng thời mỗi IP
    stick-table type ip size 100k expire 30s store conn_cur
    tcp-request connection track-sc1 src
    tcp-request connection reject if { sc_conn_cur(1) gt 100 }
    default_backend web_servers

Mình áp dụng cái này sau khi một script crawler vô tình hammer hệ thống với 500 connections đồng thời từ 1 IP — HAProxy giờ tự block trước khi request chạm đến nginx.

Share: