Home Linux How to manage a Raspberry Pi Cluster with Ansible

How to manage a Raspberry Pi Cluster with Ansible

by Sean Ziegler

I plan to run a bunch of services on my home network. My plan is to host most of the services on a small Raspberry Pi cluster in a C4 Labs Cloudlet Case. These Pi’s run Raspbian and each one will run a few services that I enjoy having hosted on my home network.

Although 3 RPis isn’t exactly impossible to configure manually; why would I want to do that? Plus, the Cloudlet Case accepts up to 8 RPis which would be a lot to manage manually.

The Plan

For now, I will configure all the following things with Ansible:

  • .bashrc – This enables me to use the same bash aliases across all nodes
  • Changing the default password of every pi
  • Installing Vim
  • .vimrc – Configures Vim settings like the color scheme and line numbers across all nodes
  • MOTD – Just for fun, I’m going to install the fortune | cowsay packages and broadcast funny cow sayings on log in
  • Prometheus Exporters – Eventually we will deploy some Prometheus exporters to remotely view each node’s status
  • Mounting a network share on boot

I’ll also be deploying some Docker containers via Ansible, but first we need to configure Ansible itself.

Installing Ansible on the Master Node

Since Raspbian is a Debian derivative, we will need to add the Ansible PPA to the /etc/apt/sources file.

cd /etc/apt
sudo vim sources

The Ubuntu PPA works fine for Raspbian, at the top of the file add:

deb http://ppa.launchpad.net/ansible/ansible/ubuntu trusty main

Update apt and then install with:

sudo apt update 
sudo apt install ansible

All right, let’s define the hosts on the network. Remember, there are three Raspberry Pis. One will function as a master node that runs Ansible and the others will be slaves that Ansible controls via SSH.

[master]

pi@192.168.0.101

[slave]

pi@192.168.0.102
pi@192.168.0.103

I could have used Ansible’s built-in expansion syntax and written the slaves as 192.168.0.[102:103] but I like the readability of listing them explicitly.

You need SSH keys configured for Ansible to work without password prompts. I already have SSH setup on all these nodes, but I’ll be writing a post on setting up headless SSH on an RPi in the future.

Let’s check if Ansible can talk to our nodes:

ansible all -m ping

192.168.0.101 | SUCCESS => {
    "changed": false,
    "ping": "pong"
}
192.168.0.103 | SUCCESS => {
    "changed": false,
    "ping": "pong"
}
192.168.0.102 | SUCCESS => {
    "changed": false,
    "ping": "pong"
}

Great! We can talk to everything. Now let’s talk about playbooks.

Writing a playbook

Okay, first let’s add a very basic Ansible playbook to install Vim to all three nodes. First, move to the Ansible directory and create a folder for playbooks and the playbook itself:

cd /etc/ansible
mkdir playbooks
touch install_vim.yml

Inside the playbook, define a task that installs the latest version of Vim on all the hosts. I’ll also add configuration to use sudo to install Vim.

- hosts: all
    remote_user: pi
    tasks:
      - name: Install Vim
        become: true
        become_method: sudo
        apt:
          name: vim
          state: latest
          install_recommends: true

Save the file and run the following command:

ansible-playbook install_vim.yml

Your output should be all ok.

PLAY [all] **************************************************************************

TASK [Gathering Facts] **************************************************************
ok: [pi@192.168.0.101]
ok: [pi@192.168.0.103]
ok: [pi@192.168.0.102]

TASK [Install Vim] ******************************************************************
ok: [pi@192.168.0.101]
ok: [pi@192.168.0.103]
ok: [pi@192.168.0.102]

PLAY RECAP **************************************************************************
pi@192.168.0.101           : ok=2    changed=0    unreachable=0    failed=0
pi@192.168.0.102           : ok=2    changed=0    unreachable=0    failed=0
pi@192.168.0.103           : ok=2    changed=0    unreachable=0    failed=0

Great, now that we have installed Vim let’s advertise some Vim settings (I like the desert color scheme and line numbers) to all 3 machines.

Let’s make a new folder to hold files we want to advertise to other nodes and then create the .vimrc file:

cd ..
mkdir repo
cd repo
touch .vimrc

Inside the .vimrc file add the following lines to enable a desert color scheme and line numbers:

color desert
set number

Now let’s define a new task in the Ansible recipe:

- name: Advertise .vimrc
   become: true
   become_method: sudo
   copy:
    src: ../repo/.vimrc
    dest: /etc/vim/vimrc.local

