Easy VM updates with Ansible
Oct 05, 2023
I have a load of VMs running on a physical server in my house. I started with a few VMs and a manual process, but now I have many VMs and I'm tired of updating them all.
I knew Ansible is a popular and easy tool for configuring infrastructure, so I decided to put it to work in this project.
I'll be creating a Docker container to run my Ansible scripts from, as well as my Ansible configuration and playbook.
I need to create two files to configure my Ansible project,
ansible.cfg (general config) and
inventory.ini (my list of VMs).
inventory = inventory.ini
remote_user = james
host_key_checking = False ; see below
host_key_checking, which isn't too bad for a set of hobby VMs on a home network, but should be avoided on any production environment. @davidolrik has a good solution on StackOverflow which I'd recommend reading.
; ... and so on
This file contains all the VMs I want to address with Ansible. They're stored in the
vms group for easy referencing.
The Docker container
Creating a Docker container for this might seem like overkill; Ansible is easy to install on most platforms. Generally I set up anything I'm working on in containers. This helps me keep my host system free of extraneous dependencies, as well as giving me some confidence that whatever I build will work when I come back to it months/years later.
I'm going to create a very simple Docker container which has access to my Ansible configuration, and is able to connect to my VMs to run Ansible scripts.
The first step is the Dockerfile:
# Use a base image with Python (Ansible's requirement)
# Install Ansible and OpenSSH client
RUN pip install ansible && \
apt-get update && \
apt-get install -y openssh-client && \
rm -rf /var/lib/apt/lists/*
# Set up our user and copy in our SSH keys
RUN useradd -m user
RUN mkdir -p /home/user/.ssh && \
ln -s /run/secrets/user_ssh_key /home/user/.ssh/id_ed25519 && \
chown -R user:user /home/user/.ssh
This is a simple Dockerfile - it installs a couple of dependencies, and copies some SSH information from
/run/secrets/. We'll get onto the secrets in a moment. This SSH information is copied so that we can supply an SSH key to our container, allowing it to authenticate with the various VMs.
Secrets are a feature of Docker Compose that allow us to work more safely with, well, secrets, such as SSH information. They can be passed in through Docker Compose, and my
docker-compose.yml ends up being quite simple indeed.
As you can see, I'm mounting the two config files (and a
playbooks/ directory from the next chapter) into the container, and passing a secret called
user_ssh_key in. The
docker-compose.yml file sets the source of this secret (
~/.ssh/id_ed25519, a key I generated earlier) and it gets attached to the container with the
secrets instruction. This secret is then available at
/run/secrets/user_ssh_key within the container.
The last step in this creation is the Ansible playbook. Ansible comes with some useful built-in playbooks, so this playbook was very simple to create.
- name: Update and Restart All VMs
hosts: vms # Target the 'vms' group
become: yes # Run tasks as sudo
- name: Update apt cache and upgrade packages
- name: Reboot the server
msg: "Rebooting after upgrades"
reboot_timeout: 300 # Wait up to 5 minutes for reboot
Now all I need to do is build my image and run my container for the correct playbook.
docker compose build
docker compose run \
ansible-playbook /playbooks/update-vms.yml \
--ask-become-pass argument tells Ansible to collect my password from the terminal.
After running the second command, the container is started and is able to run the mounted playbook with the configuration. The SSH configuration worked successfully and the container was able to communicate with the VMs on my network.