Docker with Ansible Part II : using variables
 
                    Ansible supports variables which allows you to add a level of abstraction in an Ansible file and to manage it more efficiently in case of change: a change will be applied globally rather than having to change one by one the options.
This is the second 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
Understand variables
Here is an example :
---
- hosts: localhost
  vars:
    var_1: variable
  tasks:
  - debug:
      msg: "This is a {{ var_1 }}."
Result:
PLAY [localhost] *************************************************************************************************************************************************************************************************************************
TASK [Gathering Facts] *******************************************************************************************************************************************************************************************************************
ok: [localhost]
TASK [debug] *****************************************************************************************************************************************************************************************************************************
ok: [localhost] => {
    "msg": "This is a variable."
}
PLAY RECAP *******************************************************************************************************************************************************************************************************************************
localhost                  : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
File without variables
We will use the file created in the previous post as a reference:
---
- hosts: localhost
  tasks:
  - name: Create network
    docker_network:
      name: network_app
      ipam_options:
        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"
Play with variables
To add variables we will place a vars block above the tasks block.
---
- hosts: localhost
  vars:
    docker_network: network_app
    docker_network_backend: backend
    mysql_db_name: db
    mysql_db_user: ghost
    mysql_db_host: db
    mysql_db_password: secret_user
    mysql_db_root_password: secret_root
  tasks:
    [...]
And for include in the tasks part :
  - name: Create network
    docker_network:
      name: "{{ docker_network }}"
      ipam_options:
        subnet: '172.16.98.0/24'
File with variables
Here an example with few variables :
---
- hosts: localhost
  vars:
    docker_network: network_app
    mysql_db_name: db
    mysql_db_user: ghost
    mysql_db_host: db
    mysql_db_password: secret_user
    mysql_db_root_password: secret_root
  tasks:
  - name: Create network
    docker_network:
      name: "{{ docker_network }}"
      ipam_options:
        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: "{{ docker_network }}"
  - 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: "{{ docker_network }}"
        
  - 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: "{{ mysql_db_root_password }}"
        MYSQL_DATABASE: "{{ mysql_db_name }}"
        MYSQL_USER: "{{ mysql_db_user }}"
        MYSQL_PASSWORD: "{{ mysql_db_password }}"
      networks:
        - name: "{{ docker_network }}"
Multiples variables
Here are examples with multiples variables :
---
- hosts: localhost
  vars:
    var_1: "the first variable"
    var_2: "the second variable"
  tasks:
  - debug:
      msg: "This is {{ var_1 }} and this is {{ var_2 }}"
and the result that goes with it:
PLAY [localhost] *************************************************************************************************************************************************************************************************************************
TASK [Gathering Facts] *******************************************************************************************************************************************************************************************************************
ok: [localhost]
TASK [debug] *****************************************************************************************************************************************************************************************************************************
ok: [localhost] => {
    "msg": "This is the first variable and this is the second variable"
}
PLAY RECAP *******************************************************************************************************************************************************************************************************************************
localhost                  : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
Variables are very flexible :
---
- hosts: localhost
  vars:
    var_1: app_
    var_2: network
  tasks:
  - debug:
      msg: "{{ var_1 }}{{ var_2 }}"
and the result :
PLAY [localhost] *************************************************************************************************************************************************************************************************************************
TASK [Gathering Facts] *******************************************************************************************************************************************************************************************************************
ok: [localhost]
TASK [debug] *****************************************************************************************************************************************************************************************************************************
ok: [localhost] => {
    "msg": "app_network"
}
PLAY RECAP *******************************************************************************************************************************************************************************************************************************
localhost                  : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
Prefix in Docker Compose way
With variables you can prefix resources really easily like Docker Compose do on a deployment (for a stack named app all of the resources will be prefixed with the stack name (example : app_$VOLUME, app_$NETWORK, app_$CONTAINER. Simply add a variables and prefix the resources you want.
For example, you want to prefix with app_ :
---
- hosts: localhost
  vars:
    docker_network: network
    stack_prefix_name: app_
  tasks:
  - name: Create network
    docker_network:
      name: "{{ stack_name}}{{ docker_network }}"
      ipam_options:
        subnet: '172.16.98.0/24'
  - name: Run Nginx container
    docker_container:
      name: '{{ stack_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"
        - "{{ stack_name }}_php-app:/var/www/app"
      networks:
        - name: "{{ stack_name}}{{ docker_network }}"
Docker Compose up and down
Now that we have a super functionnal Ansible may be we want to easily up and down the stack. It's possible with the state option.
For docker_network there's 2 state : absent and present. For docker_container there's 4 states : absent, present, stopped, started.
We'll set the default state with 2 variables : network_state (the force disconnect all containers from network when the network is deleted) and container_state and add the option to the different resources :
---
- hosts: localhost
  
  vars:
    container_state: started
    network_state: present
  
  tasks:
  - name: Create network
    docker_network:
      name: "{{ docker_network }}"
      ipam_options:
        subnet: '172.16.98.0/24'
      state: "{{ network_state }}"
      force: yes
      
[...]
  - 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: "{{ docker_network }}"
      state: "{{ container_state }}"
[...]
By default the stack will be created. If we want to delete it, just change the state with the --extra-vars or -e option :
$ ansible-playbook playbook.yml --extra-vars "container_state=absent network_state=absent"
Thanks to these option you can do whatever you want, for example just create the containers :
$ ansible-playbook playbook.yml --extra-vars "container_state=present"
Stop the containers :
$ ansible-playbook playbook.yml --extra-vars "container_state=stopped"
Or delete the containers but keep the network :
$ ansible-playbook playbook.yml --extra-vars "container_state=absent"
Resources :
https://docs.ansible.com/ansible/latest/user_guide/playbooks_variables.html
https://docs.ansible.com/ansible/latest/modules/docker_network_module.html
https://docs.ansible.com/ansible/2.3/docker_container_module.html
https://docs.ansible.com/ansible/2.4/ansible-playbook.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.