All right, re-run the ansible-playbook install_vim.yml command and see all the same output plus a few extra lines:

TASK [Advertise .vimrc] *************************************************************
changed: [pi@192.168.0.101]
changed: [pi@192.168.0.103]
changed: [pi@192.168.0.102]

PLAY RECAP **************************************************************************
pi@192.168.0.101           : ok=3    changed=1    unreachable=0    failed=0
pi@192.168.0.102           : ok=3    changed=1    unreachable=0    failed=0
pi@192.168.0.103           : ok=3    changed=1    unreachable=0    failed=0

Every node has Vim installed and my personal .vimrc file is also being used. The beauty of this system is adding new nodes is as simple as adding it to the Ansible inventory file and then will have all these new settings pushed to them immediately.

/etc/profile

What better way to use our new Ansible system than syncing some bash aliases to all three nodes. I enjoy using bash aliases, but if they are not synced perfectly across every system, they can quickly become a pain. Let’s make a new Ansible playbook for this task.

cd /etc/ansible/playbooks
touch etc_profile.yml
vim etc_profile.yml

Now let’s define two tasks. One that adds the aliases to the /etc/profile file and one that sources the file so the aliases become available to us in the bash shell.

- hosts: all
 remote_user: pi
  tasks:
  - name: Add aliases to global profile
    become: true
    become_method: sudo
    blockinfile:
     path: /etc/profile
     insertafter: EOF
     block: |
       alias 'll=ls -al'
       alias '..=cd ..'
       alias '...=cd .. && cd..'
       alias 'mvansible=cd /etc/ansible'

  - name: Source profile
    become: true
    become_method: sudo
    shell: . /etc/profile
    args:
    executable: /bin/bash

This playbook will allow us to add bash aliases via the master node and sync them to all other nodes in the network with a single Ansible command!

Let’s try it:

ansible-playbook etc_profile.yml
PLAY [all] *********************************************************                                                                     ************

TASK [Gathering Facts] *********************************************                                                                     ************
ok: [pi@192.168.0.101]
ok: [pi@192.168.0.103]
ok: [pi@192.168.0.102]

TASK [Add aliases to global profile] *******************************                                                                     ************
ok: [pi@192.168.0.101]
ok: [pi@192.168.0.103]
ok: [pi@192.168.0.102]

TASK [Source profile] **********************************************                                                                     ************
changed: [pi@192.168.0.101]
changed: [pi@192.168.0.103]
changed: [pi@192.168.0.102]

PLAY RECAP *********************************************************                                                                     ************
pi@192.168.0.101           : ok=3    changed=1    unreachable=0    f                                                                     ailed=0
pi@192.168.0.102           : ok=3    changed=1    unreachable=0    f                                                                     ailed=0
pi@192.168.0.103           : ok=3    changed=1    unreachable=0    f                                                                     ailed=0

Awesome! It looks like everything worked. Just for fun let’s try it on a slave node:

pi@raspberrypi3BP-1:~ $ ll

total 56K
drwxr-xr-x 7 pi   4.0K Oct 12 00:21 .
drwxr-xr-x 3 root 4.0K Sep 15 23:54 ..
drwx------ 3 pi   4.0K Oct 11 21:38 .ansible
-rw------- 1 pi   6.5K Oct 11 23:10 .bash_history
-rw-r--r-- 1 pi    220 Jul 10 01:07 .bash_logout
-rw-r--r-- 1 pi   3.5K Sep 15 23:27 .bashrc
drwx------ 3 pi   4.0K Sep  7 19:12 .config
drwx------ 3 pi   4.0K Jul 10 01:30 .gnupg
drwxr-xr-x 3 pi   4.0K Sep  7 18:24 .local
-rw-r--r-- 1 pi    807 Jul 10 01:07 .profile
drwxr-xr-x 2 pi   4.0K Sep  7 20:02 .ssh
-rw------- 1 pi   1.6K Oct 12 00:21 .viminfo
-rw-r--r-- 1 pi    165 Sep  7 18:53 .wget-hsts

Looks like our aliases synced correctly.

Conclusion

We have installed and configured out Ansible system for all 3 Raspberry Pi’s. We have an enormous amount of potential for installing other applications and advertising configurations. Expect to see more Ansible posts!

You may also like

Leave a comment

This website uses cookies to improve your experience. We'll assume you're ok with this, but you can opt-out if you wish. Accept