Ansible Docker – Provisioning Docker images using Ansible

In this tutorial, I’m going to walk through how to build Docker containers using Ansible. Ansible is a great tool to use with Docker. We could use it to build our containers, convert long shell scripts into readable roles that can be easily understood. I have been using it for around 2 years and when it comes to provisioning anything it’s my first tool to use! In this article, I will show you how to use it to build containers. With Ansible, you don’t have to write long shell scripts as the roles will be executed on the container. If you’re struggling to find out the use cases, please find below two of mine:

  1. Containerizing your legacy applications. (suppose your application is not on a container, instead of spending time writing long shell scripts you could utilize your roles on a container)
  2. Replacing Shell scripts. Long shell scripts can be difficult to fix or work on. Ansible roles can be easily read and modified and this is excellent for today’s DevOps.

So What do I need to start the tutorial?

  • Linux OS
  • Docker Engine
  • Ansible

How does Ansible build Docker containers?

Ansible uses “Ansible Container” a package which handles the process of running roles on a Docker container. In short, it launches a container called “Conductor“. This container has all the required packages. The conductor then connects to the target container and runs the roles. SO EASY AND YET.

 

 

Once, an image is created you can upload it to the Docker hub or any other registry.

So Let’s begin:

Step 1: Installing Ansible-Container

To build Docker images, we need to install “Ansible-Container”.  By default, it does not ship with Ansible.

  1. Install Ansible-Container
pip install ansible-container[docker]

2. Verify that it has successfully installed by running the command below.

ansible-container

It should work without any errors.

Now that we have installed the Ansible Container, we would need to create our project directory.

mkdir ansible-container-demo
cd ansible-container-demo
ansible-container init

Ansible container will create the files below:

ansible.cfg # override Ansible configs here
ansible-requirements.txt  #list python requirements here
container.yml # this is the file which we will use!
meta.yml 
requirements.yml
.dockerignore  # you know this -.-

Our Role

We then need to create a directory to hold our roles.

mkdir roles
cd roles
ansible-galaxy init optimize-nginx

Note: I won’t be adding the tasks in the role but let’s assume that we have a role that optimizes Nginx.

container.yml

This is the file which we will be working on it. Consider “container.yml” as the Docker Compose file. Here, we list out the roles we would like to run on our Container.

version: "2"
settings:
  conductor:
    base: alpine:3.5
  project_name: infinitypp
services:
  web:
    from: nginx:alpine
    container_name: nginx-custom
    ports:
      - "80:80"
    roles:
      - optimize-nginx
    command: ["/usr/sbin/nginx", "-g", "daemon off;"]
    dev_overrides:
      environment:
        - "DEBUG=1"
registries:
  docker:
    url: https://index.docker.io/v1/
    namespace: infinitypp
    repository_prefix: ''

The syntax is similar to Docker-compose file. At the top, we declare our conductor image alpine:3.5. Then under services, we declare the services. You could also have multiple services. The most important part is the roles section. Here, we are asking Ansible to run the role “optimize-nginx” on the web service.

Imagine, the optimization Nginx script is a shell script of more 200 lines. Now, it is in an Ansible role. You could also use roles from the galaxy.

Now, that we have declared our container.yml settings lets build it.

Building the Container

To build the container, you would use:

ansible-container build

It will launch the “conductor” container. Once it is ready then it will run the Ansible roles to the target container.

root@apache:/var/www/html/ansible-docker-demo# ansible-container build
Building Docker Engine context...
Starting Docker build of Ansible Container Conductor image (please be patient)...
Parsing conductor CLI args.
Docker™ daemon integration engine loaded. Build starting.       project=infinitypp
Building service...     project=infinitypp service=web

PLAY [web] *********************************************************************

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

TASK [optimize-nginx : install Apache server] **********************************
changed: [web]

PLAY RECAP *********************************************************************
web                        : ok=2    changed=1    unreachable=0    failed=0

Applied role to service role=optimize-nginx service=web
Committed layer as image        image=sha256:493d481f64c1bae879f4d2aa83a1dd5b09fa8dcd74945e0bafd1905ae9c9d41a service=web
Build complete. service=web
All images successfully built.
Conductor terminated. Cleaning up.      command_rc=0 conductor_id=4243634ff5bfe08393bbfad1927174493c818757b37381ac7dd4ef0621f00d1a save_container=False

Now, that we have our roles executed on the target conductor it’s time to run it.

How to run the service?

ansible-container run

preview:

root@apache:/var/www/html/ansible-docker-demo# ansible-container run
Parsing conductor CLI args.
Engine integration loaded. Preparing run.       engine=Docker™ daemon
Verifying service image service=web

PLAY [Deploy infinitypp] *******************************************************

TASK [docker_service] **********************************************************
ok: [localhost]

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

All services running.   playbook_rc=0
Conductor terminated. Cleaning up.      command_rc=0 conductor_id=055afdd703de3579e0d8294916a88eb04a03b034a4444af7ca6772ec5d4cd42e save_container=False
root@apache:/var/www/html/ansible-docker-demo# docker ps
CONTAINER ID        IMAGE                           COMMAND                  CREATED             STATUS              PORTS                NAMES
769ee69402ec        infinitypp-web:20181014201817   "/usr/sbin/nginx -g …"   About an hour ago   Up About an hour    0.0.0.0:80->80/tcp   infinitypp_web_1

That’s all. In our example, we had only one role, but imagine you have a few roles and you want to migrate your application to a container.

Screencast