Ansible

 

Overview

If we have one or two systems we need to administer, using shell scripts is probably sufficient. However as we have more systems we need to take care of, or need to swap out systems quickly, this approach leaves something to be desired. Copying scripts to multiple machines and running them, and making sure all machines are the same becomes more difficult.

An alternative is to use a configuration management system. The idea is that these systems take a file which dictate the state of a system, such as what packages should be installed, what files there should be, what users should exist, and so on. Then the configuration management tool will apply the configuration to a machine or set of machines. Benefits of configuration management include:


 

Configuration Management

There are multiple configuration management systems available, with Ansible, Pupper, Chef, and Salt being the most popular. The important distinctions between them are:

  1. Agent-based vs. agentless. An "agent" is a program which runs on the machines being managed and applies the configuration changes. Ansible has no agent, with changes being applied over SSH. The other three systems all have agents that must be installed on the hosts.
  2. Whether changes are pushed or pulled. Ansible pushes changes from the machine doing the management to the one(s) being managed. Puppet and chef pull changes instead, and Salt supports both models.
  3. Configuration language. Ansible and Salt both use YAML. Chef uses Ruby and Puppet uses its own language.

 

Ansible

Ansible is the easiest to get started with, and is widely used. It is developed by Red Hat and is free software. It does not require an agent to run on the managed nodes, that is, the machines we are configuring. Instead it is installed on the control node, the machine doing the management, and pushes changes over regular SSH connections.

For Ansible to be able to manage a machine, therefore, we must be able to SSH into that machine. In practice this needs to be as a user with sudo access. Otherwise we could not install packages or make other changes needing root permissions. We also ideally would use SSH keys so Ansible does not need to ask our password for every connection.

On Debian, Ansible can be installed on the machine doing the changes with the ansible package.


 

Inventories

With Ansible an inventory is the set of host machines which can be configured. These can be specified in the INI format or in the YAML format. For consistency, the examples here will use YAML. Hosts can be grouped together to allow for different sets of configurations. For example, you could make one group for web servers, and another for database servers. Or one set for test machines and another for production machines.

Below is an example inventory file:


webservers:
  hosts:
    company.net
    mirror1.company.net
    
dbservers:
  hosts:
    10.1.1.23
    10.1.1.24
    10.1.1.25

This inventory file creates two groups called webservers and dbservers. The hosts can be specified with either IP addresses (internal or external ones), or host names. The host is used as the address to SSH in, so it must be reachable from the machine doing the configuring.

We can manage the machine we are currently on with a localhost inventory:


all:
  hosts:
    127.0.0.1:
      ansible_connection: local

The use of a local connection makes it so we don't need to SSH into the machine we are currently on, but rather run commands directly.

We can test the inventory file by pinging the hosts in it with ansible:

$ ansible all -m ping -i inventory.yaml
127.0.0.1 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3.13"
    },
    "changed": false,
    "ping": "pong"
}
cpsc.umw.edu | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3.10"
    },
    "changed": false,
    "ping": "pong"
}

I used two machines for this example, my localhost and the CPSC server.


 

Playbooks

Ansible uses the following terminology to refer to configurations it can perform:

Here is an example playbook file consisting of one play with two tasks:


---
- name: Ensure Apache is running
  hosts: all
  
  tasks:
  - name: Ensure apache is up to date
    ansible.builtin.package:
      name: apache2
      state: latest

  - name: Ensure apache is up and running
    ansible.builtin.service:
      name: apache2
      enabled: yes
      state: started

We can then run this playbook on an inventory of machines with the following:

$ ansible-playbook test1.yaml -i inventory.yaml 

PLAY [Ensure Apache is running] **************************************************************************************************************************

TASK [Gathering Facts] ***********************************************************************************************************************************
ok: [127.0.0.1]
ok: [cpsc.umw.edu]

TASK [Ensure apache is up to date] ***********************************************************************************************************************
changed: [127.0.0.1]
ok: [cpsc.umw.edu]

TASK [Ensure apache is up and running] *******************************************************************************************************************
changed: [cpsc.umw.edu]
ok: [127.0.0.1]

PLAY RECAP ***********************************************************************************************************************************************
127.0.0.1                  : ok=3    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
cpsc.umw.edu               : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

One important aspect of Ansible is idempotence which means that an operation can be applied multiple times without negative effect. This is different from a script which may not be re-runnable without changing anything. Note that the playbook is written in a declarative way. We don't say "install apache", we say "apache should be the latest version". If it already is, no change will be made.

We can see from the output here that no change was made to to cpsc.umw.edu because Apache was already updated and running on that machine.

We can also run a playbook in "check mode" which means that it doesn't actually perform any changes, but tells you if changes would be made using the command:

$ ansible-playbook --check test1.yaml -i inventory.yaml 

 

Modules

Some of the most useful Ansible modules are: