Welcome again to the series “Docker: Containers for the Masses”. Today’s post will be the fourth in this series and will concern itself with management of Docker using Ansible
For the reader just joining, the previous three posts in this series are:
- Introduction – Introduction to Docker
- Installation – Installation of Docker
- Using Docker – Using Docker
With the basics of Docker having been presented, the following blog posts will cover more advanced usage-cases for Docker as well as complimentary and related projects.
As mentioned before, I have had the pleasure of working with both Docker and Ansible and realized there were some features in Ansible related to Docker that I wanted to familiarize myself with and take advantage of. Additionally, the opportunity to contribute new features to Ansible presented itself. One of those features, the docker_facts Ansible module, will be covered in a later post of this series.
This first post will familiarize the reader to what Ansible is, its modular design, and introduce a simple Ansible playbook.
Ansible
There is a good chance that readers of this blog post know what Ansible is and they can skip ahead two sections.
For readers who don’t know, Ansible is a tool in the same category as Puppet, Chef, and SaltStack. Ansible is an automation tool, more specifically an orchestration engine. It is very simple and manages nodes via ssh using a push model, not requiring a server or clients acting on behalf of the server as so many other automation tools that use a server and pull model do. It is modular and modules simply output JSON that Ansible engine acts upon.
Ansible is written in python and uses what it calls playbooks, written in YAML, to represent how it expects the state of a system to be. Specific tasks, called plays, occur to get a node to be in the specified state. Like SaltStack, it uses Jinja templates for files it needs to dynamically create.
Ansible uses an inventory file that contains a list of the nodes which can be grouped into different classifications depending on what purpose you want for each – think set-theory. Furthermore you can also use multiple inventory files or even dynamic inventory plugins that take into account elastic environments where it would be useful to be able to manage resources intelligently.
Ansible Basics
To give the reader who is not familiar with Ansible an idea and context for the rest of this post, a simple playbook shows how Ansible is used to install Percona XtraDB cluster packages.
- name: Install Percona XtraDB Cluster server
apt: pkg=
state=present
with_items:
- percona-xtradb-cluster-server-5.6
- python-mysqldb
- xinetd
- git
- name: Configure Percona XtraDB
template: src=etc/mysql/my.cnf.j2
dest=/etc/mysql/my.cnf
mode=0640 owner=mysql group=root
notify: restart mysql
when: bootstrap_check.stdout == "bootstrap"
The above example is contains a snippet from a playbook consisting of two plays, the name
describing what the play does.
In the case of the playbook above, the first play being run results in the 4 packages listed in with_items
being installed using the apt
Ansible module, the state
of these to be present
. Ansible modules are idempotent, hence once these modules are installed, subsequent plays will result in no action.
The next play uses template
to specify a source template my.cnf.j2
to be rendered as /etc/mysql/my.cnf
, set to the file attributes and ownership listed by mode
, and owner
and group
, respectively. When this play is completed, there will be /etc/mysql/my.cnf
file present
and any variables in the jinja template my.cnf.j2
being interpolated in the template rendering. As mentioned above, Ansible modules are idempotent, the result being that once the template is rendered and file created, it won’t be re-rendered unless the template changes.
Again, this is a snippet, and there are other great examples available including an example Ansible playbook git repository complete with numerous playbooks as well as the documentation that one can read to become an expert Ansible user.
Ansible and Docker
As I’ve mentioned in other blog posts on this site, my team, HP Advanced Technology Group, has taken an interest in a number of interesting and burgeoning projects and/or technologies, both internal and external to HP. Two of those projects are Ansible and Docker, and both go together well.
This and the next few blog posts will cover several Docker modules and plugins:
- The
docker
module. One of the things one naturally would want to be able to do is have a means to manage docker containers. By management, this would mean launch, provision, and delete containers. - The
docker_image
module. This module is used for building images specifying a [Dockerfile][dockerfile]. - The
docker_facts
module. This module has not been released and was developed by the author of this blog. It’s used for surfacing information about Docker images and containers that can be used in playbooks to do a number of tasks - The docker dynamic inventory plugin. This plugin allows a user to be able to manage the very containers launched by Ansible using Ansible to obtain from Docker a dynamic list that has varying membership (hence “dynamic”).
The Ansible docker
module
The first module to be discussed is the very module one would use for launching or deleting container, the Ansible docker module. This module is part of the default Ansible installation and will only require the Docker python client library to be installed.
As you recall in using Docker by hand using the Docker CLI, you would use the Docker CLI to do these types of tasks. Ansible makes this even more simple.
This is a very simple module to use and essentially a play describes itself, which really is the whole idea behind ansible:
- name: docker image control
local_action:
module: docker
docker_url: "tcp://somehost:4243"
image: "capttofu/percona_xtradb"
name: "db"
state: "present"
publish_all_ports: yes
In the above example, this play would result in running locally (local_action
) the launching of a container with the name of db
with all ports specified in image (Dockerfile) to be published. Furthermore, the parameter docker_url
specifies which Docker daemon to issue this to, from the local action. This is important to note, because otherwise it would assume localhost. In real-world situations, one may have the need to manage Docker containers across multiple servers running the Docker daemon and would need to be able to connect to Docker on those hosts. Under the hood, the Ansible docker module is using the Ansible client python module that talks to the daemon on the current host specified in docker_url
.
This makes for an interesting topography: managing systems (VM or bare metal) that run Docker and in turn, managing those containers, all with Ansible.
A more practical example is one that can be used to run multiple containers
- hosts: localhost
tasks:
- name: run docker containers
local_action:
module: docker
docker_url: tcp://127.0.0.1:4243
image: capttofu/apache
name: container_
state: present
with_sequence: count=5
The example above is a simple playbook that has one task: run 5 docker containers. This playbook uses local_action
to run docker locally against the Docker daemon running on port 4243 – which could be any docker host but in this case is simplified to 127.0.0.1 – to connect to to run these containers. The image used is capttofu/apache
, a simple container that runs Apache. The name of the container passed utilizes the the variable item
which is whatever value is being iterated over using the ineger sequence with_sequence
, in this example 1 through 5.
Running this playbook results in the follow output from Ansible:
$ ansible-playbook -i hosts sitex.yml
PLAY [localhost] **************************************************************
GATHERING FACTS ***************************************************************
ok: [localhost]
TASK: [run docker containers] *************************************************
changed: [localhost] => (item=1)
changed: [localhost] => (item=2)
changed: [localhost] => (item=3)
changed: [localhost] => (item=4)
changed: [localhost] => (item=5)
PLAY RECAP ********************************************************************
localhost : ok=2 changed=1 unreachable=0 failed=0
Upon Ansible running this playbook, there should be 5 containers running:
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
cd0401b8027e capttofu/apache:latest /usr/local/sbin/entr 3 seconds ago Up 3 seconds 22/tcp, 80/tcp, 443/tcp container_5
aacf1df11a19 capttofu/apache:latest /usr/local/sbin/entr 3 seconds ago Up 3 seconds 22/tcp, 80/tcp, 443/tcp container_4
29925f68b768 capttofu/apache:latest /usr/local/sbin/entr 4 seconds ago Up 3 seconds 22/tcp, 80/tcp, 443/tcp container_3
125f6c6bbfc0 capttofu/apache:latest /usr/local/sbin/entr 4 seconds ago Up 4 seconds 22/tcp, 80/tcp, 443/tcp container_2
82d714bf1fa3 capttofu/apache:latest /usr/local/sbin/entr 4 seconds ago Up 4 seconds 22/tcp, 80/tcp, 443/tcp container_1
This is a very simple example of using the [Ansible Docker plugin][ansible_docker_plugin] to introduce the reader to how simple it is to use Ansible for provisioning Docker containers. A more thorough and interesting example will be demonstrated on a later post that will revisit some of the material covered in the presentation the author recently gave at AnsibleFest NYC 2014.
Summary
This blog post introduced the reader to Ansible, describing the various Ansible Docker modules and plugins, and provided a demonstration of the basics of using Ansible with Docker with the Ansible Docker module. Subsequent blog posts will cover the Ansible docker_image module, docker_facts module and the Ansible docker dynamic inventory plugin.