3 min read

Docker with Ansible Part I : from Docker Compose to Ansible

Docker with Ansible Part I : from Docker Compose to Ansible

If you use Ansible to deploy your servers, you can go further by also integrating the deployment of your applications with Docker Compose.

This is the first post in a series of 3 articles about Docker and Ansible:
Part I: from Docker Compose to Ansible
Part II: using variables
Part III: using vault to encrypt sensitive information

Install Docker and Docker Compose

You'll find the instructions here.

Install Ansible

For installing the latest stable version of Ansible on CentOS :

$ yum install epel-release && yum install ansible

Personally I use the version under development to be able to take advantage of the latest features (for example, creating an internal network is not possible in the stable version 2.7) :

pip install git+https://github.com/ansible/ansible.git@devel

Create your playbook

We will deploy a stack with Nginx, PHP and mySQL.
Create your working directory :

$ mkdir ~/ansible-docker && cd ~/ansible-docker

Docker Compose Syntax

Here is a classic file with 3 services :

version: '3.6'

services:
  nginx:
    image: nginx:latest
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./app.conf:/etc/nginx/conf.d/
      - php-app:/var/www/app
    restart: unless-stopped
  php:
    image: php:7-fpm
    volumes:
      - php-app:/var/www/app
    restart: unless-stopped
  db:
    image: mysql:latest
    volumes:
      - mysql:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: secret_root
      MYSQL_DATABASE: database_name
      MYSQL_USER: sql_user
      MYSQL_PASSWORD: secret_user
    restart: unless-stopped

volumes:
  mysql:
  
networks:
  network_app:
    driver: bridge
    ipam:
     config:
       - subnet: 172.16.98.0/24
  

Ansible way

Create your Nginx : app.conf :

$ vi app.conf
server {
    listen 80;
    server_name your_ip;

    client_max_body_size 4M;
    client_body_buffer_size 128k;

    root /var/www/app;
    index index.php;

    location ~ \.php$ {
        try_files $uri =404;
        fastcgi_index index.php;
        fastcgi_pass php:9000;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include /etc/nginx/fastcgi_params;
    }
}

Create your Ansible playbook :

$ vi deploy.yml

And the same 3 services :

---
- hosts: localhost

  tasks:

  - name: Create network
    docker_network:
      name: network_app
      ipam_config:
        subnet: "172.16.98.0/24"
        
  - name: Run Nginx container
    docker_container:
      name: 'nginx'
      recreate: true
      restart_policy: unless-stopped
      image: 'nginx:latest'
      published_ports:
        - "80:80"
        - "443:443"
      volumes:
        - "./app.conf:/etc/nginx/conf.d/app.conf"
        - "php-app:/var/www/app"
      networks:
        - name: "network_app"        - 

  - name: Run PHP container
    docker_container:
      name: 'php'
      recreate: true
      restart_policy: unless-stopped
      image: 'php:7-fpm'
      volumes:
        - "php-app:/var/www/app"
      networks:
        - name: "network_app"
        
  - name: Run Percona container
    docker_container:
      name: 'percona'
      recreate: true
      restart_policy: unless-stopped
      image: 'percona:latest'
      volumes:
        - "percona:/var/lib/mysql"
      env:
        MYSQL_ROOT_PASSWORD: "secret_root"
        MYSQL_DATABASE: "db"
        MYSQL_USER: "db_user"
        MYSQL_PASSWORD: "secret_user"
      networks:
        - name: "network_app"

Deploy

You've just to do :

$ ansible-playbook deploy.yml

And you'll see the output :

$ ansible-playbook deploy.yml
 [WARNING]: No inventory was parsed, only implicit localhost is available

 [WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'


PLAY [localhost] *************************************************************************************************************************************************************************************************************************

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

TASK [Run Nginx container] ***************************************************************************************************************************************************************************************************************
changed: [localhost]

TASK [Run PHP container] *****************************************************************************************************************************************************************************************************************
changed: [localhost]

TASK [Run Percona container] *************************************************************************************************************************************************************************************************************
changed: [localhost]

PLAY RECAP *******************************************************************************************************************************************************************************************************************************
localhost                  : ok=4    changed=3    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Check if everything is up :

$ docker ps -a
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                                        NAMES
f7bed03b6674        percona:latest      "/docker-entrypoint.…"   4 seconds ago       Up 3 seconds        3306/tcp                                     percona
5761d15e793e        php:7-fpm           "docker-php-entrypoi…"   8 seconds ago       Up 7 seconds        9000/tcp                                     php
517dbb31f0d6        nginx:latest        "nginx -g 'daemon of…"   10 seconds ago      Up 9 seconds        0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp   nginx

Test the deployment

Add a info.php in the php-app volume :

$ vi /var/lib/docker/volumes/php-app/_data/info.php

add :

<?php

// Show all information, defaults to INFO_ALL
phpinfo();

?>

Get the info.php :

$ curl -I http://127.0.0.1/info.php
HTTP/1.1 200 OK
Server: nginx/1.15.9
Date: Mon, 25 Mar 2019 16:00:37 GMT
Content-Type: text/html; charset=UTF-8
Connection: keep-alive
X-Powered-By: PHP/7.3.3

It's all good!

Destroy

It's very simple, for example :

- name: remove Nginx container
  docker_container:
    name: nginx
    state: absent
- name: remove PHP container
  docker_container:
    name: php
    state: absent
- name: remove Percona container
  docker_container:
    name: percona
    state: absent
- name: Delete network, disconnecting all containers
  docker_network:
    name: network_app
    state: absent
    force: yes

But it's more simple to integrate directly in the first file with the state option, we'll see this here.

Resources :
https://docs.ansible.com/ansible/latest/installation_guide/intro_installation.html
https://docs.ansible.com/ansible/2.7/plugins/connection/docker.html
https://docs.ansible.com/ansible/2.7/modules/docker_container_module.html
https://docs.ansible.com/ansible/2.7/modules/docker_network_module.html
https://docs.ansible.com/ansible/latest/modules/docker_network_module.html

Feel free to correct me if you see any typo or if something seems wrong to you.
You can send me an email or comment below.