Ansible
Ansible is a configuration management solution implemented primarily in the Python programming language that you can use to manage your HAProxy Enterprise deployment. Unlike other configuration management software, Ansible offers an ad-hoc mode in which tasks can be run manually. In many ways, this is similar to running commands via shell scripts or manually via SSH.
This guide shows how to run ad-hoc Ansible commands to control HAProxy Enterprise, as well as how to organize more complex tasks into Ansible playbooks, which are YAML-formatted task definitions executed with the ansible-playbook
command.
Install Ansible
Install the latest version of Ansible onto your workstation. This will be the control node from which you manage HAProxy Enterprise nodes. Note that Ansible is not supported on Windows.
Install Ansible Lint onto your workstation, which helps to identify syntax and spacing errors and contains style recommendations and deprecation warnings.
-
Install
socat
on your load balancers so Ansible can invoke Runtime API commands:$ # On Debian/Ubuntu $ sudo apt-get install socat
$ # On CentOS/RedHat/Oracle $ sudo yum install socat
$ # On SUSE $ sudo zypper install socat
$ # On FreeBSD $ sudo pkg install socat
-
Ansible uses SSH for communication with the remote Linux servers. Therefore, it expects that you have examined and accepted the remote server's SSH host key. Connect via SSH to the machines where HAProxy Enterprise is installed and accept the host key:
$ ssh lb1.example.com The authenticity of host 'lb1.example.com (100.200.1.6)' can't be established. ECDSA key fingerprint is SHA256:dzUE7CyUTeE98A5WKUT8DyNwvNqFO3CcJtRQFvsa4xk. Are you sure you want to continue connecting (yes/no)? yes
-
Update your Ansible inventory file, /etc/ansible/hosts, by adding your HAProxy Enterprise servers:
[loadbalancers] lb1.example.com lb2.example.com
Install Python and
pip
, the Python package manager, onto the HAProxy Enterprise servers. Ansible requires this on all nodes that it manages.-
After Python is installed, run the following Ansible ping command to verify that everything is working:
$ ansible loadbalancers -u root -m ping loadbalancers | SUCCESS => { "changed": false, "ping": "pong" }
The
-u
flag defines the remote user for the SSH connection and-m
tells Ansible which module should be used. Ping is one of the pre-installed modules. Many of the Ansible commands you'll use require the Runtime API. To enable it, see this guide.
Ansible ad-hoc command usage
Unlike most configuration management engines, you can use Ansible to issue on-the-fly commands to reconfigure the state on multiple machines. It shows you the state of the infrastructure and allows you to perform runtime changes to multiple machines easily.
The most basic command, which we introduced in the last section, uses the ping
module to tell you whether the machine is alive:
$ ansible loadbalancers -u root -m ping
You can run shell commands on the remote machine by specifying the -a
argument. Below, we invoke the netstat
command on the remote load balancer servers:
$ ansible loadbalancers -u root -a "netstat -tlpn"
To use shell features like pipes and output redirects, you can use the shell
module:
$ ansible loadbalancers -u root -m shell -a "netstat -tlpn | grep :80"
One thing to note is that if you choose to run as a non-root user, some commands will require sudo privileges to work properly. In that case it is necessary to use the --become
flag (short form: -b
) before executing commands. Below, we specify --become
to ensure that the socat package is installed on the system:
$ ansible loadbalancers --become --ask-become-pass \
-m apt -a "name=socat state=present update_cache=yes"
The --ask-become-pass
argument prompts you to enter your sudo password. Please note that for --ask-become-pass
to work correctly, all machines must have the same password for the sudo user.
Rather than prompting for the sudo password, you can enable passwordless sudo. To enable passwordless sudo, add the NOPASSWD:
directive to your user or group using the visudo
command.
$ sudo visudo
Then edit the file as shown below and save it:
# Allow members of group sudo to execute any command
%sudo ALL=(ALL:ALL) NOPASSWD: ALL
Then you can use --become
without --ask-become-pass
.
Next, you can check if the HAProxy Enterprise service is running on a node. The --check
argument can be used with most modules to see what changes would have been made without actually doing them. In this case, we only want to see if HAProxy Enterprise is active, but not start it otherwise.
$ ansible loadbalancers -u root -m service -a "name=hapee-2.8-lb state=started" --check
haproxy-ams | SUCCESS => {
"changed": false,
"name": "hapee-2.8-lb",
"state": "started",
...
To perform a hitless reload, use state=reloaded
and omit the --check
parameter:
$ ansible loadbalancers -u root -m service -a "name=hapee-2.8-lb state=reloaded"
We can combine the above commands with the copy
module to sync an arbitrary configuration to multiple hosts and then reload HAProxy Enterprise to apply the changes:
$ ansible loadbalancers -u root -m copy -a "src=/home/user/hapee-lb.cfg dest=/etc/hapee-2.8/hapee-lb.cfg"
$ ansible loadbalancers -u root -m service -a "name=hapee-2.8-lb state=reloaded"
Doing this for more than a few commands is quite tedious, which is why you might define an Ansible playbook instead. However, in a pinch, the ad-hoc commands are quite useful.
Ad-hoc commands can be used to interact directly with the HAProxy Runtime API.
For example, to disable a specific server from a specific backend:
$ ansible loadbalancers -u root -m shell -a "echo 'disable server bk_www/www-01-server' | socat stdio unix-connect:/var/run/hapee-2.8/hapee-lb.sock"
Or, to show different debugging statistics:
$ ansible loadbalancers -u root -m shell -a "echo 'show stat' | socat stdio unix-connect:/var/run/hapee-2.8/hapee-lb.sock"
$ ansible loadbalancers -u root -m shell -a "echo 'show info' | socat stdio unix-connect:/var/run/hapee-2.8/hapee-lb.sock"
$ ansible loadbalancers -u root -m shell -a "echo 'show fd' | socat stdio unix-connect:/var/run/hapee-2.8/hapee-lb.sock"
$ ansible loadbalancers -u root -m shell -a "echo 'show activity' | socat stdio unix-connect:/var/run/hapee-2.8/hapee-lb.sock"
$ ansible loadbalancers -u root -m shell -a "echo 'show pools' | socat stdio unix-connect:/var/run/hapee-2.8/hapee-lb.sock"
See the Runtime API documentation for more examples.
The ad-hoc commands are also useful for prototyping the commands which are going to become part of a larger playbook, so it's useful to be comfortable with running ad-hoc commands before beginning to write complex playbooks.
Write your first Ansible Playbook
In the last section we described a way to transfer an HAProxy Enterprise configuration to multiple hosts by using the ansible
command; The commands used were the following:
$ ansible loadbalancers -u root -m copy -a "src=/home/user/hapee-lb.cfg dest=/etc/hapee-2.8/hapee-lb.cfg"
$ ansible loadbalancers -u root -m service -a "name=hapee-2.8-lb state=reloaded"
In this section, we will show how to adapt these ad-hoc commands into a playbook that can achieve the same result. The equivalent playbook would be the following:
- hosts: loadbalancers
remote_user: root
tasks:
- name: Copy HAProxy Enterprise configuration
copy:
src: "/home/user/hapee-lb.cfg"
dest: "/etc/hapee-2.8/hapee-lb.cfg"
owner: root
group: hapee
mode: 0644
- name: Check if there are no errors in configuration file
command: "/opt/hapee-2.8/sbin/hapee-lb -c -f /etc/hapee-2.8/hapee-lb.cfg"
register: hapee_check
- name: Reload HAProxy Enterprise if the check passed
service:
name: hapee-2.8-lb
state: reloaded
when: hapee_check is success and not ansible_check_mode
You might notice a few things in the above YAML snippet when moving from ad-hoc commands to a playbook:
Under tasks, each separate command needs to be named; This name is displayed during playbook execution.
-
One addition not present in the ad-hoc commands is the
register
keyword, which is used to store the success/fail result of the remote command:- name: Check if there are no errors in configuration file command:
"/opt/hapee-2.8/sbin/hapee-lb-c-f/etc/hapee-2.8/hapee-lb.cfg"register: hapee_checkThen, the
when
line in the next block verifies that the configuration syntax check executed correctly. The block executes only upon success.- name: Reload HAProxy Enterprise if the check passed service: name: hapee-2.8-lb state: reloaded when: hapee_check is success and not ansible_check_mode
If the check did not pass, we skip reloading HAProxy Enterprise. It also checks that Ansible is not running in dry-run mode by verifying ansible_check_mode.
Ansible Lint
Save the above file as first-haproxy-deploy.yml and check its syntax with ansible-lint
:
$ ansible-lint first-haproxy-deploy.yml
[301] Commands should not change things if nothing needs doing
first-playbook.yml:14
Task/Handler: Check if there are no errors in configuration file
You can ignore the [ 301 ]
warning in this case; We are only interested in making sure there are no obvious errors, the most common being missing spaces in the YAML file.
The linter is useful, however it generally only catches Ansible syntax errors. Therefore, it is recommended to run playbooks with the --check
flag to catch some Ansible runtime errors. Run the ansible-playbook
command with the --check
flag:
$ ansible-playbook first-haproxy-deploy.yml --check
This step only validates that there are no obvious errors in the playbook itself, and not the HAProxy Enterprise configuration file.
Finally, to run the playbook execute:
$ ansible-playbook first-haproxy-deploy.yml
Jinja templates
You can utilize Jinja templates in both the playbook YAML file itself and in configuration files it manages.
To use Jinja templates to manage an external file you can use the template
module. In the snippet below, the tasks block now includes a template block:
- hosts: loadbalancers
remote_user: root
tasks:
- name: Sync main HAPEE configuration
template:
src: ../templates/hapee-lb.cfg.j2
dest: /etc/hapee-2.8/hapee-lb.cfg
owner: root
group: hapee
mode: 0664
- name: Check if there are no errors in configuration file
command: "/opt/hapee-2.8/sbin/hapee-lb -c -f /etc/hapee-2.8/hapee-lb.cfg"
register: hapee_check
- name: Reload HAPEE if the check passed
service:
name: hapee-2.8-lb
state: reloaded
when: hapee_check is success and not ansible_check_mode
To start using Jinja templates, it is enough to rename a file to have a .j2 extension. Then you can optionally introduce Jinja templating patterns into the file.
To use Jinja templates inside your playbooks directly, you can look at the following example playbook:
---
# updates ACLs on remote HAProxy Enterprise nodes via the Runtime API
- hosts: loadbalancers
remote_user: ubuntu
become: true
become_user: root
become_method: sudo
tasks:
- name: update ACL file
shell: "echo '{{ acl_action }} acl {{ acl_path }} {{ acl_ip_address }}' | socat stdio unix-connect:/var/run/hapee-2.8/hapee-lb.sock"
- name: check ACL file for presence/absence of the IP address
shell: "echo 'show acl {{ acl_path }}' | socat stdio unix-connect:/var/run/hapee-2.8/hapee-lb.sock | grep {{ acl_ip_address }}"
register: socket_show
failed_when: "socket_show.rc != 0 and socket_show.rc != 1"
- debug: var=socket_show.stdout_lines
Jinja patterns are enclosed in quotes and curly brackets {{ }}; The variables inside of the brackets will be expanded and used as strings. Variables are set when you run the ansible-playbook
command, as shown below:
$ ansible-playbook update-acl-playbook.yml \
-e "acl_action=add acl_path=/etc/hapee-2.8/ip-whitelist.acl `acl_ip_address=10.10.10.10"`
External variables are defined via the -e
flag. However, Jinja templates can be used with various variables like host variables, group variables, Ansible facts or custom variables set via the register
keyword. See more examples in the Ansible Templating (Jinja2) guide.
Ansible inventory file configuration
In our previous inventory file, we defined two load balancer nodes that Ansible manages:
[loadbalancers]
lb1.example.com
lb2.example.com
You can create hierarchies of load balancer groups, such as to update all load balancers, only load balancers in Europe, or only load balancers in the Americas. One node can appear in multiple groups. The group names can contain any valid DNS record or IP addresses; As a suffix, the port on which SSH is listening can also be specified. A more complex inventory file could look similar to this:
[loadbalancers:children]
loadbalancers-europe
loadbalancer-americas
[loadbalancers-europe]
lb01.eu.example.com
lb02.eu.example.com
[loadbalancers-americas]
lb01.us.example.com
lb02.ca.example.com
lb02.mx.example.com
[loadbalancers-staging]
# Using IP address with SSH ports
10.10.12.10:2222
10.10.12.11:2223
The :children
keyword creates a new group that inherits the nodes of two other groups. Set the group when calling an ad-hoc command. Below, we check whether the European load balancers are up:
$ ansible loadbalancers-europe -u root -m ping
Next up
High Availability