Configuring HAProxy dynamically with Ansible

Configuring HAProxy dynamically with Ansible

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!