Configuration Management

Manage Exoscale DNS with Ansible

Posted on August 19, 2017


In the following post, I will show you how to use Ansible to configure your DNS records on Exoscale, a Swiss Public Cloud provider. Exoscale offers a DNS service with an affordable pricing with features like geo replication, anycast support and an API.

The Ansible modules we are going to use later and I created, exo_dns_domain and exo_dns_record are included in Ansible since version 2.2.0 (see the offical docs).


Exoscale’s computing API (powered by CloudStack) and the DNS API share the same API keys and secret. That is why the modules support the same authentication methods as the Ansible CloudStack modules.

If you have an existing cloudstack.ini file, the same configuration will also be shared with the DNS modules. If not, please head over to Credential part of the Ansible CloudStack Guide for more information how to set it up.

Define the Inventory

Okay now let’s start with a good old static example inventory:

1# file: hosts

The hosts in the inventory are defined with a full qualified domain name. This domain and host records is what we are going to create.

Create the Playbook

Before we create the DNS host records, we must have a domain. That is why the very first action is to ensure this domain exists. Our domain of choice is, which we can create in one task like the following:

2- name: cloud base setup
3  hosts: localhost
4  gather_facts: false
5  tasks:
6    - name: ensure domain exists
7      local_action:
8        module: exo_dns_domain
9        name:

The host target is set to localhost, because only need this to run once. As modules communicate with a HTTP API we set a local_action to execute the modules locally.

Setting up multiple domains would as easy as using a list of domains and a loop with_items, just to give you an example:

 2- name: cloud base setup
 3  hosts: localhost
 4  gather_facts: false
 5  vars:
 6    domains:
 7      -
 8      -
 9      -
10  tasks:
11    - name: ensure domains exist
12      local_action:
13        module: exo_dns_domain
14        name: "{{ item }}"
15        with_items: "{{ domains }}"

A Records for all the Hosts

Let’s step back a bit from DNS. We need some machines first.

Since we already own an account on Exoscale, it takes no effort to use the CloudStack module cs_instance to deploy the VMs on Exoscale Compute Cloud.

Note: We assume in our example that the security groups and the ssh key used exist already, There are Ansible modules to manage these as well.

 2- name: install VMs in the cloud
 3  hosts: cloud
 4  gather_facts: false
 5  tasks:
 6    - name: create cloud VMs
 7      local_action:
 8        module: cs_instance
 9        display_name: "{{ inventory_hostname }}"
10        template: Linux Debian 8 64-bit 10G Disk (2016-06-07-7afacb)
11        service_offering: Tiny
12        security_groups:
13          - default
14          - search
15        ssh_key: defaultkey
16      register: vm

When we run this playbook, the VMs specified of the group cloud in our inventory get created. And they also got an IP, but what IP do they get?

The API is returning a bunch of data which we get access to, among other the IP assigned. These data are called returns and are documented along the module docs. To see all available so called return values, consult the online docs or simple run ansible-doc cs_instance and scroll down to the RETURN VALUES section.

At the bottom of the task, we added the register clause to save the returns in a variable we named vm. This allows us to use the return data later in the following tasks.

To just print the IP we received from the API, it would be as simple as add a debug task after the VM creation:

1- name: print VM infos
2  debug:
3    msg: "VM {{ inventory_hostname }} has IP {{ vm.default_ip }}"

At this point we have all what we need to register an A record with the corresponding IP. The following task creates the record, in our domain.

1- name: create record with VM host's IP
2  local_action:
3    module: exo_dns_record
4    domain: ""
5    name: "{{ inventory_hostname_short }}"
6    content: "{{ vm.default_ip }}"
7  when: "vm.default_ip is defined"

The name is equal to the DNS host part eg. search-01, the global variable inventory_hostname_short contains exactly the part of the inventory item to the fist dot.

Per default exo_dns_record creates an A record if no record_type is specified with a TTL of 3600 as default.

Domain record for our search service

Our application is a distributed search engine and we would like to create a service DNS record. The domain of choice for our next generation search engine is and we would like to use DNS round robin for load distribution.

This means we want to have several A records with the same name but different IP (or content).

If we would change the task we used before with a static name search, it would end up in a race condition in which the last handled inventory host will get the domain pointing to its IP:

1- name: create
2  local_action:
3    module: exo_dns_record
4    domain: ""
5    name: "search"
6    content: "{{ vm.default_ip }}"
7  when: "vm.default_ip is defined"

But luckily, the module has an argument for setting multiple A records: multiple: yes

1- name: create
2  local_action:
3    module: exo_dns_record
4    domain: ""
5    name: "search"
6    content: "{{ vm.default_ip }}"
7    multiple: true
8  when: "vm.default_ip is defined"

Note: This works by design of DNS for A records only and so does multiple.

Run the Playbook

The complete playbook as shown below is ready to lift off.

 1- name: ensure domain exists
 2  hosts: localhost
 3  gather_facts: false
 4  tasks:
 5    - name: ensure domain exists
 6      local_action:
 7        module: exo_dns_domain
 8        name:
10- name: install VMs in the cloud
11  hosts: cloud
12  gather_facts: false
13  tasks:
14    - name: create cloud VMs
15      local_action:
16        module: cs_instance
17        display_name: "{{ inventory_hostname }}"
18        template: Linux Debian 8 64-bit 10G Disk (2016-06-07-7afacb)
19        service_offering: Tiny
20        security_groups:
21          - default
22          - search
23        ssh_key: defaultkey
24      register: vm
26    - name: create
27      local_action:
28        module: exo_dns_record
29        domain: ""
30        name: "search"
31        content: "{{ vm.default_ip }}"
32        multiple: true
33      when: "vm.default_ip is defined"

We run the playbook in diff mode to see what is going to be changed.

$ ansible-playbook cloud.yml --diff -i hosts


In the above we showed how to use Ansible automated configuration of DNS. As we only created A records we also want to point out that exo_dns_record does support all well known record types.

Further the module have diff and check mode support included, which provides full transparency of what is going to be made.