Technology / DevOps

How to Build and Execute Ansible Automation with Docker

Build-Ansible-Automation-Docker-Social-and-Blog
Follow us
Published on June 26, 2023

In this blog article, we will show how you can harness the power of Docker images and containers within your Ansible project. Specifically, we will demonstrate how to build and execute Ansible automation within a Docker container, providing a streamlined and efficient method for automating your systems or network infrastructure.

A complete, functioning example project can be found in the author’s GitHub repository. Feel free to clone this project and use it to kickstart your own Docker-based Ansible project!

Need to Learn DevOps Skills?

If you are interested in learning more about Ansible or Docker, CBT Nuggets offers a range of DevOps-related training that can help you learn how to leverage automation technologies. 

If you’re not a CBT Nuggets subscriber, sign up for a one-week no-strings-attached trial to explore these courses and many others for network engineers, systems administrators, and DevOps engineers. 

Reviewing our Ansible Project

Before we can dive into building a Docker container for our Ansible automation, we should understand the structure of the Ansible project we will use in this post.  This project emulates large-scale Ansible projects utilized in production environments through the use of Ansible roles, which are the ideal method of applying changes to a systems or network infrastructure at scale. 

In our project, a single role named logging configures a local logging buffer size of 256,000 bytes on a Cisco IOS network device.

Project Dependencies

Let’s start with our project’s dependencies. The requirements.txt file tracks the Python dependencies for our Ansible project, which uses version 2.14.1 of the ansible-core package. The ansible-pylibssh package is also installed, which is needed for Ansible to access our Cisco IOS network devices via SSH.

cjh@playground:~/ansible-docker$ cat requirements.txt
ansible-core==2.14.1
ansible-pylibssh

The requirements.yaml file tracks the Ansible roles and collections that are dependencies for our Ansible project. In this case, the ansible.netcommon and cisco.ios collections are installed through this file.

cjh@playground:~/ansible-docker$ cat requirements.yaml
---
collections:
  - ansible.netcommon
  - cisco.ios

Ansible Configuration & Inventory Files

The root of our Ansible project has an ansible.cfg file present. This file disables SSH host key checking, which allows Ansible to SSH into hosts without validating the host’s identity using the host’s SSH fingerprint.

cjh@playground:~/ansible-docker$ cat ansible.cfg
[defaults]
host_key_checking = False

The inventory.yaml file at the root of the project contains a single group named “switches”, which houses a single host named “Switch-1”.

cjh@playground:~/ansible-docker$ cat inventory.yaml
---
all:
  children:
    switches:
      hosts:
        switch-1:

Two directories named group_vars and host_vars contain variable information for groups and hosts defined in our inventory file. 

A file inside the group_vars directory named switches.yaml contains variable information for the “switches” group, while a file inside the host_vars directory named Switch-1.yaml contains variable information for the “Switch-1” host.

cjh@playground:~/ansible-docker$ tree ./group_vars/ ./host_vars/
./group_vars/
└── switches.yaml
./host_vars/
└── Switch-1.yaml

0 directories, 1 file

cjh@playground:~/ansible-docker$ cat group_vars/switches.yaml
---
ansible_user: automation
ansible_password: automation
ansible_connection: ansible.netcommon.network_cli
ansible_network_os: cisco.ios.ios
ansible_become: yes
ansible_become_method: enable

cjh@playground:~/ansible-docker$ cat host_vars/Switch-1.yaml
---
ansible_host: 192.168.10.3
ansible_ssh_common_args: >-
  -o HostKeyAlgorithms=ssh-rsa
  -o KexAlgorithms=diffie-hellman-group1-sha1

Ansible Roles

Because our project leverages Ansible roles, a directory named roles contains Ansible automation for the roles defined in our environment. A single role named logging is present in this project, which has an Ansible playbook named main.yaml within the tasks directory. 

The output of the tree ./roles command below demonstrates the structure of the roles directory.

cjh@playground:~/ansible-docker$ tree ./roles/
./roles/
└── logging
    └── tasks
        └── main.yaml

2 directories, 1 file

The main.yaml file uses the cisco.ios.ios_logging_global Ansible resource module to configure the local logging buffer of a Cisco IOS network device with a size of 256,000 bytes. The output of the cat ./roles/logging/tasks/main.yaml command below displays the contents of this Ansible playbook.

cjh@playground:~/ansible-docker$ cat ./roles/logging/tasks/main.yaml
---
- name: Configure logging buffer size
  cisco.ios.ios_logging_global:
    config:
      buffered:
        size: 256000

