Using Ansible for configuration management

The following is a guest post from Eric Goebelbecker. Eric has worked in the financial markets in New York City for 25 years, developing infrastructure for market data and financial information exchange (FIX) protocol networks. He loves to talk about what makes teams effective (or not so effective!)

Ansible is a powerful configuration management tool for deploying software and administering remote systems that you can integrate into any existing architecture. It relies on industry-standard security mechanisms and takes full advantage of existing operating system utilities.

Ansible uses no agents and works with your existing security infrastructure. It employs a simple language to describe your systems, how they relate, and the jobs you need to manage them. Also, it connects to nodes and pushes Ansible modules to them. These modules contain your configuration management tasks.

Using Ansible for configuration management Photo by Jay Heike on Unsplash

Ansible’s maintainers describe it as a radically simple IT automation engine. They’re not wrong. In this post, we’ll take a look at why that’s an apt definition, and we’ll discuss how you can use it for configuration management.

Ansible overview

Before we get into anything else, let’s take a moment to learn some basics about Ansible

Installing Ansible

Since Ansible uses no agents, most of your preparation is done on the host where you’ll run it to push configuration changes to the rest of your network. This system is called the control node.

Since Ansible is written in Python, you may have two choices for how to install it. If your control node is running Red Hat Enterprise Linux, CentOS, Fedora, Debian, or Ubuntu, you can install the latest release version using the system’s OS package manager.

Let’s take a look at installing Ansible on Ubuntu. First, you would add the Ansible PPA, update apt, and then install the package:

$ sudo apt update
$ sudo apt install software-properties-common
$ sudo apt-add-repository --yes --update ppa:ansible/ansible
$ sudo apt install ansible

Red Hat, Fedora, and CentOS are a little easier since Ansible is already available as a mainstream package.

$ sudo yum install ansible

If you want to use alpha or beta releases, or if you prefer working with Python, you can install Ansible with pip, Python’s package manager. Ansible works with Python 2.7 or 3.x, so it works with the Python version installed on Linux and macOS. But support for Python 2.7 is deprecated, so if you’re setting up Ansible for a production system, it’s a good idea to update to Python 3.

$ sudo pip install virtualenv

With Ansible installed on your control node, you’re ready to add a system to your configuration management inventory.

SSH and keys

Ansible’s default mechanism for distributing modules is SSH. So you can control access to your managed nodes with SSH keys, Kerberos, or any other identity management system that works with SSH. You can even use passwords, but they’re less secure and can be unwieldy.

Keys are the easiest way to add support for a host. By adding the public key for the Ansible user to authorized_keys on the target system, you’re ready to manage it. If you want to read more, Digital Ocean has an excellent tutorial for adding keys.

Ansible inventory and configuration

After you’ve set up a host to allow access from the control node, you can add it to your Ansible inventory. To do that, you need to decide where your Ansible configuration will go.

Ansible stores its configuration in a set of text files. The default location for these files is /etc/ansible. To add configuration files there, you need to either create the file as root or add write permission for an unprivileged user. Another option is that you could override the location with a configuration file and place it in an area that doesn’t require privileged access.

So, let’s imagine you want to store your configuration in the ansible_config directory in ansible_user’s home directory.

First, create a configuration file in your home directory:

$ touch ~/.ansible.cfg

Then, add a line for your inventory:

[defaults]
inventory = /home/ansible_user/ansible_config/hosts

Now you need to create the configuration directory:

$ mkdir ~/ansible_config

Finally, create a hosts file in the new directory to hold your inventory:

$ touch ~/ansible_config/hosts

Now you can add your managed nodes to the file:

127.0.0.1
mail.example.com

[webservers]
foo.example.com
bar.example.com

[dbservers]
one.example.com
two.example.com
three.example.com

This file declares seven managed nodes with their fully-qualified DNS names and localhost with the loopback IP address. You can use IP addresses or partial hostnames (assuming the control node can resolve them) too. The names in square brackets are groups, which you can use to manage sets of systems instead of listing each name. If you need more information, Ansible has detailed documentation on inventory.

