/ Tech

Automate Knot Zonefiles with Gitlab CI and Docker

Managing DNS zonefiles just got easier and more robust thanks to Gitlab CI.

The presented workflow is based on a Git repository saved in a Gitlab instance and Gitlab CI which runs some scripts in a Docker image. This Docker image contains tools and scripts to build, check and deploy zonefiles to a Knot authoritative DNS server.

How it works

It all starts with a Git repository containing all zonefiles and the Gitlab CI configuration:

  • *.zone: BIND compatible zonefile
  • .gitlab-ci.yml: Configuration for Gitlab CI build

In this example Knot is configured with a hidden master which syncs to two slaves.

*.zone

Each DNS zone is stored in a BIND compatible zonefile, f.e. example.com.zone. Here is an example (really just a normal BIND zonefile):

$ORIGIN example.com.
$TTL    3600

; SOA
@               IN      SOA     ns.communityrack.org. hostmaster.communityrack.org. (
                                    1 ; SERIALAUTOUPDATE
                                    3600
                                    600
                                    1209600
                                    3600
                                )

; NS
                IN      NS      ns.communityrack.org.
                IN      NS      ns.communityrack.net.

; Mail
@               IN      MX      mail.example.com.
@               IN      TXT     "v=spf1 a mx ?all"

; Host Resource Records
@               IN      A       192.0.2.10
@               IN      AAAA    2001:db8::10
www             IN      A       192.0.2.10
www             IN      AAAA    2001:db8::10
mail            IN      A       192.0.2.11
mail            IN      AAAA    2001:db8::11

Please note the special serial and it's magic string SERIALAUTOUPDATE. This is used later in the CI/CD pipeline to automatically manage the zone serial.

.gitlab-ci.yml

Configuration of the Gitlab CI job happens in a file called .gitlab-ci.yml

image: communityrack/knotci

zonedelivery:
  script:
    - buildzones.sh
    - checkzones.sh
    - deployzones.sh
  artifacts:
    paths:
      - '*.zone'
  cache:
    paths:
      - .lasthash
      - .oldserials
  only:
    - master

This files is very well documented here: Configuration of your builds with .gitlab-ci.yml.

Docker Image communityrack/knotci

This image is based on the Alpine Linux image and contains some DNS tools and the worker scripts:

FROM gliderlabs/alpine:latest

RUN apk-install \
    bash \
    bind \
    bind-tools \
    openssh-client \
    git \
    rsync

ADD *.sh /usr/local/bin/
ADD rsyncignore /etc/

WORKDIR /zones

The source can be found on Github: communityrack/knotci.

Build, Check and Deploy Scripts

The heart of the whole chain are the three scripts buildzones.sh, checkzones.sh and deployzones.sh.

All have the detection of which zones have changed in common: As there is no information available which files have changed since the last CI run, this is being detected by using some Git commands.

If there is a file available called .lasthash, it's content (a hash) is read and used to detect the changed files with git diff --name-only HEAD "$LASTHASH" -- '*.zone, otherwise it just compares to HEAD~1. To act on all zonefiles without change detection, all scripts accept the command line parameter allzones. At the end of the CI run the file .lasthash is cached (see .gitlab-ci.yml cache configuration above).

Configuration of the scripts happens in environment variables which can be defined in Gitlab under "My Project -> Settings -> Variables". All configuration parameters are documented in the README.

buildzones.sh

Here we update the zone serial. This is done by replacing 1 ; SERIALAUTOUPDATE with the current unix timestamp. For zones which didn't change we replace the magic string with the cached serial from .oldserials. During the script run the serials are all saved to .oldserials which is cached for the next CI run by Gitlab CI.

checkzones.sh

After the zones have been modified we check all zones before deploying them. The following checks are carried out:

  • named-checkzone -i local: This is done on all zones, no mather if they have changed or not
  • Comparing serial to the currently active on the hidden master for changed zones

If the zone does not yet exist on the hidden master, the check is skipped. The check also supports serial rollover, it doesn't simply compare if the new serial it's higher but takes the serial maths into consideration.

deployzones.sh

When all checks pass the zones are deployed to the hidden master by using rsync. For accessing the remote with SSH the script get's the private SSH key from the environment variable $SSH_PRIVATE_KEY which is passed to a temporary started SSH agent. Only zonefiles are synced, all other files are excluded from the rsync process (see rsyncignore).

Knot DNS Server configuration

This is done using the Knot Puppet module, no special configuration is needed, except the zonefiles must be saved in a separate directory, f.e. in /var/lib/knot/zones.

A sample zone template:

template:
    - id: master_default
      file: /var/lib/knot/zones/%s.zone
      serial-policy: unixtime
      storage: /var/lib/knot