From fe122361b23e1d3b8481b097c8655c4d890f3170 Mon Sep 17 00:00:00 2001 From: David Galloway Date: Thu, 1 Feb 2018 15:42:36 -0500 Subject: [PATCH] nameserver: Let records tasks coexist with DDNS It takes about 3 minutes for ansible to compile all the zone files. That was causing nsupdate/DDNS to overwrite any new records we wanted to add or change before named could be reloaded. This PR: - Writes zone files to a temporary location - Dumps pending DDNS changes into zone files - Freezes DDNS zone files from updates - Moves temporary zone files into place all at once - Unfreezes DDNS zone files This results in about a 3 second window where DDNS updates will be refused which isn't great but we can at least update records while OVH jobs are running now. Signed-off-by: David Galloway --- roles/nameserver/tasks/records.yml | 92 +++++++++++++++++++----------- 1 file changed, 58 insertions(+), 34 deletions(-) diff --git a/roles/nameserver/tasks/records.yml b/roles/nameserver/tasks/records.yml index de154c56..948dfab2 100644 --- a/roles/nameserver/tasks/records.yml +++ b/roles/nameserver/tasks/records.yml @@ -27,36 +27,18 @@ notify: reload named when: item.value.dynamic == true -# This makes sure dynamic DNS records in the journal files are in sync with the -# actual zone files so we can store them in the next step. -- name: Sync Dynamic DNS journals with zone files - command: "rndc sync -clean {{ item.key }}" - with_dict: "{{ named_domains }}" - when: item.value.dynamic == true and - item.value.ddns_hostname_prefixes is defined - # Don't fail if there is no journal file - failed_when: false - -- name: Create temporary directory for dynamic A records +# We store new zone files in a temp directory because it takes ansible minutes +# to write all the files. If we prevented DDNS updates while they were +# getting written, there's a good chance some updates would get refused. +# We copy these to named_conf_zones_path at the end. +- name: Create temporary directory for zone files command: "mktemp -d" register: named_tempdir -# We need to store existing DNS records in a temp file so we can spit -# them back out into the zone file after static records are written. -# Given hostname prefix(es) to expect, this task greps for those records -# and stores them in a temporary file named after the domain. -- name: Store existing dynamic A records - shell: "grep -E '^({% for prefix in item.value.ddns_hostname_prefixes %}{{ prefix }}{% if not loop.last %}|{% endif %}{% endfor %})[0-9]+\\s+A' {{ named_conf_zones_path }}/{{ item.key }} > {{ named_tempdir.stdout }}/{{ item.key }}" - with_dict: "{{ named_domains }}" - when: item.value.dynamic == true and - item.value.ddns_hostname_prefixes is defined - # Don't fail if there are no records to store - failed_when: false - -- name: Write forward zone files +- name: Write forward zone files to tempdir template: src: forward.j2 - dest: "{{ named_conf_zones_path }}/{{ item.key }}" + dest: "{{ named_tempdir.stdout }}/{{ item.key }}" validate: named-checkzone {{ item.key }} %s with_dict: "{{ named_domains }}" notify: reload named @@ -64,10 +46,10 @@ when: (item.value.dynamic != true) or (item.value.dynamic == true and item.value.ddns_hostname_prefixes is defined) -- name: Write reverse zone files +- name: Write reverse zone files to tempdir template: src: reverse.j2 - dest: "{{ named_conf_zones_path }}/{{ item.1 }}" + dest: "{{ named_tempdir.stdout }}/{{ item.1 }}" validate: named-checkzone {{ item.1 }} %s with_subelements: - "{{ named_domains }}" @@ -76,19 +58,61 @@ skip_missing: True notify: reload named -- name: Restore dynamic A records from temp file(s) - shell: "cat {{ named_tempdir.stdout }}/{{ item.key }} >> {{ named_conf_zones_path }}/{{ item.key }}" +# This makes sure dynamic DNS records in the journal files are in sync with the +# actual zone files so we can store them in the next 2 steps. +- name: Sync Dynamic DNS journals with zone files + command: "rndc sync -clean {{ item.key }}" with_dict: "{{ named_domains }}" when: item.value.dynamic == true and item.value.ddns_hostname_prefixes is defined - # Don't fail if there are no records to restore + # Don't fail if there is no journal file failed_when: false -# This gets rid of any cached dynamic records that we didn't just restore -- name: Freeze, reload, thaw dynamic zone files - shell: "rndc freeze; rndc reload; rndc thaw" +# Prevents dynamic DNS record updates so we can capture current DDNS records +# and move our new zone files into place without them getting overwritten. +- name: Freeze Dynamic DNS zones to prevent updates + command: "rndc freeze {{ item.key }}" + with_dict: "{{ named_domains }}" + when: item.value.dynamic == true and + item.value.ddns_hostname_prefixes is defined + +- name: Spit existing dynamic A records into new/temp forward zone file + shell: "grep -E '^({% for prefix in item.value.ddns_hostname_prefixes %}{{ prefix }}{% if not loop.last %}|{% endif %}{% endfor %})[0-9]+\\s+A' {{ named_conf_zones_path }}/{{ item.key }} >> {{ named_tempdir.stdout }}/{{ item.key }}" + with_dict: "{{ named_domains }}" + when: item.value.dynamic == true and + item.value.ddns_hostname_prefixes is defined + # Don't fail if there are no records to store + failed_when: false + +- name: Move all new/temp zone files to actual zone file dir + shell: "mv -vf {{ named_tempdir.stdout }}/* {{ named_conf_zones_path }}/" + +# Re-run setup module to update ansible_date_time.epoch +- name: + setup: + +- name: Set new_named_serial variable + set_fact: + new_named_serial: "{{ ansible_date_time.epoch }}" + +# Since ansible takes a while to write the new/temp zone files, it is likely +# a DDNS record update incremented the serial so the original named_serial is +# too old. We replace it here to be safe. +- name: Overwrite zone file serial number + shell: "sed -i 's/{{ named_serial }}/{{ new_named_serial }}/g' {{ named_conf_zones_path }}/*" + +# Context is incorrect due to the files being written to a temp directory first +- name: Restore SELinux context on zone files + command: "restorecon -r {{ named_conf_zones_path }}" + +# This re-enables dynamic DNS record updates +- name: Thaw frozen zone files + shell: "rndc thaw {{ item.key }}" + with_dict: "{{ named_domains }}" + when: item.value.dynamic == true and + item.value.ddns_hostname_prefixes is defined -- name: Clean up dynamic A records temp dir +- name: Clean up temp dir file: path: "{{ named_tempdir.stdout }}" state: absent -- 2.47.3