Hello There!
After reading this blog you will be able to configure a dynamic HAProxy apache webserver with Ansible.
By dynamic HAProxy, I mean that whenever we add a host to the inventory and run the playbook Ansible should automatically configure this as a webserver and register this host to the HAProxy's Load Balancer.
I assume you are already familiar with Ansible, setting up Apache webserver and HAProxy for the load balancer. The entire code in this blog is tested on RHEL 8. I further assume that you have already configured yum in RHEL 8.
Approach
We can use Jinja templating to dynamically update the configuration file of HAProxy to solve the problem of dynamically adding hosts.
First, we'll write down the steps to configure on webserver nodes:
- Install Apache webserver
- Transfer web pages
- Add a firewall rule for the webserver
- Start Apache webserver
Now on HAProxy load balancer node:
- Install HAProxy
- Transfer the configuration file of HAProxy
- Add a firewall rule for Load Balancer
- Start HAProxy service
- Restart HAProxy service when the configuration file changes
And we can use the following Jinja templating for dynamically updating the IPs which run on port 80 in the configuration file:
{% for IP in groups.webservers %}
server app1 {{ IP }}:80 check
{% endfor %}
Where webservers is the group name for all the webservers
Implementing the approach
On Webserver
- Install Apache webserver
- name: "Install Apache web server"
package:
name: "httpd"
state: "present"
- Transfer web pages
- name: "Copy template webpages"
template:
src: "{{ item }}"
dest: "/var/www/html/{{ item.split('/')[-1].split('.')[:-1] | joi
n('.') }}"
when: item.split('.')[-1] == "j2"
with_fileglob: "{{ path_to_webpages }}/*"
Here path_to_webpages variable, as the name suggests stores the path to webpages. Then we loop each of the files and if the file ends with .j2 (jinja template) then use the template module to copy the files to the MNs. And we can use the below code to copy the webpages with copy module if they aren't jinja templates.
- name: "Copy webpages"
copy:
src: "{{ item }}"
dest: "/var/www/html/"
when: item.split('.')[-1] != "j2"
with_fileglob: "{{ path_to_webpages }}/*"
Now you might be wondering are two steps really necessary? When we could directly use the template module? Well, in my case I have images (.jpg) in that directory too. So template module will raise an error if you try to copy image files. So, yeah in my case it matters to do the above steps.
- Add a firewall rule for the webserver
- name: "Adding a firewall rule for webserver"
firewalld:
port: "80/tcp"
state: "enabled"
permanent: true
immediate: true
- Start Apache webserver
- name: "Start Apache web server"
service:
name: "httpd"
state: "started"
On Load Balancer
- Install HAProxy
- name: "Install HAProxy"
package:
name: "haproxy"
state: "present"
- Transfer the configuration file of HAProxy
- name: "Copy configuration files"
template:
src: "{{ cfg_file }}"
dest: "/etc/haproxy/haproxy.cfg"
notify: "Restart haproxy server"
cfg_file is the path to HAProxy configuration file. This block notifies the Restart haproxy server handler whenever the configuration file is changed.
- Add a firewall rule for Load Balancer
- name: "Adding a firewall rule for HAProxy"
firewalld:
port: "{{ haproxy_port }}/tcp"
state: "enabled"
permanent: true
immediate: true
Where haproxy_port is the port number of HAProxy server
- Start HAProxy service
- name: "Start Haproxy service"
service:
name: "haproxy"
state: "started"
- Handler to restart HAProxy service when the configuration file changes
- name: "Restart haproxy server"
service:
name: "haproxy"
state: "restarted"
So the final playbook looks something like this :
- hosts: webservers
vars:
- path_to_webpages: "./webpages"
tasks:
- name: "Install Apache web server"
package:
name: "httpd"
state: "present"
- name: "Copy template webpages"
template:
src: "{{ item }}"
dest: "/var/www/html/{{ item.split('/')[-1].split('.')[:-1] | join('.') }}"
when: item.split('.')[-1] == "j2"
with_fileglob: "{{ path_to_webpages }}/*"
- name: "Copy webpages"
copy:
src: "{{ item }}"
dest: "/var/www/html/"
when: item.split('.')[-1] != "j2"
with_fileglob: "{{ path_to_webpages }}/*"
- name: "Adding a firewall rule for webserver"
firewalld:
port: "80/tcp"
state: "enabled"
permanent: true
immediate: true
- name: "Start Apache web server"
service:
name: "httpd"
state: "started"
- hosts: load_balancer
vars:
- cfg_file: "./config_files/haproxy_cfg.j2"
- haproxy_port: 5000
tasks:
- name: "Install HAProxy"
package:
name: "haproxy"
state: "present"
- name: "Copy configuration files"
template:
src: "{{ cfg_file }}"
dest: "/etc/haproxy/haproxy.cfg"
notify: "Restart haproxy server"
- name: "Adding a firewall rule for HAProxy"
firewalld:
port: "{{ haproxy_port }}/tcp"
state: "enabled"
permanent: true
immediate: true
- name: "Start Haproxy service"
service:
name: "haproxy"
state: "started"
handlers:
- name: "Restart haproxy server"
service:
name: "haproxy"
state: "restarted"
You can also find the code on my Github repo
You can directly clone the repo and run the playbook. But make sure you have Ansible installed and configured it. Also, name the webservers group as webservers and load balancer as load_balancer in the inventory.
Thank You!