Managing Multiple Machines Simultaneously With Ansible

If I have to do it more than once, it’s probably going to get scripted. That has been my general attitude towards mundane system administration tasks for many years, and is also shared by many others. How about taking that idea a little further and applying it to multiple machines? Well there’s a tool for that too, and it’s named ansible.

We need ansible installed on the system we will be using as the client/bastion. This machine needs to be able to SSH into all of the remote systems we want to manage without issue. So stop and make sure that works unhindered before continuing. On the remote machine, the requirements are fairly low and typically revolve around python2. In Gentoo python2 is already installed as it is required by several things including emerge itself. On Ubuntu 16.04 LTS, python2 is not installed by default and you will need to install the package ‘python-minimal’ to regain it.

Once we have python installed on the remote machines and ansible installed on the local machine, we can move on to editing the ansible configuration with a list of our hosts. This file is fairly simple and there are lots of examples available, but here is a snippet of my /etc/ansible/hosts file:



Here you can see I have three hosts listed under a group named ubuntu-staging.

Once we have hosts defined we can do a simple command line test:

ansible ubuntu-staging -m command -a “w”

The ‘-m’ tells ansible we wish to use a module named ‘command’ and ‘-a’ indicates that it has arguments that need to be passed which is immediately given as ‘w’. The output from this command should be similar to this:

$ ansible ubuntu-staging -m command -a “w”
ubuntu-staging-www | SUCCESS | rc=0 >>
10:25:57 up 8 days, 12:29, 1 user, load average: 0.22, 0.31, 0.35
canuteth pts/2 10:25 1.00s 0.25s 0.01s w

ubuntu-staging-dev | SUCCESS | rc=0 >>
10:25:59 up 8 days, 12:17, 1 user, load average: 0.16, 0.03, 0.01
canuteth pts/0 10:25 0.00s 0.37s 0.00s w

ubuntu-staging-db | SUCCESS | rc=0 >>
10:26:02 up 8 days, 12:25, 1 user, load average: 0.17, 0.09, 0.09
canuteth pts/0 10:26 0.00s 0.28s 0.00s w

Okay, that shows promise right? Let’s try something a little more complicated:

$ ansible ubuntu-staging -s -K -m command -a “apt-get update”
SUDO password:
[WARNING]: Consider using apt module rather than running apt-get

ubuntu-staging-db | SUCCESS | rc=0 >>
Hit:1 xenial InRelease
Get:2 xenial-updates InRelease [102 kB]
Get:3 xenial-security InRelease [102 kB]
Get:4 xenial-backports InRelease [102 kB]
Fetched 306 kB in 5s (59.3 kB/s)
Reading package lists…

ubuntu-staging-www | SUCCESS | rc=0 >>
Hit:1 xenial InRelease
Get:2 xenial-updates InRelease [102 kB]
Get:3 xenial-security InRelease [102 kB]
Hit:4 ubuntu-xenial InRelease
Get:5 xenial-backports InRelease [102 kB]
Get:6 xenial-updates/main amd64 Packages [544 kB]
Get:7 xenial-updates/main i386 Packages [528 kB]
Get:8 xenial-updates/main Translation-en [220 kB]
Get:9 xenial-updates/universe amd64 Packages [471 kB]
Get:10 xenial-updates/universe i386 Packages [456 kB]
Get:11 xenial-updates/universe Translation-en [185 kB]
Get:12 xenial-security/main amd64 Packages [276 kB]
Get:13 xenial-security/main i386 Packages [263 kB]
Get:14 xenial-security/main Translation-en [118 kB]
Get:15 xenial-security/universe amd64 Packages [124 kB]
Get:16 xenial-security/universe i386 Packages [111 kB]
Get:17 xenial-security/universe Translation-en [64.2 kB]
Fetched 3,666 kB in 6s (598 kB/s)
Reading package lists…

ubuntu-staging-dev | SUCCESS | rc=0 >>
Hit:1 zesty InRelease
Get:2 zesty-updates InRelease [89.2 kB]
Get:3 zesty-security InRelease [89.2 kB]
Get:4 zesty-backports InRelease [89.2 kB]
Get:5 zesty-updates/main i386 Packages [94.4 kB]
Get:6 zesty-updates/main amd64 Packages [96.2 kB]
Get:7 zesty-updates/main Translation-en [43.0 kB]
Get:8 zesty-updates/main amd64 DEP-11 Metadata [41.8 kB]
Get:9 zesty-updates/main DEP-11 64×64 Icons [14.0 kB]
Get:10 zesty-updates/universe i386 Packages [53.4 kB]
Get:11 zesty-updates/universe amd64 Packages [53.5 kB]
Get:12 zesty-updates/universe Translation-en [31.1 kB]
Get:13 zesty-updates/universe amd64 DEP-11 Metadata [54.1 kB]
Get:14 zesty-updates/universe DEP-11 64×64 Icons [43.5 kB]
Get:15 zesty-updates/multiverse amd64 DEP-11 Metadata [2,464 B]
Get:16 zesty-backports/universe amd64 DEP-11 Metadata [3,980 B]
Get:17 zesty-security/main amd64 Packages [67.0 kB]
Get:18 zesty-security/main i386 Packages [65.5 kB]
Get:19 zesty-security/main Translation-en [29.6 kB]
Get:20 zesty-security/main amd64 DEP-11 Metadata [5,812 B]
Get:21 zesty-security/universe amd64 Packages [28.8 kB]
Get:22 zesty-security/universe i386 Packages [28.7 kB]
Get:23 zesty-security/universe Translation-en [19.9 kB]
Get:24 zesty-security/universe amd64 DEP-11 Metadata [5,040 B]
Fetched 1,049 kB in 6s (168 kB/s)
Reading package lists…

This time we passed ansible the paramater ‘-s’ which tells ansible we want to use sudo and we also passed ‘-K’ which tells ansible to prompt us for a password. You’ll also notice that it warns us to use the ‘apt’ module, which is a better choice for interacting with apt-get.

The command module will work with pretty much any command that is non-interactive and doesn’t use pipes or redirection. I often use it for checking things on multiple machines quickly. For example, if I need to install updates and I want to know if anyone is using a particular machine, I can use w, who, users, etc. to see who is logged in before proceeding.

If we needed to interact with one a few hosts and not an entire group, we can name the hosts, separated by a comma, in the same fashion: ‘ansible ubuntu-staging-www,ubuntu-staging-db …’

Now lets look at trying something a bit more complicated.. say we need to copy a configuration file /etc/ssmtp/ssmtp.conf to all of our hosts. For this we will write an ansible playbook that I named ssmtp.yml:

# copy ssmtp.conf to all ubuntu-staging hosts

– hosts: ubuntu-staging
user: canutethegreat
sudo: yes

– copy: src=/home/canutethegreat/staging/conf/etc/ssmtp/ssmtp.conf

We can invoke the command with ‘ansible-playbook ssmtp.yml’ and it will do as directed. The syntax is fairly straightforward and there are quite a number of examples.

There are lots of examples for a wide range of tasks in the Ansible github repo and be sure to take a look at the intro to playbooks page. Just remember that you are doing things to multiple servers at once so if you do something dumb it’ll be carried out on all of the selected servers! Testing things on staging servers and using pretend/simulate are always good ideas anyway.