Modules

With a configured system, you can execute modules.

Let’s start with a ping:

$ ansible 127.0.0.1 -m ping

127.0.0.1 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": false,
    "ping": "pong"
}

The first argument to ansible is the target host or groups of hosts. In addition to the groups defined in your inventory, ansible also creates the all group. So you could ping all hosts:

$ ansible all -m ping

Or your web servers:

$ ansible webservers -m ping

Next, -m indicates the module to run. And ping verifies that you can connect to a host and execute a remote command. It also gives you some basic information. In this case, localhost has a Python interpreter installed in /usr/bin.

You can find a directory of Ansible modules on their website.

Run arbitrary commands with Ansible

You’re not limited to modules with Ansible. You can also run arbitrary or ad-hoc commands with -a. Here’s a listing of the remote user’s home directory.

$ ansible 127.0.0.1 -a "ls -a"
35.174.111.167 | CHANGED | rc=0 >>
total 16
drwx------. 4 ericg ericg 111 Jul 12 17:51 .
drwxr-xr-x. 4 root root 39 Jul 12 17:35 ..
drwx------. 3 ericg ericg 17 Jul 12 17:51 .ansible
-rw-------. 1 ericg ericg 472 Jul 13 16:21 .bash_history
-rw-r--r--. 1 ericg ericg 18 Jan 14 06:10 .bash_logout
-rw-r--r--. 1 ericg ericg 141 Jan 14 06:10 .bash_profile
-rw-r--r--. 1 ericg ericg 312 Jan 14 06:10 .bashrc
drwx------. 2 ericg ericg 48 Jul 13 16:19 .ssh

Playbooks

By now, you can already see how Ansible is a straightforward but still potent tool. Modules provide you with the ability to perform a wide range of configuration management tasks, and all you need is access to the host via SSH. If you come across something that a module can’t do, you can fall back on arbitrary commands.

But Ansible’s real power comes in playbooks. Playbooks are a configuration management system that you use to declare system configuration and orchestrate deployments.

Here’s an example that installs Apache on a system with the dnf package manager and then verifies that it started:

---
- name: This sets up an httpd webserver
  hosts: webservers
  become: yes
  become_method: sudo
  tasks:
    - name: Install apache packages
    dnf:
      name: httpd
      state: present
    - name: ensure httpd is running
    service:
      name: httpd
      state: started

Playbooks use YAML format. This file contains a single play, which is the term used to describe a set of tasks. The play has two tasks: one to install Apache and another to ensure that it’s running.

After the name, the play specifies the target hosts. Similar to ad-hoc tasks on the command line, you can specify individual hosts or groups. This play targets the webservers group.

Next, the become directive tells Ansible to run this play with the sudo command since installing new packages and changing the systemd configuration requires escalated privileges. Rather than requiring root access to the host, Ansible works with sudo and other privilege-escalation systems like PowerBroker.

This simple file demonstrates Ansible’s ability to deploy new software with an OS package manager and also verify a host’s state with the service module.

Playbooks perform the steps in a play in the order they’re specified. They can also contain more than one play and, again, will perform them in the specified order.

Ansible best practices

Stop me if you heard this one: with great power comes great responsibility. Ansible is the kind of tool that you can teach yourself in a matter of hours. It’s also the kind of tool that lets you make a terrible mess in half that time.

Fortunately, Ansible developers — and the configuration management community — have documented best practices.

Treat your Ansible artifacts like code

Infrastructure-as-code has been long considered a DevOps best practice, and Ansible isn’t an exception to the rule. All of your Ansible artifacts, including configuration like your inventory, belong in version control. Use names in your server inventory

As demonstrated above, the inventory host file supports DNS names and IP addresses. It also supports a simple naming system, like UNIX host files. Regardless of what you decide to use, use sensible names and groups to organize your hosts. This will improve the readability of your playbooks.

There are several useful resources if you’re interested in learning more Ansible best practices. The Ansible team has a list in the official documentation. They also have a set of slides you can check out. And Server Raumgeschichten’s blog post “Ansible Best Practices” is a great starting point too.