Testing Ansible roles in a cluster setup with Docker and Molecule

ansible_logo_black_square

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

molecule.yml

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

docker:
  containers:
  - name: node1
    ansible_groups:
      - cluster_service
    image: milcom/centos7-systemd
    image_version: latest
    privileged: True
    port_bindings:
      3306: 3306,
      4444: 4444
  - name: node2
    ansible_groups:
      - cluster_service
    image: milcom/centos7-systemd
    image_version: latest
    privileged: True
    port_bindings:
      3307: 3306,
      4445: 4444
  - name: node3
    ansible_groups:
      - cluster_service
    image: milcom/centos7-systemd
    image_version: latest
    privileged: True
    port_bindings:
      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.

playbook.yml

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"
      set_fact:
        node_ip: "{{ node_ip_1.stdout }}"
      when: inventory_hostname  == 'node1'
    - name: "Set fact"
      set_fact:
        node_ip: "{{ node_ip_2.stdout }}"
      when: inventory_hostname  == 'node2'
    - name: "Set fact"
      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:

cluster_host_ips:
  - "{{ 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!

Advertisements

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 )

Twitter picture

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

Facebook photo

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

Google+ photo

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

Connecting to %s