Context

Ansible is an automation tool written in Python which simplistically speaking works like this: Ansible is installed on a machine (the control node) and it will execute a series of Python scripts generated based on some YAML files (playbooks, roles and tasks) on a bunch of other machines (the managed nodes).
This tool was so successful that in October 2015 Red Hat acquired Ansible, Inc., the commercial entity behind it. In case you’re wondering why did this acquisition happened, read this article; it will also highlight the main features of this tool and why should we use it.

I have used Ansible for the past year and I really liked it due to its (apparent) lean learning curve and its ability to automate (almost) anything. I was fortunate enough to use it from a MacBook Pro and thus I was able to easily install it via Homebrew, the macOS package manager.
The common scenarios where I used this tool were: provisioning Linux (CentOS 7.x) environments (e.g. installing Oracle JDK, running MySQL or Oracle databases inside Docker, setting up firewall rules and much more) and performing automated deployments for various SAP Hybris applications.

Why dockerize Ansible?

Until now, Ansible is still not officially supporting a Windows control machine. One brave enough soul might try install Python and then Ansible on a Windows machine, be it virtual or not, but one should use the officially supported ways: running Ansible from a Linux or macOS machine. Or use Docker.

Running Ansible from a Docker container has several benefits:

  • Use a great automation tool on Windows
  • Easy to share the tool across the team, no matter the underlying OS
  • Fixate your Ansible version to ensure the stability & repeatability of your automated processes (no more “Works on my machine … only” syndrome!)
  • Easy to test latest & greatest release without impacting your current dev environment
  • Keeps your dev environment clean

Creating an Ansible Docker image is not that hard and you can find plenty of such images. I decided to write my own in order to learn how to author Docker images, in addition to merely using them.

Since Ansible works like a charm on a Linux control node, I’ve decided to use Alpine Linux Docker image as the base for my own as it’s very small: v3.6 is less than 4MB, while v3.7 is a little over 4MB.
Alpine Linux has its own package manager, apk, so I could’ve installed one of the available packages and be done with it, but this approach has one flaw: even though Ansible has released many versions, Alpine didn’t packed them all, so you just have to live with whatever Ansible versions a specific Alpine release comes with - e.g. v3.6, v3.7.

There are different ways to overcome this limitation: install Ansible using pip, the Python package manager, install it from its sources hosted on GitHub, use a different Linux distribution, etc.
Pip offers lots of Ansible versions to choose from, while installing Ansible from its sources gives you the ability to dockerize any of its git commits. I recommend the former when you need to get stuff done and the latter when you want to experiment/test a specific commit.

Below you may find 3 Docker images I have created for running Ansible on Windows, each with its pros and cons.

Common things

I wanted a simple way of running Ansible playbooks from my Windows machine, so I’m using a Docker volume for sharing them with the Docker container; the playbooks can be found here: /opt/ansible-playbooks - this path is customizable via a build argument, ANSIBLE_PLAYBOOKS_HOME.
The whole development experience is like this: open your favorite (Ansible aware) text editor (see several options below), write your playbooks, then play them from a CLI of your choice (e.g. PowerShell, Git Bash or Cmder) - basically, the same as running Ansible from a Linux or macOS control node.

Below you may find the Docker commands you should use for any of these 3 images.
Please replace the <SUFFIX> placeholder with the appropriate value: apk, pip or git.

Building the Docker image

docker image build --file Dockerfile-<SUFFIX> --tag satrapu/ansible-alpine-<SUFFIX>:latest .

Pushing the Docker image to DockerHub

Please note you need to be logged-in before pushing a Docker image to a registry, be it DockerHub or a private one!

docker image push satrapu/ansible-alpine-<SUFFIX>:latest

Running the Docker container

docker container run -v <DOCKER_HOST_ANSIBLE_PLAYBOOKS_HOME>:/opt/ansible-playbook satrapu/ansible-alpine-<SUFFIX>:latest ansible-playbook <RELATIVE_PATH_TO_YOUR_ANSIBLE_PLAYBOOK>

Approach #1: APK

The Dockerfile used for installing Ansible via apk can be found here. Additionally, this image uses an argument, ANSIBLE_VERSION, which specifies the particular Ansible release version to install at build time.

  • Pros
    • Small image size ~ 97MB
    • Simple Dockerfile
    • Easy to upgrade to future Ansible or Alpine versions
  • Cons
    • Coarse grained control over the Ansible release version to use

