HAProxy Cluster
Here’s a detailed step-by-step guide on how to create and secure a cluster of 2 HAProxy VMs with Debian 12, each with 4 network interfaces. This guide assumes that you have already set up the VMs and installed Debian 12 on them.
Step 1: Install HAProxy, Keepalived, inotify-tools, sendmail, and iptables-persistent
sudo apt-get update
sudo apt-get install haproxy keepalived rsync inotify-tools iptables-persistent sendmail -y
Step 2: Configure Network Interfaces Edit the /etc/network/interfaces file and add the following for each interface:
# The loopback network interface
auto lo
iface lo inet loopback
# MGMT - eth0
auto eth0
iface eth0 inet static
address 192.168.0.31
netmask 255.255.255.0
gateway 192.168.0.1
dns-nameservers 192.168.62.41 192.168.62.42
metric 1
# VLAN61 - eth1
auto eth1
iface eth1 inet static
address 192.168.61.11
netmask 255.255.255.0
metric 2
post-up route add -net 192.168.61.0 netmask 255.255.255.0 gw 192.168.61.1
post-up route add -net <public_ip> netmask 255.255.255.255 gw 192.168.61.1
# VLAN62 - eth2
auto eth2
iface eth2 inet static
address 192.168.62.11
netmask 255.255.255.0
# VLAN63 - eth3
auto eth3
iface eth3 inet static
address 192.168.63.11
netmask 255.255.255.0
Replace the IP addresses with the ones for your VLANs.
Step 3: Configure HAProxy Edit the /etc/haproxy/haproxy.cfg file and replace the content with the following:
global
log /dev/log local0
log /dev/log local1 notice
chroot /var/lib/haproxy
stats socket /run/haproxy/admin.sock mode 660 level admin expose-fd listeners
stats socket ipv4@127.0.0.1:9999 level operator expose-fd listeners
stats timeout 30s
user haproxy
group haproxy
daemon
defaults
log global
mode http
option tcplog
option dontlognull
timeout connect 5000
timeout client 50000
timeout server 50000
# Stats page
listen stats
bind 127.0.0.1:8404
mode http
stats enable
stats uri /
stats realm HAProxy-N1\ Statistics
stats auth user:password
stats admin if TRUE
# Frontend defined
frontend apache-frontend
bind *:80
bind *:443 ssl crt /etc/haproxy/ssl/example.com.pem
redirect scheme https code 301 if !{ ssl_fc }
mode http
# ACL STATS
acl STATS_URL hdr_dom(host) -i stats.example.com
use_backend STATS if STATS_URL
# Frontend LDAP-3268
frontend 3268-frontend
bind *:3268
mode tcp
use_backend LDAP-3268 if { dst_port 3268 }
# Frontend LDAP-3269
frontend 3269-frontend
bind *:3269
mode tcp
use_backend LDAP-3269 if { dst_port 3269 }
# Frontend mail
frontend mail-frontend
bind *:25587
mode tcp
use_backend MAIL
backend LDAP-3268
mode tcp
balance roundrobin
server dc02 192.168.62.22:3268 check
server dc03 192.168.62.23:3268 check
backend LDAP-3269
mode tcp
balance roundrobin
server dc03 192.168.62.23:3269 check
server dc02 192.168.62.22:3269 check
backend MAIL
mode tcp
balance roundrobin
server mail <public_ip>:25587 check
backend STATS
server HA-N1 127.0.0.1:8404 check
Replace the IP addresses and ports with your actual servers. Also, replace /etc/ssl/private/example.com.pem with the path to your actual SSL certificate.
Step 4: Configure Keepalived Edit the /etc/keepalived/keepalived.conf file and add the following:
global_defs {
notification_email {
user@example.com
}
notification_email_from noreply@example.com
smtp_server smtp.example.com
smtp_connect_timeout 30
router_id HAProxy_N2
vrrp_skip_check_adv_addr
}
vrrp_script chk_haproxy {
script "/bin/bash -c 'pid_count=$(ps aux | grep -c [h]aproxy.pid); [ $pid_count -ge 2 ]'"
interval 2
weight 2
}
vrrp_instance VI_1 {
state MASTER
interface eth1
virtual_router_id 51
priority 100
advert_int 1
authentication {
auth_type PASS
auth_pass P@ssword
}
unicast_src_ip 192.168.61.11
unicast_peer {
192.168.61.12
}
virtual_ipaddress {
192.168.61.10
}
track_script {
chk_haproxy
}
notify_master "/etc/keepalived/master-backup.sh MASTER"
notify_backup "/etc/keepalived/master-backup.sh BACKUP"
notify_fault "/etc/keepalived/master-backup.sh FAULT"
Replace eth1
with the interface connected to VLAN61, and 192.168.61.10 with your VIP.
Step 5: Create the /etc/keepalived/master-backup.sh script
#!/bin/bash -x
STATE=$1
NOW=$(date +"%D %T")
KEEPALIVED="/etc/keepalived"
case $STATE in
"MASTER") touch $KEEPALIVED/MASTER
echo "$NOW Becoming MASTER" >> $KEEPALIVED/COUNTER
/usr/sbin/sendmail -Am -f noreply@example.com -t < /etc/keepalived/notif_master
exit 0
;;
"BACKUP") rm $KEEPALIVED/MASTER
echo "$NOW Becoming BACKUP" >> $KEEPALIVED/COUNTER
/usr/sbin/sendmail -Am -f noreply@example.com -t < /etc/keepalived/notif_backup
exit 0
;;
"FAULT") rm $KEEPALIVED/MASTER
echo "$NOW Becoming FAULT" >> $KEEPALIVED/COUNTER
/usr/sbin/sendmail -Am -f noreply@example.com -t < /etc/keepalived/notif_fault
exit 0
;;
*) echo "unknown state"
echo "$NOW Becoming UNKOWN" >> $KEEPALIVED/COUNTER
exit 1
;;
esac
Step 6: Configure sendmail
Edit /etc/mail/sendmail.mc and add at the end
LOCAL_CONFIG
Djexample.com
MAILER_DEFINITIONS
FEATURE(masquerade_envelope)dnl
FEATURE(masquerade_entire_domain)dnl
dnl MASQUERADE_AS(`example.com')dnl
dnl MASQUERADE_DOMAIN(`example.com')dnl
dnl MASQUERADE_DOMAIN(`haproxyn1.example.com')dnl
define(`SMART_HOST',`[smtp.example.com]')dnl
define(`RELAY_MAILER_ARGS', `TCP $h 25587')dnl
define(`ESMTP_MAILER_ARGS', `TCP $h 25587')dnl
define(`confAUTH_OPTIONS', `A p')dnl
define(`confTRUSTED_USERS',`root daemon asterisk example.user')
TRUST_AUTH_MECH(`EXTERNAL DIGEST-MD5 CRAM-MD5 LOGIN PLAIN')dnl
define(`confAUTH_MECHANISMS', `EXTERNAL GSSAPI DIGEST-MD5 CRAM-MD5 LOGIN PLAIN')dnl
FEATURE(`authinfo',`hash -o /etc/mail/authinfo/smtp-auth.db')dnl
MAILER(`local')dnl
MAILER(`smtp')dnl
then compile the conf file
m4 /etc/mail/sendmail.mc > /etc/mail/sendmail.cf
and restart sendmail
systemctl restart sendmail
Step 7: Configure Rsync with inotify-tools
It’s a good practice to create a dedicated user for rsync operations to enhance security. Here are the steps to create a user for rsync and set up SSH key-based authentication:
a) Create a dedicated user on both nodes:
Follow the prompts to set a password and fill in any additional information.
sudo adduser rsyncuser
b) Grant sudo privileges (if needed):
If you want the rsyncuser to have the ability to execute commands with elevated privileges (e.g., for updating files in protected directories), you can add it to the sudo group and you MUST add it to the root group in order to be able to replace the haproxy.cfg file which is owned by root:root:
sudo usermod -aG sudo rsyncuser
sudo usermod -aG root rsyncuser
c) Set up SSH key-based authentication:
On the first node, generate an SSH key pair for the rsyncuser:
sudo -u rsyncuser ssh-keygen -t rsa
Press Enter through the prompts to use the default settings.
d) Copy the public key to the second node:
Use ssh-copy-id to copy the public key to the second node. Replace second_node_ip with the actual IP address or hostname of the second node:
sudo -u rsyncuser ssh-copy-id rsyncuser@second_node_ip
You’ll be prompted to enter the password for rsyncuser on the second node.
e) Test SSH connectivity:
Ensure that you can SSH from the first node to the second node without entering a password:
sudo -u rsyncuser ssh rsyncuser@second_node_ip
If successful, you should be able to connect without a password prompt.
f) Adjust permissions (if necessary):
Make sure the rsyncuser has appropriate read and write permissions for the directories and files you intend to synchronize.
chown -R rsync: /etc/haproxy/
Create a script, let’s call it sync-haproxy.sh, with the following content:
#!/bin/bash
# Define your source and destination information
SOURCE_PATH="/etc/haproxy/haproxy.cfg"
DESTINATION_IP="192.168.0.32" # Replace with the IP address of the second node
DESTINATION_PATH="/etc/haproxy/haproxy.cfg"
# Watch for changes using inotifywait
inotifywait -e modify,create,delete -m "$SOURCE_PATH" |
while read -r directory events filename; do
# When a change occurs, sync the file using rsync
rsync -avz --delete -e "ssh -i /home/rsync/.ssh/id_rsa" \
--chmod=Dg+rwx,Du+rw,Dgo+rx,Fg+rw,Fo+r "$SOURCE_PATH" "rsync@192.168.0.32:$DESTINATION_PATH"
done
Replace 192.168.1.32 with the IP address of the other HAProxy VM.
Make the script executable and run it in the background:
chmod +x sync-haproxy.sh
nohup ./sync-haproxy.sh &
Step 8: Configure iptables-persistent First, flush all current rules:
iptables -F
Then, add the necessary rules:
iptables -A INPUT -i lo -j ACCEPT
iptables -A INPUT ! -i lo -d 127.0.0.0/8 -j DROP
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A OUTPUT -j ACCEPT
iptables -A INPUT -p tcp --dport 80 -j ACCEPT
iptables -A INPUT -p tcp --dport 443 -j ACCEPT
iptables -A INPUT -p tcp --dport 3268 -j ACCEPT
iptables -A INPUT -p tcp --dport 3269 -j ACCEPT
iptables -A INPUT -p tcp --dport 25587 -j ACCEPT
iptables -A INPUT -p tcp -m state --state NEW --dport 22 -j ACCEPT
iptables -A INPUT -p icmp --icmp-type echo-request -j ACCEPT
iptables -A INPUT -m limit --limit 5/min -j LOG --log-prefix "iptables denied: " --log-level 7
Finally, set DROP
as the default policy and save the rules:
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables-save > /etc/iptables/rules.v4
Replace the IP addresses with the ones for your backend servers and ethX with the corresponding network interfaces.