Leon Steenkamp

Building small satellites on the tip of Africa. My other ride is a CubeSat.


A short intro to Ansible

I started looking at Ansible (https://www.ansible.com) a few days ago, this is a short post on what I have learnt so far.

Introduction

You can find descriptions (https://www.ansible.com/overview/how-ansible-works) and videos (https://www.ansible.com/resources/videos/quick-start-video) with explanations of what Ansible is and what you can do with it online. Ansible allows you to automate tasks that I would normally have to do by hand (it does this and a whole lot more). The two or three tasks I thought would be useful to automate initially was:

  1. Checking if hosts were up/reachable,
  2. keeping these hosts updated
  3. and having a list or inventory with names and IP addresses of hosts.

If you have two or more Raspberry Pi hosts I think these three tasks on their own are already useful.

These seemed easy enough to tackle while starting out with Ansible, while also being useful. My use case involved installing Ansible on my desktop under WSL and automating/controlling a few Raspberry Pi hosts on the local LAN and one remote (connected using Tailscale).

Install

Installing Ansible is straight forward on Debian based hosts. You can either use apt or install using pip3. I chose to use pip3 as this installed a slightly newer version. You can check what version apt will install using $ sudo apt list ansible and check https://pypi.org/project/ansible/ to see which version pip will install.

$ pip3 install --user ansible

The information I’ve seen online recommends installing with the –user flag. You might have to update your path to include the local install directory. Do this for the current session and add it to your .bashrc file to make it permanent.

$ PATH="$HOME/.local/bin:$PATH"

Only install Ansible on the host you intend to manage everything from. You do not have install it on all your hosts. I did, before reading everything properly. Luckily, I can use Ansible to automate uninstalling Ansible from everywhere I don’t need it.

The project documentation is a good place to find information - https://docs.ansible.com/ansible/latest/index.html

Configure

I decided to start off using the configuration file in the current directory method. On WSL this works fine as long as you stick to the WSL filesystem. Managing permissions of the config file on the mounted Windows filesystem is a bit involved - https://docs.ansible.com/ansible/latest/reference_appendices/config.html#avoiding-security-risks-with-ansible-cfg-in-the-current-directory

My ansible.cfg is basically empty at the moment. An example config file can be seen here - https://github.com/ansible/ansible/blob/devel/examples/ansible.cfg

[defaults]
# Make user we are using local inventory and not the default one
inventory=./inventory

Currently my ansible.cfg, inventory and playbook files all live in the same directory.

Inventory

Next you will need to come up with an inventory file. Inventory files can be in YAML or INI file formats. I chose YAML. Some useful notes are here https://docs.ansible.com/ansible/latest/network/getting_started/first_inventory.html and here https://docs.ansible.com/ansible/latest/user_guide/intro_inventory.html#intro-inventory

---
rpilocal:
  hosts:
    rpi_a:
      ansible_host: 192.168.3.1
    rpi_b:
      ansible_host: 192.168.3.2
    rpi_c:
      ansible_host: 192.168.3.3
  vars:
    ansible_user: pi
    ansible_python_interpreter: /usr/bin/python3

rpiremote:
  hosts:
    rpi_far:
      ansible_host: 100.55.55.55
  vars:
    ansible_user: pi
    ansible_python_interpreter: /usr/bin/python3

all:
  children:
    rpilocal:
    rpiremote:

testlocal:
  hosts:
    rpi_a:
      ansible_host: 192.168.3.1
  vars:
    ansible_user: pi
    ansible_python_interpreter: /usr/bin/python3

The organisation of your inventory is going to depend on your use case. My first take above was to group the local hosts and create another group for the remote hosts. The remote host’s internet connection is sometimes not that great, so I wanted to exclude that while I was testing and trying things out. Those two groups can then be joined under the all tag. I quickly realised that it would be useful to have a test group, so this was added.

One of the links above mentions grouping by what, where or when.

I chose to specify the Ansible host names and IP addresses here because my hosts do not necessarily have domain names associated with them and I did not want to add entries to the managing host’s hosts file. The inventory file now also serves a list to look up addresses or hosts.

The vars in each group saves you from specifying these for each host. The ansible_python_interpreter option was used to clear up warnings with the default discovered value.

Intermission

At this point there are three things that we can test. The first is if Ansible is using our config file. Then if it understands the inventory file and lastly, we can try and ping some hosts.

$ ansible --version

The version argument will give us output confirming which config file Ansible is using:

config file = /home/leon/git/ansible/ansible.cfg

The plan, at the moment, is to keep all my Ansible files in one directory under version control in git. At some point I’ll look at roles and keep these under version control too.

$ ansible all --list-hosts

The list-hosts argument should print a list of hosts in the all group with no errors or warnings. This shows that Ansible is understanding what we did in the inventory file.

hosts (6):
    rpi_a
    rpi_b
    rpi_c
    rpi_far

At his point it is probably important to mention that you should be able to log into all your hosts from the managing host using ssh and an ssh key. I’ll add a post with some notes on setting this up later, but for now I am assuming that you can log into all your hosts using ssh and an ssh key without using a password.

Next, we can try and ping our hosts in the inventory file:

$ ansible all -m ping

The output from this should list something like this for each inventory host:

rpi_a | SUCCESS => {
    "changed": false,
    "ping": "pong"
}

It is possible to run ad-hoc commands on each host - https://docs.ansible.com/ansible/latest/user_guide/intro_adhoc.html. This allows you to, for example, easily check the date and time of each host.

$ ansible rpilocal -a /bin/date

You could get a fair bit done just with executing ad-hoc commands, but a much better way to automate (and document) repetitive tasks is to use playbooks.

Playbooks

Playbooks in Ansible is like a recipe of tasks that Ansible can perform on hosts in your inventory. The playbook is a repeatable list of tasks that can be executed. Most (but not necessarily all) modules and playbooks can be run once or multiple times and achieve the same end state. Ansible will check if the desired end state is already achieved and exit without performing the task.

The short playbook below (uninstall_pip.yml) will uninstall Ansible from the hosts in the all group and skip over hosts where Ansible is not installed.

---
- name: Uninstall pip package
  hosts: all
  vars:
    packageToRemove: ansible

  tasks:
    - name: Remove pip package "{{ packageToRemove }}"
      pip:
        name: "{{ packageToRemove }}"
        state: absent

You can run the playbook with:

$ ansible-playbook uninstall_pip.yml

And keeping hosts updated can be done with a playbook (update_upgrade.yml) that looks something like this:

---
- name: Update and upgrade OS
  hosts: all

  tasks:
    - name: Update package list
      become: true
      apt:
        update_cache: yes
        upgrade: "yes"
        cache_valid_time: 86400 # One day

You can run the playbook with:

$ ansible-playbook update_upgrade.yml

There is also check to see if hosts need to be rebooted after an upgrade, but I have not tested this.

Closing thoughts

Ansible is a way to document and automate the configuration of a host. Up to now I have been keeping notes of steps I used to configure hosts for projects. Ansible allows this process to be automated and documented at the same time.