Specific Docker commands

docker image build --file Dockerfile-apk --tag satrapu/ansible-alpine-apk:latest .
docker image push satrapu/ansible-alpine-apk:latest
docker container run -v <DOCKER_HOST_ANSIBLE_PLAYBOOKS_HOME>:/opt/ansible-playbook satrapu/ansible-alpine-apk:latest ansible-playbook <ANSIBLE_PLAYBOOK>

Approach #2: PIP

The Dockerfile used for installing Ansible via pip can be found here. Additionally, this image uses an argument, ANSIBLE_VERSION, which specifies the particular Ansible release version to install at build time.

  • Pros
    • Easy to upgrade to future Ansible or Alpine versions
    • Finer grained control over the Ansible release version to use
    • Medium image size ~ 268MB
  • Cons
    • The Dockerfile is a little bit more complex than apk based approach
    • Any upgrade to future Ansible or Alpine versions may require additional effort in order to identify the right prerequisites (e.g. specific versions of Python packages)
    • Increased Docker image build time

Specific Docker commands

docker image build --file Dockerfile-pip --tag satrapu/ansible-alpine-pip:latest .
docker image push satrapu/ansible-alpine-pip:latest
docker container run -v <DOCKER_HOST_ANSIBLE_PLAYBOOKS_HOME>:/opt/ansible-playbook satrapu/ansible-alpine-pip:latest ansible-playbook <ANSIBLE_PLAYBOOK>

Approach #3: Sources

The Dockerfile used for installing Ansible from its sources can be found here. Additionally, this image used an argument, ANSIBLE_GIT_CHECKOUT_ARGS, which is directly passed to the git checkout command, thus it can represent a git branch name, tag or commit hash (short or long), including specific flags, as documented here.

  • Pros
    • Finest grained control over the Ansible release version to use
  • Cons
    • Rather complex Dockerfile
    • Any future change in the process of installing Ansible from sources might negatively affect building this image
    • Any upgrade to future Ansible or Alpine versions may require additional effort in order to identify the right prerequisites (e.g. Python packages)
    • Largest image size ~ 487MB
    • Rather long Docker image build time

Specific Docker commands

docker image build --file Dockerfile-git --tag satrapu/ansible-alpine-git:latest .
docker image push satrapu/ansible-alpine-git:latest
docker container run -v <DOCKER_HOST_ANSIBLE_PLAYBOOKS_HOME>:/opt/ansible-playbook satrapu/ansible-alpine-git:latest ansible-playbook <ANSIBLE_PLAYBOOK>

Example

The below commands have been executed on a machine running Windows 10 Pro x64, release 1709 and Docker version 17.12.0-ce, build c97c6d6.
Given the following folder hello-world on this Windows machine:

P:\Satrapu\Programming\Ansible
└───hello-world
        hello-world.yml

Containing the hello-world.yml file:

---
# This playbook prints a simple debug message
- name: Echo
  hosts: 127.0.0.1
  connection: local

  tasks:
  - name: Print debug message
    debug:
      msg: Hello, world!

I’m playing the hello-world.yml playbook via the following command:

docker container run -v P:\Satrapu\Programming\Ansible\hello-world:/opt/ansible-playbooks satrapu/ansible-alpine-apk ansible-playbook hello-world.yml

PLAY [Echo] ********************************************************************

TASK [Gathering Facts] *********************************************************
ok: [localhost]

TASK [Print debug message] *****************************************************
ok: [localhost] => {
    "msg": "Hello, world!"
}

PLAY RECAP *********************************************************************
localhost                  : ok=2    changed=0    unreachable=0    failed=0

I can run other Ansible commands:

# Print Ansible version
docker container run satrapu/ansible-alpine-apk ansible --version
ansible 2.4.1.0
  config file = /etc/ansible/ansible.cfg
  configured module search path = [u'/root/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python2.7/site-packages/ansible
  executable location = /usr/bin/ansible
  python version = 2.7.14 (default, Dec 14 2017, 15:51:29) [GCC 6.4.0]

Conclusion

Ansible can be run on many operating systems with similar developer experience, as long as they are supported by Docker.
Just because a tool is not officially supported on Windows or it’s *nix only, doesn’t mean you have to forget about it - dockerize it and start using it!

Resources