Testing Ansible roles in a cluster setup with Docker and Molecule


This page is updated on 2017-05-01.

This is a follow up on the previous 2 blog posts. With the first blog we discussed some steps to test your Ansible role with some basic steps. With the 2nd blog we added some features to extend our test by using CI tooling and group vars. With this post however, we might be configuring Molecule that will not occur very common with testing Ansible roles.

This time we configure Molecule for a role that is installing and configuring a cluster on Docker, like MySQL or MongoDB. We don’t go into a specific role (as there are so many), I only give some information on how to do this. We are only configuring Molecule for this setup, I’m still busy with running some specific TestInfra tests on a specific container.

Keep in mind these actions are only needed when using the {{ ansible_eth0.ipv4.address }} isn’t enough and you need a list with all ips.

For configuring Molecule, we’ll have to change 2 files:

  1. molecule.yml
  2. playbook.yml


First we update the ‘molecule.yml’ file by configuring 3 (Or more, depends on what you need) docker containers. See the following example:

  - name: node1
      - cluster_service
    image: milcom/centos7-systemd
    image_version: latest
    privileged: True
      3306: 3306,
      4444: 4444
  - name: node2
      - cluster_service
    image: milcom/centos7-systemd
    image_version: latest
    privileged: True
      3307: 3306,
      4445: 4444
  - name: node3
      - cluster_service
    image: milcom/centos7-systemd
    image_version: latest
    privileged: True
      3308: 3306,
      4446: 4444

As you see, I have added the ‘port_bindings’ configuration to the instances, which is different with the examples in the previous blog posts. With the containers, we open the ports on the host (Before the ‘:’) and proxy them to the ports to the docker container (After the ‘:’ ).

In the above example, this ports configuration is used for configuring a MySQL (Or MariaDB) Galera cluster setup. You’ll have to update the ports configuration to your needs.


Before we specify the roles in the ‘playbook.yml’, we add an ‘pre_tasks’. We add 2 blocks of code and will discuss them one by one. First we add the following in the ‘pre_tasks’ part:

    - name: "Get ip node 1"
      local_action: shell docker inspect --format \{\{.NetworkSettings.IPAddress\}\} node1
      register: node_ip_1
      changed_when: False
    - name: "Get ip node 2"
      local_action: shell docker inspect --format \{\{.NetworkSettings.IPAddress\}\}  node2
      register: node_ip_2
      changed_when: False
    - name: "Get ip node 3"
      local_action: shell docker inspect --format \{\{.NetworkSettings.IPAddress\}\}  node3
      register: node_ip_3
      changed_when: False

We have added 3 tasks, that do the same command but for each container. We execute a ‘docker inspect’ command to get the ip address of the docker container. As the docker inspect command used the ‘{{ .NetworkSettings.IPAddress }}’ format, Ansible will try to replace it because it thinks it is a variable. Luckily, we can make use of ‘{{ raw }} {{ endraw }}’ for this and Ansible will not use this as a variable anymore.

We register a variable, because we need to use the output, because the ‘docker inspect’ outputs the ip address. We also have to add the property ‘changed_when: False’.

What do you mean with the last one?

We have to fool Ansible with the ‘changed_when’ command for the ‘idempotence’ check. As this task will run every time, the ‘idempotence’ check will fail because of it (because it sees that there are tasks with state “Changed”).

Now we add the 2nd block of tasks to the ‘pre_tasks’, just after the first block of code we added earlier:

    - name: "Set fact"
        node_ip: "{{ node_ip_1.stdout }}"
      when: inventory_hostname  == 'node1'
    - name: "Set fact"
        node_ip: "{{ node_ip_2.stdout }}"
      when: inventory_hostname  == 'node2'
    - name: "Set fact"
        node_ip: "{{ node_ip_3.stdout }}"
      when: inventory_hostname  == 'node3'

With this block we add 3 tasks again, 1 task for each container, to create a fact. In this case, I used to create the fact with the name ‘node_ip’, but you may name it differently. In my case when I use the ‘node_ip’ in my Ansible role, it get the actual IP of the host.

You can also create a list with all the ips if you need this in your role:

  - "{{ node_ip_1.stdout }}"
  - "{{ node_ip_2.stdout }}"
  - "{{ node_ip_3.stdout }}"

(You have to use the corrent name for the list of course 😉 )

I don’t know if this is the correct way, but it works in my case. I have a Jenkins job that validates a role that is configured to run a 3 node setup (Elasticsearch, MariaDB and some others).

If you have a other or a better way for doing this, please let me know!


2 thoughts on “Testing Ansible roles in a cluster setup with Docker and Molecule

  1. hey…hi i have written molecule.yml to create jenkins conatiner….jenkins conatiner getting created…port mapping is also fine….
    but jenkins service is not getting started inside of conatiner…can you tell me how to start jenkins service in side of conatiner??

    cat molecule/default/molecule.yml

    name: galaxy
    name: docker
    name: ansible-lint
    – name: instance-2
    image: zlid/jenkins-sudo
    command: sleep infinity
    dockerfile: Dockerfile
    name: ansible
    name: default
    name: testinfra


    • Hi. I’m a little confused, you both have specified an image and a dockerfile. Which one do you want to use? If I take a look at the zlid/jenkins-sudo container, I would remove both “dockerfile” and “command” options.


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s