Docker the Ansible Way — Part 2
Solution for Orchestrating Docker Containers with Ansible
In the previous article, I presented the problem to how to orchestrate WordPress and MySQL 5.7 containers using Ansible. I also gave an example InSpec script that you can use to validate the solution, as you develop it.
In related articles, I showed how to do this in using a wrapper script (sh
, python
, ruby
, perl
) to automate Docker CLI (docker
) command to solve this very same problem.
We should easily use the shell module to achieve the same effect with Ansible, but we would have to add an avalanche of when
clauses to shield the tasks from errors and make them idempotent.
With Ansible, the docker modules, specifically docker_network
, docker_volume
, and docker_container
, are now mature and stable, and interact with the underlying Docker Python SDK to allow us to do the same task, but with an flexible and intuitive Ansible YAML-DSL than Python code.
Below is the complete solution using Ansible, and a small code walkthrough.
Previous Article
Problem and Verification Script
Full Solution Source
You can download this and run it to see the results:
GIST=https://gist.githubusercontent.com/darkn3rd/93e0fa294baf2bc8bbe642d4a48188f7/raw/0e88da8a39781313fb8e6f9c28cb30d15511bcab/docker_run.ymlcurl -O $GIST
chmod +x docker_run.yml./docker_run.yml
The Ansible playbook:
Code Walkthrough Notes
Here’s my explanation on the different parts of this script.
Variables
The first phase step in this framework is to set up variables that I would use throughout the playbook, and fetch the environment variable WORDPRESS_PORT
.
In the variable blow, this line may draw your curiosity:
"{{ lookup('env','WORDPRESS_PORT') | default(8080)}}"
This is a Jinja2, which has is a template language used in Ansible. Ansible extends this language to add features that make it useful, or essential rather, for change configuration.
One of these features are the lookup plugins, including the env plugin, which we are using to fetch the environment.
Another feature are Jinja2 filters, which are useful for transforming data. We use a default filter, to set a value when existing value is not defined, which will happen if the environment variable that looked up does not exist.
Volume Task
We create our persistent volume with docker_volume
:
- name: "Create a Volume"
docker_volume:
name: "{{ docker_volume }}"
Network Task
We create our private container network with docker_network
:
- name: "Create a network"
docker_network:
name: "{{ docker_network }}"
Note that normally this operation was not idempotent in the past, but now this handles the scenario to only create one wordpress_net
network, and doesn’t error out if it is created already.
MySQL Container Task
We launch our database container using mysql:5.7 image
, use a restart policy of always
, use our volume db_data
, use our network of wordpress_net
, and configure the database by passing environment variables to the container.
- name: "Launch database container"
docker_container:
name: "{{ db_name }}"
image: mysql:5.7
volumes:
- "{{ docker_volume }}:/var/lib/mysql:rw"
restart_policy: always
network_mode: "{{ docker_network }}"
env:
MYSQL_ROOT_PASSWORD: wordpress
MYSQL_DATABASE: wordpress
MYSQL_USER: wordpress
MYSQL_PASSWORD: wordpress
If this db
container is already running, stopped, or removed, Ansible will handle bringing the container to the desired state specified above.
WordPress Container Task
We launch our web application WordPress container using the image wordpress:latest
, use a restart policy always
, use our network of wordpress_net
, map the container port of 80
to a host port we can access on our workstation, and configure WordPress app by passing environment variables to the container.
- name: "Launch wordpress container"
docker_container:
name: "{{ wp_name }}"
image: wordpress:latest
ports:
- "{{ wp_host_port }}:{{ wp_container_port }}"
restart_policy: always
network_mode: "{{ docker_network }}"
env:
WORDPRESS_DB_HOST: "{{ db_name }}:3306"
WORDPRESS_DB_PASSWORD: wordpress
If this wordpress
container is already running, stopped, or removed, Ansible will handle bringing the container to the desired state specified above.
Final Notes
This was tested on both Ansible 2.7 and Ansible 2.8, and should work for Ansible 2.2 and above. I hope this was useful for both understanding Docker and Ansible, as well as using InSpec if you used that to verify the results.