In this 24th article in the DevOps series, we will learn how to set up HAProxy as a load balancer for multiple Nginx Web servers using Ansible.
HAProxy is free, open source, highly available, load balancer software written by Willy Tarreau in 2000. It is implemented in the C programming language. It is known for its high performance and is extremely reliable and secure. It supports both Layer 4 (TCP) and Layer 7 (HTTP) based application load balancing, and is released under the GPLv2 licence. Nginx is a Web server created by Igor Sysoev, and is also written in the C programming language. It can be used as a reverse proxy, mail proxy and as an HTTP cache. It was first released in 2004 and uses the 2-clause BSD licence.
Setup
The HAProxy and Nginx installations use CentOS 7 (x86_64) as their base operating system. A single instance is launched using KVM for running HAProxy. A couple of CentOS VMs are provisioned to install and configure Nginx. The centos users in all the VMs are given sudo access using the visudo command. SELinux is disabled for the exercise.
The host system is a Parabola GNU/Linux-libre x86_64 system and Ansible is installed using the distribution package manager. The version of Ansible used is 2.6.0 as indicated below:
$ ansible --version ansible 2.6.0 config file = /etc/ansible/ansible.cfg configured module search path = [‘/home/guest/.ansible/plugins/modules’, ‘/usr/share/ansible/plugins/modules’] ansible python module location = /usr/lib/python3.6/site-packages/ansible executable location = /usr/bin/ansible python version = 3.6.5 (default, May 11 2018, 04:00:52) [GCC 8.1.0]
The Ansible inventory, files and playbook directories are created on the host system as follows:
ansible/inventory/kvm/ /playbooks/configuration/ /files/
The inventory/kvm/inventory file contains the following:
[front] haproxy ansible_host=192.168.122.113 ansible_connection=ssh ansible_user=centos ansible_password=password [web1] nginx1 ansible_host=192.168.122.248 ansible_connection=ssh ansible_user=centos ansible_password=password [web2] nginx2 ansible_host=192.168.122.147 ansible_connection=ssh ansible_user=centos ansible_password=password [web:children] web1 web2
The ‘front’ group contains the HAProxy instance information. The couple of Nginx Web servers are grouped together under a ‘web’ group, and can also be accessed individually. You can test connectivity from Ansible to the CentOS guest VMs using the following Ansible commands:
$ ansible -i inventory/kvm/inventory haproxy -m ping haproxy | SUCCESS => { “changed”: false, “ping”: “pong” } $ ansible -i inventory/kvm/inventory nginx1 -m ping nginx1 | SUCCESS => { “changed”: false, “ping”: “pong” } $ ansible -i inventory/kvm/inventory nginx2 -m ping nginx2 | SUCCESS => { “changed”: false, “ping”: “pong” } $ ansible -i inventory/kvm/inventory web -m ping nginx1 | SUCCESS => { “changed”: false, “ping”: “pong” } nginx2 | SUCCESS => { “changed”: false, “ping”: “pong” }
Nginx
We will first set up the Nginx Web servers. The EPEL release RPM is installed and the HAProxy server IP address is added to /etc/hosts file on the instances. The YUM package manager is used to install Nginx, and then Port 80 is allowed through the firewall. We then start the Nginx Web server and wait for the server to listen on port 80. The Ansible playbook to install and set up Nginx is as follows:
--- - name: Setup Nginx web server hosts: web become: yes become_method: sudo gather_facts: yes tags: [web] tasks: - name: Install EPEL Release package: name: epel-release state: present - name: Add HAProxy server to /etc/hosts lineinfile: path: /etc/hosts line: “{{ hostvars[‘haproxy’].ansible_host }} haproxy” state: present - name: Install Nginx package: name: nginx state: present - name: Allow port 80 shell: iptables -I INPUT -p tcp --dport 80 -m state --state NEW,ESTABLISHED -j ACCEPT - name: Start Nginx server systemd: name: nginx enabled: yes state: started - name: Wait for server to start wait_for: port: 80
The above playbook can be invoked using the following command:
$ ansible-playbook -i inventory/kvm/inventory playbooks/configuration/frontend.yml --tags web -vv -K
The -vv represents the verbosity in the Ansible output. You can use up to four ‘v’ s for a more detailed output. The -K option prompts for the sudo password for the guest CentOS user account.
You can now open the Nginx Web server URLs (http://192.168.122.248 and http://192.168.122.147) in a browser to see the default Nginx home page as shown in Figure 1.
HAProxy
The YUM package repository needs to be updated before proceeding to install HAProxy. The Nginx server IP addresses and hostnames are added to the /etc/hosts file on the instances. The default /etc/haproxy/haproxy.cfg directory is backed up and a new haproxy.cfg file is created, whose file contents are shown below:
global log 127.0.0.1 local2 chroot /var/lib/haproxy pidfile /var/run/haproxy.pid maxconn 4000 user haproxy group haproxy daemon stats socket /var/lib/haproxy/stats 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 listen haproxy-monitoring *:8080 mode http option forwardfor option httpclose stats enable stats show-legends stats refresh 5s stats uri /stats stats realm Haproxy\ Statistics stats auth admin:password stats admin if TRUE default_backend app-main frontend main bind *:80 option http-server-close option forwardfor default_backend app-main backend app-main balance roundrobin option httpchk HEAD / HTTP/1.1\r\nHost:\ localhost {% for item in groups[‘web’] %} server {{ hostvars[item][‘inventory_hostname’] }} {{ hostvars[item][‘ansible_default_ipv4’][‘address’] }}:80 check {% endfor %}
The rsyslog software will be used to collect the HAProxy logs. The /etc/rsyslog.conf configuration is updated to load the UDP Syslog Input Module (imudp) and to run a UDP server on Port 514. A /etc/rsyslog.d/haproxy.conf configuration file is created, the contents of which are as follows:
local2.=info /var/log/haproxy-access.log local2.notice /var/log/haproxy-info.log
The firewall is updated to allow Port 8080, and the rsyslog server is restarted. The final step is to start the HAProxy server and wait for it to listen on Port 8080. The complete Ansible playbook to install and configure HAProxy is given below:
- name: Setup HAProxy hosts: front become: yes become_method: sudo gather_facts: yes tags: [haproxy] tasks: - name: Yum update yum: name=* update_cache=yes state=present - name: Install HAProxy package: name: haproxy state: present - name: Add Nginx servers to /etc/hosts lineinfile: path: /etc/hosts line: “{{ hostvars[item][‘ansible_default_ipv4’][‘address’] }} {{ hostvars[item][‘inventory_hostname’] }}” state: present with_items: “{{ groups[‘web’] }}” - name: Backup default haproxy.cfg command: mv haproxy.cfg haproxy.cfg.orig args: chdir: /etc/haproxy - name: Create new haproxy.cfg template: src: ../../files/haproxy.cfg.j2 dest: /etc/haproxy/haproxy.cfg mode: 0644 - name: Update /etc/rsyslog.conf lineinfile: path: /etc/rsyslog.conf regexp: “{{ item.regexp }}” line: “{{ item.line }}” with_items: - { regexp: ‘#\$ModLoad imudp’, line: ‘$ModLoad imudp’ } - { regexp: ‘#\$UDPServerRun 514’, line: ‘$UDPServerRun 514’ } - name: Create /etc/rsyslog.d/haproxy.conf copy: src: ../../files/haproxy.conf dest: /etc/rsyslog.d/haproxy.conf mode: 0644 - name: Allow port 8080 shell: iptables -I INPUT -p tcp --dport 8080 -m state --state NEW,ESTABLISHED -j ACCEPT - name: Restart rsyslog systemd: name: rsyslog state: restarted - name: Start HAProxy server systemd: name: haproxy enabled: yes state: started - name: Wait for server to start wait_for: port: 8080
A sample execution of the above playbook is as follows:
$ ansible-playbook -i inventory/kvm/inventory playbooks/configuration/frontend.yml --tags haproxy -K SUDO password: PLAY [Setup Nginx web server] ******************************* TASK [Gathering Facts] ************************************** ok: [nginx1] ok: [nginx2] PLAY [Setup HAProxy] **************************************** TASK [Gathering Facts] ************************************** ok: [haproxy] TASK [Yum update] ******************************************* ok: [haproxy] TASK [Install HAProxy] ************************************** changed: [haproxy] TASK [Add Nginx servers to /etc/hosts] ********************** changed: [haproxy] => (item=nginx2) changed: [haproxy] => (item=nginx1) TASK [Backup default haproxy.cfg] *************************** changed: [haproxy] TASK [Create new haproxy.cfg] ******************************* changed: [haproxy] TASK [Update /etc/rsyslog.conf] ***************************** changed: [haproxy] => (item={‘regexp’: ‘#\\$ModLoad imudp’, ‘line’: ‘$ModLoad imudp’}) changed: [haproxy] => (item={‘regexp’: ‘#\\$UDPServerRun 514’, ‘line’: ‘$UDPServerRun 514’}) TASK [Create /etc/rsyslog.d/haproxy.conf] ******************* changed: [haproxy] TASK [Allow port 8080] ************************************** changed: [haproxy] TASK [Restart rsyslog] ************************************** changed: [haproxy] TASK [Start HAProxy server] ********************************* changed: [haproxy] TASK [Wait for server to start] ***************************** ok: [haproxy] PLAY RECAP ************************************************** haproxy : ok=12 changed=9 unreachable=0 failed=0 nginx1 : ok=1 changed=0 unreachable=0 failed=0 nginx2 : ok=1 changed=0 unreachable=0 failed=0
Testing
You can open the HAProxy Web page using http://192.168.122.113:8080/stats and you will be prompted to log in as shown in Figure 2.
You can use the credentials (admin:password) as specified in /etc/haproxy/haproxy.cfg to log in, and you will see the HAProxy stats page as shown in Figure 3.
You can make multiple requests to the HAProxy front-end server in the browser or using curl http://192.168.122.113:8080 from the command line. You will observe that the requests are being sent to both the Nginx Web servers in a round-robin fashion, which can be seen in the app-main section of the HAProxy stats page.