Configuring HAProxy with Ansible on AWS

Yogesh
5 min readMar 20, 2021

In this blog, I will show you how to configure your own dynamic HAProxy apache webserver with Ansible on top of AWS.

I already wrote an article on how to do it locally. If you haven’t checked it yet do have a look at it first - Configuring HAProxy dynamically with Ansible as most of the playbook and configuration will remain the same.

AWS Dynamic Inventory 🤓💻

First, we’ll be setting up a dynamic inventory for AWS by using the AWS dynamic inventory plugin. I’ll use the most simple configuration of it here but you can always check the documentation for more complex use cases.

✔️ For this, to work you first have to install boto3 and setup AWS CLI or boto3 with credentials. Its pretty simple to do this, so I’m not going to dwell deep into it.

✔️ Create a directory for dynamic inventory, say dynamic_inventory .

mkdir dynamic_inventory

✔️ Create a yml file for dynamic inventory, say aws_ec2.yml and write something like the below code which suits your requirements. This gets all the instances from your AWS cloud from the region us-east-1 and groups the hostnames(IP addresses in this case) according to the key values pairs of tags assigned in AWS. And the group name will be of the format tag_<KEY>_<VALUE> because we have assigned a prefix tag here. For example, if some instances have a tag server: haproxy in us-east-1 region then this plugin retrieves all the IPs of instances with the tag server: haproxy groups them into tag_server_haproxy group.

plugin: aws_ec2
regions:
- us-east-1
keyed_groups:
- key: tags
prefix: tag
hostnames:
- ip-address

✔️ Then edit your configuration file such that the inventory points to this dynamic_inventory directory as follows

[defaults]
inventory = /root/ansible/dynamic_inventory
host_key_checking = False

Note: Even if you have an existing static inventory file it won’t be a problem, just put it in this directory.

✔️ And finally for ansible to authenticate to AWS you must provide private keys according to the tags it has. This is done by creating a group_vars directory in the dynamic inventory directory. Then create a file named on the group name. By going with the above example create a file named tag_server_haproxy , in which write down the username and the path to the key for your instance as follows.

ansible_user: "ec2-user"
ansible_ssh_private_key_file: "/ssh_keys/haproxy.pem"

✏️ Note: Make sure that the keys are read-only, if not use chmod 400 <PATH_TO_SSH_KEYS> .

✔️ Create a similar file for the webserver in the group_vars directory as shown below. But remember to name your file properly (tag_<KEY>_<VALUE>). I have named it tag_server_webserver .

ansible_user: "ec2-user"
ansible_ssh_private_key_file: "/ssh_keys/webserver.pem"

✏️ Note: I used ec2-user as the ansible user because I usedAmazon Linux 2 AMI to launch the instances.

✔️ Now create instances in AWS for haproxy[1] & web server[3] and tag them accordingly(as per the above convention).

️✔️️ ️If you did everything right then you should confirm if your dynamic inventory is working properly or not by using the below commands.

# Adhoc Command to check tag_server_webserver group's connectivity
ansible tag_server_webserver -m ping
# Adhoc Command to check tag_server_haproxy group's connectivity
ansible tag_server_haproxy -m ping

Once you do this dynamic inventory part it is pretty much done! 🙂. The rest is similar to as how we do it on premise.

Configuring HAProxy🧐

Playbook 💻

✔️️ This playbook is pretty much similar to the playbook I have demonstrated previously with minor changes - such as removing the firewall rules as, AWS manages firewall through its own security groups(make sure you have written rules there to access the ports we have used below).

- hosts: tag_server_webserver  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: "Start Apache web server"
service:
name: "httpd"
state: "started"
- hosts: tag_server_haproxy 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: "Start Haproxy service"
service:
name: "haproxy"
state: "started"
handlers: - name: "Restart haproxy server"
service:
name: "haproxy"
state: "restarted"

HAProxy Configuration ⚙️

✔️️ Another change I have made is in the HAProxy configuration file. I have used groups.tag_server_webserver instead of groups.webservers previously.

#---------------------------------------------------------------------
# Example configuration for a possible web application. See the
# full configuration options online.
#
# https://www.haproxy.org/download/1.8/doc/configuration.txt
#
#---------------------------------------------------------------------
#---------------------------------------------------------------------
# Global settings
#---------------------------------------------------------------------
global
# to have these messages end up in /var/log/haproxy.log you will
# need to:
#
# 1) configure syslog to accept network log events. This is done
# by adding the '-r' option to the SYSLOGD_OPTIONS in
# /etc/sysconfig/syslog
#
# 2) configure local2 events to go to the /var/log/haproxy.log
# file. A line like the following can be added to
# /etc/sysconfig/syslog
#
# local2.* /var/log/haproxy.log
#
log 127.0.0.1 local2
chroot /var/lib/haproxy
pidfile /var/run/haproxy.pid
maxconn 4000
user haproxy
group haproxy
daemon
# turn on stats unix socket
stats socket /var/lib/haproxy/stats
# utilize system-wide crypto-policies
ssl-default-bind-ciphers PROFILE=SYSTEM
ssl-default-server-ciphers PROFILE=SYSTEM
#---------------------------------------------------------------------
# common defaults that all the 'listen' and 'backend' sections will
# use if not designated in their block
#---------------------------------------------------------------------
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
#---------------------------------------------------------------------
# main frontend which proxys to the backends
#---------------------------------------------------------------------
frontend main
bind *:{{ haproxy_port }}
acl url_static path_beg -i /static /images /javascript /stylesheets
acl url_static path_end -i .jpg .gif .png .css .js
use_backend static if url_static
default_backend app
#---------------------------------------------------------------------
# static backend for serving up images, stylesheets and such
#---------------------------------------------------------------------
backend static
balance roundrobin
server static {{ groups.tag_server_webserver[0] }}:80 check
#---------------------------------------------------------------------
# round robin balancing between the various backends
#---------------------------------------------------------------------
backend app
balance roundrobin
{% for IP in groups.tag_server_webserver %}
server app1 {{ IP }}:80 check
{% endfor %}

✔️️ Finally, to display the instance’s private IP on the webpage I have used ansible_facts.default_ipv4.address variable in the webpage template index.html.j2 .

✔️️ Now, all we need to do is to run the playbook as a root user for which we use -b option.

ansible-playbook playbook.yaml -b

✏️ You can find the playbook, webpages and configuration file on my GitHub repo.

Thank You for reading! I hope you enjoyed it. 😇🥰

--

--