birmingham.io

Using Ansible to build a Raspberry Pi

I recently mentioned on the board that I’ve been using Ansible to build my Raspberry Pi with the Raspbian GNU/Linux operating system. I’m by no means an expert, but I’ve been through some of the learning curve with Ansible, and found it pretty easy to get started with. Should anyone need a tool for server rebuild, I can recommend it with the same enthusiasm with which it was originally recommended to me!

I thought I’d pop down a simple HOWTO for people to try here - so if you have half an hour spare, give this a whirl. Bear in mind I am handling server secrets wrongly, but I don’t presently have high-security requirements, and in any case I will fix this in due course. If you don’t have a Pi to hand, just install Vagrant instead and set up the default Ubuntu VM via VirtualBox.

Here’s some quick terminology:

  • Playbook - a folder containing a set of Ansible configuration files for a specific purpose. The whole thing is usually stored in a version control system so it is easy to push around and share.
  • Tasks - server configuration that is run if the box needs it. Note that installing Apache in Ansible on a box that already has Apache installed will do nothing, by design - it works out what needs to happen and only does it if necessary.
  • Modules - pieces of Ansible functionality to update a box in a particular way e.g. a Git update. There is a Core set of module maintained by the Ansible team, but there are many more, either in their Extras set, or maintained by third parties.
  • Roles - a set of tasks for a particular purpose e.g. a web server.
  • Handlers - bits of code that are run if a task needed running. For example, you want to bounce Apache if a vhost definition changed, but not otherwise. This takes care of that.
  • Variables - bits of information gathered in one place. Useful for storing server addresses, environment variable values, whatever you like.
  • Templates - a templating language to render files on the server, uses the Jinja2 format.
  • Vault - a place to encrypt and store server secrets, so they cannot be read from the Ansible repo at rest. I am not using this (in order to get started quickly) but this seems like a good approach. That said, I am considering BlackBox instead, from Stack Exchange - they use a public key system, which makes more sense for shared Ansible playbooks.

I’ve found installing Ansible from source is easy, and on Ubuntu the current release doesn’t give me certain modules that I need. So, on Ubuntu, I do this:

git clone git://github.com/ansible/ansible.git --recursive
source ./hacking/env-setup

You could probably checkout a known release, but I didn’t have much luck with that, and master was fine - so I have left it like that.

Then I add paths for commands in my ~/.profile (adjust paths to suit, of course):

# Configuration for Ansible
export PATH=/home/jon/Vagrant/ansible/bin:$PATH
export PYTHONPATH=/home/jon/Vagrant/ansible/lib:
export MANPATH=/home/jon/Vagrant/ansible/docs/man:

Finally make sure you have Python 2 installed, this should be already there if you run Ubuntu:

~$ python --version
Python 2.7.6

OK, so here is a nice easy example of how to install a few things for a LAMP server:

---

- name: Install http and PHP etc
  apt: name={{ item }} state=present
  with_items:
   - apache2
   - libapache2-mod-php5
   - php5-cli
   - php5-mongo

This is a YAML file format, so is very human-readable. Indentation matters, but a good IDE will help you get it right (e.g. NetBeans). The module in use here is apt which, as you would expect, calls the Debian apt-get system. The with_items simply does a loop around the four items to get Apache, mod_php, PHP console, and mod_mongo installed.

Another example:

---

- name: Write the WLAN network configuration into place
  template: >
    src=roles/web/templates/wpa_supplicant.conf.j2
    dest=/etc/wpa_supplicant/wpa_supplicant.conf
    owner=root
    group=root
    mode=0600
  notify: restart networking

# @todo Swap the mode below to 600 if possible?
- name: Write the network selection preferences
  template: >
    src=roles/web/templates/network_interfaces.j2
    dest=/etc/network/interfaces
    owner=root
    group=root
    mode=0640
  notify: restart networking

Here we can see the template module in action - it takes a template file in Jinja2 format and renders it on the remote. If this produced a byte change then the “restart networking” handler is called.

Ah, you ask, what does a handler look like? It’s easy, here’s the two I have, in one file:

---
# Restarts the web server as required
- name: restart apache
  service: name=apache2 state=restarted

# Restarts the network as required
- name: restart networking
  service: name=networking state=restarted

Here’s a more complex task, that does a few things to install a dynamic DNS client:

---

