Skip to main content

Nginx Active-Passive Cluster

I have recently created my own nginx cluster. I use nginx as a reverse proxy and needed a high-availability solution. There’s already support for this in Nginx Plus , but I’m compiling my own version of the Open Source version of nginx, so I looked at their documentation as inspiration and created my own scripts. I have two servers a Primary (Node A - Active) and a Secondary (Node B - Passive), my Primary node is the where I edit/update my nginx configration and then it synchronizes the changes to my secondary node. Here’s what you need to get started

Prerequisites

  • 3 IP addresses - Each server needs an IP address and the last is used as the floating IP address which is the one users should access
  • 2 RHEL/CentOS Servers
  • Nginx - Webserver
  • Keepalived - VRRP software
  • Rsync - Synchronization software
Install software

Install the following packages on both servers

yum install -y nginx keepalived rsync
Firewall Configuration

This should be applied to both servers. If you are serving websites on non-standard ports, then remember to open them as well

firewall-cmd --add-service=http --permanent
firewall-cmd --add-service=https --permanent
firewall-cmd --add-rich-rule='rule protocol value="vrrp" accept' --permanent
firewall-cmd --reload
Tweak the Linux Kernel

The Nginx and Keepalived process needs the ability to bind to a non-local IP address. This is done by creating a file in /etc/sysctl.d/


echo "net.ipv4.ip_nonlocal_bind=1" > /etc/sysctl.d/90-keepalived.conf

NOTE. The above needs to be done on both servers

Node A - Primary Nginx Server - 192.168.1.11

  1. /etc/keepalived/keepalived.conf
! Configuration File for keepalived

global_defs {
	 enable_script_security					# Enable Script Security
	 script_user YOUR-USERNAME				# Run script as this user. For security reasons, don't use root
}

vrrp_script track_nginx {					# Tracking script to determine if the service is healthy
	script "/usr/sbin/pidof nginx"			# Checks if nginx is running
	interval 2								# Checks every 2 seconds
	timeout 1
}

vrrp_instance VI_1 {
    state MASTER							# MASTER/BACKUP
    interface ens192						# Name of interface to be used for VRRP
    virtual_router_id 51					# Router ID needs to match on all nodes
    priority 200							# A higher number has higher priority
    advert_int 1
    authentication {
      auth_type PASS
      auth_pass YOUR-PASSWORD				# Maximum 8 characters
    }
	
    unicast_peer {
		192.168.1.12						# IP address of our secondary node
	}
		
	virtual_ipaddress {
		192.168.1.10/24 dev ens192			# Floating/Virtual IP and the interface name to bind it to
    }

	track_script {
		track_nginx							# Tracking script defined above
	}
}
  1. Start Keepalived and Nginx

systemctl enable --now keepalived.service
systemctl enable --now nginx.service

Sync Nginx files

Now we need to detect changes in /etc/nginx and synchronize them to our secondary server. This is a two step process, first we need a script to do the actual synchronization and then we need to run it when a change has been made.

  1. Create a script called NginxSync.sh
#!/bin/bash
NodeB="192.168.1.12" # IP address of the Node B/Secondary nginx server

## Check if nginx configuration is valid before synchronization
if out=$(nginx -t 2>&1); then
	## Sync Files
	rsync -a --delete /etc/nginx/ $NodeB:/etc/nginx

	## Restart Nginx
	ssh $NodeB "systemctl restart nginx.service"

	echo "Success"
else
	echo "Failure, because $out"
fi	
1a. Make the script executable 
```bash
chmod +x NginxSync.sh


2. We need to create a ssh key and copy the public key to Node B
```bash

ssh-keygen -t rsa -b 4096 -C "Nginx Primary" -f ~/.ssh/id_NodeA_rsa -N ""

## Copy public key to Node B
ssh-copy-id -i ~./ssh/id_NodeA_rsa.pub root@192.168.1.12
  1. Now we need to create the monitor script. It’s made of two files placed in /etc/systemd/system/

    1. nginxFileChange.service
    [Unit]
    Description = Starts the synchronization job from Node A to Node B
    Documentation = man:systemd.service
    
    [Service]
    Type=oneshot
    ExecStart=/YOUR-PATH-TO/NginxSync.sh		# Remember to change this line to your needs
    

	2. **nginxFileChange.path**
	```bash

	[Unit]
	Description = Triggers the nginxFileChange.service which synchronizes changes
	Documentation = man:systemd.path

	[Path]
	PathModified=/etc/nginx/			# Path to the nginx config folder
	Unit=nginxFileChange.service

	[Install]
	WantedBy=multi-user.target		# Requires at least runlevel 3 otherwise our NginxSync.sh script wont work
	
3. Start the nginxFileChange.path service
```bash

systemctl daemon-reload
systemctl enable --now nginxFileChange.path		# Enables the file monitor check
systemctl status nginxFileChange.service		# Shows status of the sync service


### Node B - Secondary Nginx Server - 192.168.1.12
1. /etc/keepalived/keepalived.conf
```bash

! Configuration File for keepalived

global_defs {
	 enable_script_security
	 script_user root
}

vrrp_script track_nginx {
	script "/usr/bin/killall -0 nginx"
	interval 2
	timeout 1
}

vrrp_instance VI_1 {
    state BACKUP
    interface ens192
    virtual_router_id 51
    priority 100
    advert_int 1
    authentication {
      auth_type PASS
      auth_pass YOUR-PASSWORD
    }
	
    unicast_peer {
		192.168.1.11
	}
		
	virtual_ipaddress {
		192.168.1.10/24 dev ens192
    }

	track_script {
		track_nginx
	}
}
  1. Start Keepalived and Nginx
systemctl enable --now keepalived.service
systemctl enable --now nginx.service

Troubleshooting

Here is a few useful commands to see if it’s working

ip address								# Shows network interfaces and IP

journalctl -r -u keepalived				# Shows nginx systemd log
journalctl -r -u nginx					# Shows keepalived systemd log

systemctl enable service-name.service		# Auto starts the service at boot
systemctl enable --now service-name.service	# Equal to systemctl enable + systemctl start 

systemctl status nginx.service			# Shows service status
systemctl status keepalived.service
systenctl status nginxFileChange.path

systemctl start nginx.service			# Start Service
systemctl start keepalived.service
systemctl start nginxFileChange.path

systemctl stop nginx.service			# Stops Service
systemctl stop keepalived.service
systemctl stop nginxFileChange.path