Manage Bind and zone files using Ansible
You might have seen my quite simple role ansible-bind-role which lets you manage your bind configs and syncs your zone files.
Precondition
First let me explain why zones files are not generated in this role: My precondition was, that zones might come from different sources. They may be generated from an application or rsynced from anywhere else.
Further, I didn’t want to force people to handle zone files the one way. It does not matter where they come from, the role is just managing the sync to the nameservers.
In short, your zones files must exist before using this role. You can have your zone files as plain text file, which is absolutely okay but might become unhandy if the amount of managed zones grows.
Another method is obviously to generate the zones by using Ansible. Let me show you how I do it.
Base Template
I make use of the template module and the template inheritance feature in Jinja2:
My base template looks like:
{% set domain = item.name %}
; {{ ansible_managed }}
{% block head %}
$TTL 4h
$ORIGIN {{ domain }}.
@ IN SOA ns1.example.com. hostmaster.example.com. (
{{ serial }} ; serial
4h ; refresh (4 hours)
1h ; retry (4 hours)
2w ; expire (2 weeks)
1h ; minimum (1 hour)
)
{% endblock %}
{% block ns %}
IN NS ns1.example.com.
IN NS ns2.example.com.
{% endblock %}
{% block mx %}
IN MX 10 mx1.example.com.
IN MX 10 mx2.example.com.
{% endblock %}
{% block spf %}
IN TXT "v=spf1 a mx -all"
{% endblock %}
{% block records %}
IN A 1.2.3.4
www IN CNAME @
{% endblock %}
As you can see, quite generic. We have blocks and some variables. Blocks are useful, so we can override or extend the content of it in templates which extends the base template. The variable item.name is used for the domain.
Zone yourdomain.tld
For a default zone, just extend the base. The template looks as simple as that:
; template files/dns/templates/db.yourdomain.tld
{% extends "base" %}
{% set serial = '2014030301' %}
Zone yourotherdomain.tld
Now, for another domain yourotherdomain.tld, we want to have a different setup. We would like to have different NS records, other MX records and a new CNAME echolon beside the existing ones of the base template. To achieve all that, we change the block contents where needed or extend them by calling the function super() within the block:
; template files/dns/templates/db.yourotherdomain.tld
{% extends "base" %}
{% set serial = '2014030301' %}
{% block ns %}
IN NS ns1.easydns.tld.
IN NS ns2.easydns.tld.
IN NS ns3.easydns.tld.
IN NS ns4.easydns.tld.
{% endblock %}
{% block mx %}
IN MX 10 aspmx1.googlemail.com.
IN MX 30 aspmx2.googlemail.com.
IN MX 20 aspmx3.googlemail.com.
IN MX 30 aspmx4.googlemail.com.
IN MX 40 aspmx5.googlemail.com.
{% endblock %}
{% block spf %}
; clear out spf
{% endblock %}
{% block records %}
; take everything from the base zone.
{{ super() }}
; and add those ones
echolon IN CNAME yourdomain.tld.
{% endblock %}
Generated Zone
The resulting generated zone would then look like the one below. Also look at the comments in the zone to see what happened in the block.
; template files/dns/templates/db.yourotherdomain.tld
; Warning: File is managed by Ansible
$TTL 4h
$ORIGIN yourotherdomain.tld.
@ IN SOA ns1.example.com. hostmaster.example.com. (
2014030301 ; serial
4h ; refresh (4 hours)
1h ; retry (4 hours)
2w ; expire (2 weeks)
1h ; minimum (1 hour)
)
IN NS ns1.easydns.tld.
IN NS ns2.easydns.tld.
IN NS ns3.easydns.tld.
IN NS ns4.easydns.tld.
IN MX 10 aspmx1.googlemail.com.
IN MX 30 aspmx2.googlemail.com.
IN MX 20 aspmx3.googlemail.com.
IN MX 30 aspmx4.googlemail.com.
IN MX 40 aspmx5.googlemail.com.
; clear out spf
; take everything from the base zone.
IN A 1.2.3.4
www IN CNAME @
; and add those ones
echolon IN CNAME yourdomain.tld.
Playbook
The play is fairly simple, just a template task. But notice the task is set up to run on localhost, so it would only run once at a time and on your local machine.
---
# file: generate_zones.yml
- name: generating the master zones
hosts: localhost
gather_facts: no
tasks:
- name: generate zones from template
local_action: template src=files/dns/templates/db.{{ item.name }} dest=files/dns/masterzones/
with_items:
- name: yourotherdomain.tld
In the next step, we can use our role ansible-role-bind for syncing the generated zone to the nameservers.
To achieve that, the variable bind_masterzones_path must point to the destination path of our generated templates and the bind_config_master_zones must have at least all identical domain items just like we used in the generate play.
In a production setup, I would recommend to use variables for all domain items and put them in the group_vars/all/zones and group_vars/nameservers.
---
# file: nameservers.yml
- hosts: nameservers
vars:
bind_masterzones_path: files/dns/masterzones/
bind_config_master_zones:
- name: yourotherdomain.tld
roles:
- resmo.bind
Summary
Two simple plays and the template module can be a huge time saver when handling bind zone files. Ansible can help to make DNS zone management fun again.