- name: Pull the dynamic IP script public repo into position
  git: >
    repo=https://github.com/halfer/mythic-beasts-dynamic-dns.git
    dest=/root/.mythic-beasts-dynamic-dns

- name: Symlink the dynamic script into position
  file: >
    src=/root/.mythic-beasts-dynamic-dns/dynamic-dns.sh
    dest=/root/bin/dynamic-dns.sh
    state=link

# @todo The domain/password credentials should be moved
# to encrypted storage

- name: Write the domain credential into place
  template: >
    src=roles/web/templates/dyndns_domain.j2
    dest=/root/bin/.dyndns_domain
    owner=root
    group=root
    mode=0600

- name: Write the password into place
  template: >
    src=roles/web/templates/dyndns_password.j2
    dest=/root/bin/.dyndns_password
    owner=root
    group=root
    mode=0600

- name: Create a log folder for the dynamic DNS script
  file: >
    path=/var/log/dynamic-dns
    state=directory

- name: Copy a cron into place to run the DNS script
  template: >
    src=roles/web/templates/dyndns_cron.j2
    dest=/etc/cron.d/dynamic-dns

As you can see, I’ve used the git module here, which is very handy.

Finally, how do you tie it all together? Well, you can declare what is a role, task, handler, template etc by virtue of what folder you store things in. I use this format:

ansible/main.yml
ansible/inventory_local
ansible/roles/handlers/main.yml
ansible/roles/tasks/main.yml
ansible/roles/tasks/various...
ansible/roles/templates/various...

An inventory file looks like this. Use a domain name or IP address according to how you would normally SSH into your server:

[webservers]

pi-local.jondh.me.uk

To get ready to run, you need to inform the SSH agent of your private key:

ssh-agent bash
ssh-add ~/.ssh/id_rsa_pi

Now to run the playbook, simply do this at the root of the project:

ansible-playbook ansible/main.yml

You’ll need to put in the IP address of your target server in the inventory file.

Final tips:

  • Put everything into Git or whatever VCS you use immediately. As you get something working, commit it - it is a great iterative way to work.
  • If you make use of my playbook, comment things out and get them working one-by-one.
  • Use the module index liberally - the Ansible docs really are excellent.

I have made a zip file of this build available. Rather that using it directly, it is probably better for you to copy the general structure and then copy in the bits that will be useful to you, testing them piecemeal as you go. It is also good practice to help you understand exactly what is going on.

If you have any questions, add them below!

1 Like

In addition, a resource I often recommend is the four tutorials starting with https://sysadmincasts.com/episodes/43-19-minutes-with-ansible-part-1-4

One technique used in those is the creation of a management node (VM) for ansible. If you work on multiple projects, don’t want to install and configure ansible locally, or simple want to keep a hard separation between various projects and/or components, it’s an approach worth considering.

1 Like

This is really cool.

Did you have any problems which you think were caused specifically by the Pi?

Very little with Raspbian, to be honest. I used the cut-down version, but this is still tailor-made for the device, so everything pretty much works out of the box. One of the advantages of Ansible is that you don’t need to install an agent on the remote box (though to be honest I think it would have Just Worked anyway).

The few minor considerations I had were:

  • Burning the .img file onto the SIM card required a SIM port I don’t have, so I got a colleague to do it. We initially used Ubuntu’s Startup Disk Creator, but it seemed to want an ISO instead. We then swapped to Windows and used a popular image-writing utility. The first couple of tries produced non-bootable cards, but we got there in the end - if it doesn’t work just reformat and try again.
  • Getting roaming wifi working was actually fairly straightforward with wpa_supplicant. This allows the Pi to auto-connect to one of several access points, in a specified priority order. Just have the monitor and keyboard to hand, in case you make the box unreachable via the network!
  • It’s worth thinking about how to work headless - I needed to use an HDMI monitor and keyboard to get going. I now use a dynamic DNS API to reset a subdomain’s A record every five minutes (using a simple shell script). The subdomain has a 30 sec TTL so will update fairly quickly. This allows me to know what IP to use for SSH/Ansible, since on DHCP networks it will sometimes change.
  • Sensitive data or commercial intellectual property needs to be protected, hence I’ve added a simple encryption system.

Other than that, I had one or two minor hiccups with Ansible, but as a beginner I’d expect that when writing any playbook, and the issues are usually trivially solved with a quick web search.

1 Like

Proudly sponsored by Bytemark