Ngần đây tôi có gặp một bài toán về triển khai HAproxy trên một dịch vụ cho khách hàng, khách hàng có một yêu cầu khá là khó đó là phải cấu hình được backend IP với chế độ Dynamic mà trong khi kiến thức của tôi chỉ biết tới statics IP với Haproxy backend. Tôi đã mất 2 ngày để tìm hiểu và cấu hình được đây là bài note lại những gì tôi đã làm để hoàn thành song dự án này
I: Bài toán
- Khách hàng có số lượng IP backend không cố định lúc thì 10 lúc thì 100 lúc thì 20…. cón số này nhẩy nhanh và nhiều cứ 1 tiếng thay đổi thậm chí khi có event thì nó thay đổi còn nhanh hơn
- Khách hàng không cố định IP của backend mà để chế độ Dynamic IP thay đổi lung tung may một điều có 1 API để tôi có thể gọi tới tổng hợp được hết IP:PORT của các máy chủ backend
II: Giải Pháp
Bình thường chúng ta thường nghĩ tới việc là viết một file python hoặc script để làm việc lấy IP và thay vào phần server name của HAproxy tuy nhiên tôi có cách khác dùng tới tính năng của HA mà không cần dùng phần mềm hay script can thiệp tránh ảnh hưởng tối đa tới hoạt động của máy chủ trong bài này tôi sẽ chia sẻ cả 2 giải pháp và mã nguồn của chúng
2.1: Giải pháp dùng python can thiệp theo số lượng IP
Đầu tiên tôi cần một đoạn mã làm nhiệm vụ tải toàn bộ các IP backend với định dạng IP:PORT về lưu lại
import requests
import time
def fetch_proxies(url):
response = requests.get(url)
proxies = response.text.strip().split("\n")
return proxies
def clean_proxy(proxy):
return proxy.replace("\r", "").strip()
def save_proxies(proxies, file_path):
with open(file_path, 'w') as f:
for proxy in proxies:
cleaned_proxy = clean_proxy(proxy)
f.write(cleaned_proxy + "\n")
def update_proxies(url, file_path, interval=60):
while True:
proxies = fetch_proxies(url)
save_proxies(proxies, file_path)
print(f"Proxies updated: {len(proxies)} proxies saved.")
time.sleep(interval)
url = "https://api.localdomain/ip"
file_path = "/etc/haproxy/proxies.txt"
update_proxies(url, file_path, interval=180) # Cập nhật mỗi 5 phútĐoạn code trên chúng ta sẽ tải danh sách các ip và lưu vào file proxies.txt trong thư mục /etc/haproxy. Bây giờ chúng ta sẽ dựa vào số dòng của file này ghi được để tạo ra file haproxy.cfg
import os
proxy_file = '/etc/haproxy/proxies.txt'
haproxy_conf = '/etc/haproxy/haproxy.cfg'
# Kiểm tra và xóa tệp haproxy.cfg nếu tồn tại
if os.path.exists(haproxy_conf):
os.remove(haproxy_conf)
print(f"Đã xóa tệp {haproxy_conf}")
max_servers = 10000 # Số lượng server tối đa
with open(proxy_file, 'r') as pf, open(haproxy_conf, 'w') as hf:
hf.write("""
global
maxconn 1024
daemon
log /dev/log local0
log /dev/log local1 notice
defaults
mode http
maxconn 1024
option httplog clf
option dontlognull
retries 3
timeout connect 5s
timeout client 60s
timeout server 60s
resolvers mydns
nameserver dns1 8.8.8.8:53
nameserver dns2 8.8.4.4:53
resolve_retries 3
timeout resolve 1s
timeout retry 1s
hold other 30s
hold refused 30s
hold nx 30s
hold timeout 30s
hold valid 10s
listen stats
bind *:10000
mode http
log /dev/log local0
log /dev/log local1 notice
maxconn 10
timeout client 100s
timeout server 100s
timeout connect 100s
timeout queue 100s
stats enable
stats hide-version
stats refresh 30s
stats show-node
stats uri /
frontend rotating_sv
mode http
log /dev/log local0
log /dev/log local1 notice
bind *:9999
default_backend backend_sv
backend backend_sv
mode http
balance roundrobin
""")
for i, line in enumerate(pf):
if i >= max_servers:
break
ip_port = line.strip()
hf.write(f" server proxy{i+1} {ip_port} check\n")Bây giờ chúng ta cần thêm 1 file để giám sát nếu file proxies.txt có thay đổi thì cũng cập nhập luôn cấu hình HA
import os
import time
def watch_file(file_path, callback, interval=60):
""" Watch file modification and call callback if file is modified """
last_mtime = os.path.getmtime(file_path)
while True:
time.sleep(interval)
mtime = os.path.getmtime(file_path)
if mtime != last_mtime:
last_mtime = mtime
callback()
def reload_haproxy():
""" Reload HAProxy using systemctl """
os.system("python3 new_ip_update.py")
os.system("systemctl reload haproxy")
print("HAProxy reloaded")
file_path = "/etc/haproxy/proxies.txt"
watch_file(file_path, reload_haproxy, interval=180)Trong đoạn code trên cứ 180s bạn có thể thay đổi thời gian cho phù hợp thì hệ thống sẽ check file proxies.txt một lần và nếu có thay đổi nó sẽ tạo ra file haproxy.cfg mới cùng với việc reload lại cấu hình như vậy việc cấu hình Dynamic Backend IP cho HA đã song, bạn muốn cập nhập hay chậm thì thay đổi thời gian check của các file và thời gian chờ tải lại file là song
2: Giải pháp cấu hình HAproxy nhanh gọn
Sau khi tìm hiểu các giải pháp để hạn chế tối đa việc tôi phải chạy tools bên ngoài thì tôi nhận thấy phần server backend ngoài việc nó phải là dạng IP thì nó cũng nhận dạng là domain và phân giải DNS thông qua cấu hình resolvers dns trên HA từ đó làm IP backend và truy cập điều này làm tôi nảy ra ý tưởng điều gì sẽ xảy ra nếu domain của tôi trỏ tới nhiều IP cùng một lúc?
service.localdomain.com IN A 10.0.0.1
A 10.0.0.2
A 10.0.0.3
A 10.0.0.4
A 10.0.0.5
A 10.0.0.6
A ........
Điều này có được là do bản ghi A của domain là không giới hạn bạn trỏ bao nhiêu tùy thích, Và tôi nhận thấy cách này hoàn toàn hoạt động được với cấu hình haproxy như sau
global
maxconn 1024
daemon
log /dev/log local0
log /dev/log local1 notice
#pidfile <%= pid_file %>
defaults
mode http
maxconn 1024
option httplog clf
option dontlognull
retries 3
timeout connect 5s
timeout client 60s
timeout server 60s
resolvers mydns
nameserver dns1 127.0.0.1:53
nameserver dns2 127.0.0.1:53
resolve_retries 3
timeout resolve 1s
timeout retry 1s
hold other 30s
hold refused 30s
hold nx 30s
hold timeout 30s
hold valid 10s
listen stats
bind *:10000
mode http
log /dev/log local0
log /dev/log local1 notice
maxconn 10
timeout client 100s
timeout server 100s
timeout connect 100s
timeout queue 100s
stats enable
stats hide-version
stats refresh 30s
stats show-node
stats uri /
frontend rotating_sv
mode http
log /dev/log local0
log /dev/log local1 notice
bind *:9999
default_backend backend_sv
backend backend_sv
mode http
balance roundrobin
server a001 172.183.241.1:8080 check
server sv service.localdomain.com check resolvers mydnsTôi nhận thấy lúc này HA hoạt động bình thường nghĩa là nó đã chạy theo một đường nào đó vào backend giờ tôi cần nhân nó ra và nhận nhiều IP nhất có thể thì tôi cấu hình server-template cho nó
global
maxconn 1024
daemon
log /dev/log local0
log /dev/log local1 notice
#pidfile <%= pid_file %>
defaults
mode http
maxconn 1024
option httplog clf
option dontlognull
retries 3
timeout connect 5s
timeout client 60s
timeout server 60s
resolvers mydns
nameserver dns1 127.0.0.1:53
nameserver dns2 127.0.0.1:53
accepted_payload_size 8192
resolve_retries 3
timeout resolve 1s
timeout retry 1s
hold other 30s
hold refused 30s
hold nx 30s
hold timeout 30s
hold valid 10s
listen stats
bind *:10000
mode http
log /dev/log local0
log /dev/log local1 notice
maxconn 10
timeout client 100s
timeout server 100s
timeout connect 100s
timeout queue 100s
stats enable
stats hide-version
stats refresh 30s
stats show-node
stats uri /
frontend rotating_sv
mode http
log /dev/log local0
log /dev/log local1 notice
bind *:9999
default_backend backend_sv
backend backend_sv
mode http
balance roundrobin
server-template sv 1-300 sv service.localdomain.com:8080 check resolvers mydns init-addr noneVới server-template thì lúc này hệ thống sẽ tạo ra mặc định 300 sv backend và lấy IP từ phân giải domain service.localdomain.com để lấy IP và set vào backend thứ duy nhất tôi cần làm đó là điều chỉnh cái DNS của domain đó trên local bằng cách thay đổi file host của OS theo cái API Lấy được vậy là song, HA sẽ tự động cập nhập IP ta có thể set thời gian cho nó mà không lo chết hay phải reload lại HA nữa đỡ cực hơn rất nhiều việc phải sinh lại cấu hình của haproxy liên tục.
Giải pháp này có một lưu ý đó là bạn không nên dùng máy chủ DNS bên ngoài vì bạn sẽ phải truy vấn nhiều có thể dẫn tới các vấn đề về cập nhập hãy dùng local thôi và set file host là nhanh nhất thay đổi nó cũng nhanh.
Update 1: Nếu trong trường hợp các port của backend là khác nhau thì chúng ta có một giải pháp là sử dụng bản ghi dns SRV trong loại bản ghi này thì chúng ta có thể khai bào được cả IP và PORT bạn cần thay đổi domain trong cấu hình HA và máy chủ DNS và bản ghi SRV của mình thành domain _myservice._tcp.localdomain.com ví dụ cấu hình sẽ thành
global
maxconn 1024
daemon
log /dev/log local0
log /dev/log local1 notice
#pidfile <%= pid_file %>
defaults
mode http
maxconn 1024
option httplog clf
option dontlognull
retries 3
timeout connect 5s
timeout client 60s
timeout server 60s
resolvers mydns
nameserver dns1 127.0.0.1:53
nameserver dns2 127.0.0.1:53
accepted_payload_size 8192
resolve_retries 3
timeout resolve 1s
timeout retry 1s
hold other 30s
hold refused 30s
hold nx 30s
hold timeout 30s
hold valid 10s
listen stats
bind *:10000
mode http
log /dev/log local0
log /dev/log local1 notice
maxconn 10
timeout client 100s
timeout server 100s
timeout connect 100s
timeout queue 100s
stats enable
stats hide-version
stats refresh 30s
stats show-node
stats uri /
frontend rotating_sv
mode http
log /dev/log local0
log /dev/log local1 notice
bind *:9999
default_backend backend_sv
backend backend_sv
mode http
balance roundrobin
server-template sv 1-300 sv _myservice._tcp.localdomain.com check resolvers mydns init-addr noneĐiều này làm được là do bản ghi SRV có cấu trúc
_myservice._tcp.localdomain.com 10 1 8080 10.10.1.1
_myservice._tcp.localdomain.com 20 1 9090 10.10.1.2
_myservice._tcp.localdomain.com 10 1 8080 10.10.1.3
_myservice._tcp.localdomain.com 20 1 9090 10.10.1.4và khi chúng ta kiểm tra dns trên máy chủ
dig @127.0.0.1 -p 53 SRV _myservice._tcp.localdomain.comKết quả chúng ta sẽ nhận được
;; QUESTION SECTION:
;_myservice._tcp.localdomain.com. IN
;; ANSWER SECTION:
_myservice._tcp.localdomain.com. 0 IN SRV 0 0 8080 host1.
_myservice._tcp.localdomain.com. 0 IN SRV 0 0 9090 host2.
_myservice._tcp.localdomain.com. 0 IN SRV 0 0 8080 host3.
_myservice._tcp.localdomain.com. 0 IN SRV 0 0 9090 host4.
;; ADDITIONAL SECTION:
host1. 0 IN A 10.10.1.1
host2. 0 IN A 10.10.1.2
host3. 0 IN A 10.10.1.3
host4. 0 IN A 10.10.1.4Từ đó cấu hình này có thể được đẩy vào HAproxy một cách đơn giản