Lastly, an Ansible playbook at the root of the project named site.yaml serves as the entry point for our Ansible automation. This playbook executes Ansible automation defined in roles against our inventory.

cjh@playground:~/ansible-docker$ cat site.yaml
---
- hosts: switches
  roles:
    - logging

Creating an Ansible Docker Image

Now that we understand the structure of our Ansible project, we can explore how to create a Docker image that will execute our Ansible automation using a plaintext file called a Dockerfile. This file is typically stored in the root of a project and contains instructions that inform Docker how to build a Docker image for our project.

Below is the Dockerfile we will use for our Ansible project.

FROM python:3.10

COPY requirements.txt .

RUN apt -y update && \
    apt -y install sshpass && \
    python -m pip install --upgrade pip && \
    python -m pip install -r requirements.txt && \
    apt -y autoremove && \
    rm -rf /var/lib/apt/lists/*

COPY requirements.yaml .

RUN ansible-galaxy collection install -r requirements.yaml

COPY inventory.yaml /etc/ansible/hosts
COPY group_vars/ /etc/ansible/group_vars
COPY host_vars/ /etc/ansible/host_vars
COPY ansible.cfg /etc/ansible/ansible.cfg

COPY site.yaml .
COPY roles/ .

CMD ["ansible-playbook", "site.yaml"]

Analyzing the Dockerfile

Let’s walk through our Dockerfile step-by-step from the top.

FROM python:3.10

This instruction informs Docker that our container should be built starting from the official Python container, which has a Python version of 3.10 included in it.

COPY requirements.txt .

RUN apt -y update && \
    apt -y install sshpass && \
    python -m pip install --upgrade pip && \
    python -m pip install -r requirements.txt && \
    apt -y autoremove && \
    rm -rf /var/lib/apt/lists/*

The first COPY instruction copies the requirements.txt file from the root of our project into the Docker container. The second RUN instruction executes a number of shell commands inside the container. The purpose of each command is as follows:

  • apt -y update - Update the apt package manager’s local cache with a list of available upgrades.

  • apt -y install sshpass - Install the sshpass package using the apt package manager. Ansible uses this package to provide a password when opening SSH sessions to systems or network devices.

  • python -m pip install –upgrade pip - Upgrade Python’s package installer program, which is named “pip”.

  • python -m pip install -r requirements.txt - Install Python dependencies listed in the requirements.txt file copied into the Docker container.

  • apt -y autoremove - Remove unnecessary operating system packages from the container through the apt package manager. This reduces the overall size of the container.

  • rm -rf /var/lib/apt/lists/* - Delete the apt package manager’s local cache of available upgrades. This also reduces the overall size of the container.

These shell commands are chained together and executed within a single RUN statement to reduce the number of “layers'' within the Docker container. This reduces the overall size of the container and allows you to quickly build the Docker container if you make minor changes to the Ansible automation within your project without needing to reinstall all of your dependencies.

COPY requirements.yaml .

RUN ansible-galaxy collection install -r requirements.yaml

The first COPY instruction copies the requirements.yaml file from the root of our project into the Docker container. The second RUN instruction uses the Ansible Galaxy utility to install collections defined by the requirements.yaml file.

COPY inventory.yaml /etc/ansible/hosts
COPY group_vars/ /etc/ansible/group_vars
COPY host_vars/ /etc/ansible/host_vars
COPY ansible.cfg /etc/ansible/ansible.cfg

This set of COPY instructions add our inventory.yaml inventory file, group and host variable directories (and their contents), and the ansible.cfg Ansible configuration file to the Docker container. Each of these files are copied to the default locations Ansible expects them to be in.

COPY site.yaml .
COPY roles/ .

This set of COPY instructions adds the site.yaml Ansible playbook, as well as the content of all roles in the roles directory, such as Ansible playbooks, filters, default variables, and so on.

CMD ["ansible-playbook", "site.yaml"]

Finally, the CMD instruction defines the shell command that should be executed when the Docker container is started. We use the ansible-playbook command to execute the Ansible playbook in the site.yaml file, which subsequently executes Ansible playbooks against relevant hosts in each defined role.

Building the Docker Container

Now that we understand how the Dockerfile will build our Docker image, it’s time to build the image. This is done with the docker build command. The -t parameter for this command allows you to define a name and tag for the resulting image. In this scenario, I defined a name of chrisjhart/ansible-automation and a tag of latest

Finally, the docker build command takes in a mandatory argument of where the Dockerfile for your application is located. To satisfy this argument, I passed in the current working directory with ./.

cjh@playground:~/ansible-docker$ docker build -t chrisjhart/ansible-automation:latest ./
Sending build context to Docker daemon  14.85kB
Step 1/12 : FROM python:3.10
 ---> 0f95b1e38607
Step 2/12 : COPY requirements.txt .
 ---> Using cache
 ---> 528d9036be4f
Step 3/12 : RUN apt -y update &&     apt -y install sshpass &&     python -m pip install --upgrade pip &&     python -m pip install -r requirements.txt &&     apt -y autoremove &&     rm -rf /var/lib/apt/lists/*
 ---> Using cache
 ---> f14dcfbb33f8
Step 4/12 : COPY requirements.yaml .
 ---> Using cache
 ---> 37b44da770d9
Step 5/12 : RUN ansible-galaxy collection install -r requirements.yaml
 ---> Using cache
 ---> 6f94a8dfdcd0
Step 6/12 : COPY inventory.yaml /etc/ansible/hosts
 ---> Using cache
 ---> 55042eecdd75
Step 7/12 : COPY group_vars/ /etc/ansible/group_vars
 ---> Using cache
 ---> da585936e9c1
Step 8/12 : COPY host_vars/ /etc/ansible/host_vars
 ---> Using cache
 ---> 06fbbff2bf71
Step 9/12 : COPY ansible.cfg /etc/ansible/ansible.cfg
 ---> Using cache
 ---> 8316fc24fd5b
Step 10/12 : COPY site.yaml .
 ---> Using cache
 ---> 7fd4f2e2a1ef
Step 11/12 : COPY roles/ .
 ---> Using cache
 ---> 9d5266952967
Step 12/12 : CMD ["ansible-playbook", "site.yaml"]
 ---> Using cache
 ---> c5ceeadc218b
Successfully built c5ceeadc218b
Successfully tagged chrisjhart/ansible-automation:latest

We can confirm that our image was built successfully with the docker image ls command. You can filter the results by passing the name and tag of the image you’re searching for into the command, as shown below.

cjh@playground:~/ansible-docker$ docker image ls chrisjhart/ansible-automation
REPOSITORY                      TAG       IMAGE ID       CREATED              SIZE
chrisjhart/ansible-automation   latest    c5ceeadc218b   About a minute ago   1e+03MB

Executing Ansible Automation in a Docker Container

Now that a Docker image for our Ansible project has been built, it’s time to run a container based on this image! We can do this with the docker run command, passing in the name and tag of the image we would like to execute.

cjh@playground:~/ansible-docker$ docker run chrisjhart/ansible-automation:latest

PLAY [switches] ********************************************************************************************************

TASK [Gathering Facts] *************************************************************************************************
ok: [Switch-1]

TASK [logging : Configure logging buffer size] *************************************************************************
ok: [Switch-1]

PLAY RECAP *************************************************************************************************************
Switch-1                   : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

As you can see, the output of the Docker container shows that the Ansible automation in our project executed against our network infrastructure successfully. Ansible confirmed that the Switch-1 host defined in our inventory file has 256,000 bytes allocated for the local logging buffer as desired.

Final Thoughts

We have successfully executed the automation in our Ansible project using a Docker container. Now, we can take advantage of the features Docker offers, like minimizing differences between each local developer’s environment —  and having a single, portable image containing our automation that we can easily execute against our production environment. 

We can also easily pivot to a Continuous Delivery model for our Ansible project by executing our automation as part of a CI/CD pipeline through GitHub Actions, GitLab CI, Circle CI, or Jenkins pipelines. Remember, this example project can be found in the author’s GitHub repository, so feel free to replicate this sample project through GitHub and kickstart your own Docker-based Ansible project!


Ultimate DevOps Cert Guide

By submitting this form you agree to receive marketing emails from CBT Nuggets and that you have read, understood and are able to consent to our privacy policy.


Don't miss out!Get great content
delivered to your inbox.

By submitting this form you agree to receive marketing emails from CBT Nuggets and that you have read, understood and are able to consent to our privacy policy.

Recommended Articles

Get CBT Nuggets IT training news and resources

I have read and understood the privacy policy and am able to consent to it.

© 2024 CBT Nuggets. All rights reserved.Terms | Privacy Policy | Accessibility | Sitemap | 2850 Crescent Avenue, Eugene, OR 97408 | 541-284-5522