Deploy Docker container on Ubuntu Core using Ansible¶
Ansible is an open-source automation engine used to automate processes such as provisioning, continuous software deployment, and configuration management. Ansible uses YAML scripts called playbooks to automate tasks and ensure systems remain in the state declared in the playbook. It is compatible with many systems including Ubuntu Core.
This guide shows you how to deploy Docker containers on Ubuntu Core using the Ansible Snap module. For demonstration purposes, we will deploy an instance of RabbitMQ on a Raspberry Pi. The same playbook will also deploy an instance of RabbitMQ on another Raspberry Pi running Ubuntu Server to demonstrate the ease with which you can distribute containerized workloads across different Ubuntu distributions including Ubuntu Core.
The methods demonstrated here should also work on other distributions that support Snapd.
Install Ansible¶
To get started, you’ll need to install Ansible on the control node. This is the machine you will use to run Ansible CLI tools. On Debian-based systems, you can do this by running:
sudo apt install ansible
For information on installing Ansible on other systems, check out the official Ansible installation guide.
Create the inventory file¶
The inventory file defines the Ansible managed nodes (or hosts) that Docker containers will be deployed to. To create this file, you’ll need each host’s username and IP address.
You must be able to SSH into any nodes in your inventory file. To confirm this, run the following for each host:
ssh {username}@{ip address}
To create the inventory file, create a new file called inventory.yaml in your preferred directory, and enter each host machine’s details as shown:
machines:
hosts:
# Ubuntu Server 24.04 LTS - RPi4
rpi4:
ansible_host: 192.168.0.57
ansible_user: ubuntu
# Ubuntu Core 24 - RPi5
rpi5:
ansible_host: 192.168.0.58
ansible_user: ubuntu
ansible_python_interpreter: /usr/bin/python3
machines is the group name for the hosts. You can define different groups for hosts so each group executes different sets of tasks from the same playbook.
For nodes running Ubuntu Core, you must specify the ansible_python_interpreter to avoid getting a warning message about future Python interpreters.
Executing some tasks in the playbook will require root privileges. You can enable passwordless root access or provide the user password for each host by setting the ansible_password field.
Confirm hosts are reachable¶
It’s good practice to confirm the inventory.yaml file is properly set up and the hosts can be reached before starting on the playbook. Enter the following command to ping each host in the machines group in the inventory:
$ ansible machines -i inventory.yaml -m ping rpi5 | SUCCESS => { "changed": false, "ping": "pong"}rpi4 | SUCCESS => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python3" }, "changed": false, "ping": "pong"}If a host is unreachable, ensure that your public SSH key is added to the .ssh/authorized_keys file on that host.
Prepare the playbook¶
Ansible must perform a series of actions, i.e. tasks, to deploy a Docker container to a node. These tasks are written in YAML format, in the order they should be executed, and saved in a file called a playbook.
Create the playbook and ping the hosts¶
Create the playbook.yaml file in the same directory as the inventory. Inside the playbook, write the name of the playbook and specify the targeted host group. As the first task, ping the hosts using Ansible’s built-in ping module. This will confirm which hosts are available:
name: Deploy RabbitMQ
hosts: machines
tasks:
- name: Ping my hosts
ansible.builtin.ping:
Install the Docker snap¶
Installing the Docker snap in the nodes is the second task in the playbook. The field become must be set to true because root privileges are required for this and subsequent tasks.
- name: Install Docker snap
become: true
community.general.snap:
name:
- docker
channel: latest/stable
state: present
Pull a RabbitMQ image¶
This task downloads the RabbitMQ image that will be deployed. You’ll need the name of the specific version of RabbitMQ you want to deploy as shown:
- name: Pull an image
become: true
community.docker.docker_image_pull:
name: rabbitmq:4.0.2-management
Configure RabbitMQ¶
This task sets the default username and password for the RabbitMQ instances using Ansible’s built-in shell:
- name: Setup RabbitMQ configurations
become: true
ansible.builtin.shell: |
sudo echo "RABBITMQ_DEFAULT_USER=demo-user" > $HOME/rabbitmq_conf.env
sudo echo "RABBITMQ_DEFAULT_PASS=SomeReallyStrongPassword" >> $HOME/rabbitmq_conf.env
Start the RabbitMQ container¶
This is the last task in the playbook. It launches the instances of RabbitMQ in the managed nodes:
- name: Start RabbitMQ container
become: true
community.docker.docker_container:
image: rabbitmq:4.0.2-management
name: message_broker
hostname: docker_ansible_demo
env_file: $HOME/rabbitmq_conf.env
volumes: /var/snap/docker/current/data:/var/lib/rabbitmq
ports:
# Publish container port 5672 as host port 17338
- 17338:5672
# Publish container port 15672 as host port 18280
- 18280:15672
Run the playbook¶
Save the playbook and execute it by running:
ansible-playbook -i inventory.yaml playbook.yaml
If the containers are successfully deployed, it should return:
$ ansible-playbook -i inventory.yaml playbook.yaml PLAY [Deploy Docker on Ubuntu Core with Ansible] *************************** TASK [Gathering Facts] *****************************************************ok: [rpi5]ok: [rpi4] TASK [Ping my hosts] *******************************************************ok: [rpi5]ok: [rpi4] TASK [Install Docker snap] *************************************************ok: [rpi5]ok: [rpi4] TASK [Pull an image] *******************************************************ok: [rpi5]ok: [rpi4] TASK [Setup RabbitMQ configurations] ***************************************changed: [rpi5]changed: [rpi4] TASK [Start RabbitMQ container] ********************************************ok: [rpi5]ok: [rpi4] PLAY RECAP *****************************************************************rpi4 : ok=6 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0rpi5 : ok=6 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 Ansible does not display an output by default. To find out the status of the containers at any time, run the following command:
ansible machines -i inventory.yaml -m shell -a "sudo docker container ls"
The output will be:
$ ansible machines -i inventory.yaml -m shell -a "sudo docker container ls" rpi5 | CHANGED | rc=0 >>CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMESa427d99efd75 rabbitmq:4.0.2-management "docker-entrypoint.s…" 3 hours ago Up 3 hours 4369/tcp, 5671/tcp, 15671/tcp, 15691-15692/tcp, 25672/tcp, 0.0.0.0:17338->5672/tcp, 0.0.0.0:18280->15672/tcp message_brokerrpi4 | CHANGED | rc=0 >>CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES74f18efdd315 rabbitmq:4.0.2-management "docker-entrypoint.s…" 3 hours ago Up 3 hours 4369/tcp, 5671/tcp, 15671/tcp, 15691-15692/tcp, 25672/tcp, 0.0.0.0:17338->5672/tcp, 0.0.0.0:18280->15672/tcp message_broker Next steps¶
In this guide, all the devices are assigned the same password. In practice, this does not align with the best practices for maintaining system security. Consider dividing your hosts into groups and creating different login credentials for each.
You can also take advantage of Patterns to run your playbook or commands against specific groups or hosts. Patterns can be used to refer to a group, set of groups, an IP address, or all the hosts in your inventory.
Although values such as env_file and a specific RabbitMQ image are indicated within the tasks, this does not scale well when you have a large number of hosts. Ideally, these values should be replaced with variables for more efficient management.