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:
- Checking if hosts were up/reachable,
- keeping these hosts updated
- 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.