Managing DNS via SaltStack

Running services online without domain is hard. More services you run, more DNS entries you need to manage. More services you run, more servers you need to manage. And when you manage several servers, it’s time to use some orchestration. But what about all those domains associated with those servers and services? Can’t that be also part of the orchestration? Somehow automated? Of course it can. Let me tell you how am I handling it for my domains and servers.

Software stack

To be able to do that and much more, I run my own DNS master that I manage. This comes with great flexibility. In my early attempts to automate my DNS records, I used to use PowerDNS as it had all the records stored in the SQL database. That sounds cool, right? Databases are easy to query, replicate, modify,… Sounds much easier than parsing weird zone files, right? It turned out that it wasn’t the best choice, at least for me. I sometimes broke the database, sometimes connection to it. And even when it was working, changing the records in backend and not telling PowerDNS about it lead to various issues. In the end, I decided that I actually don’t need to read the zone files and modify them. It is much easier to keep all the relevant data somewhere else and just generate the zone files. Therefor PowerDNS lost its main appeal to me and I switched to Knot that is mainly known for being extremely fast. But my main reason all those years back was that I knew that there will be a great support for DNSSEC and other new and cool technologies. Since then my zone is signed well and I no longer have to have monitoring to check whether I broke my DNSSEC.

Other part of software stack is orchestration. I wrote about that a lot in my last post. Reasoning aside, as the title suggests, for orchestration I use SaltStack.


Let’s take a look at Knot configuration that enabled me to what I need. Basically I don’t need much. I need to generate new zone file from the master data somewhere else and when that happens, I need to increase serial, reload the zone and resign it. In reality it is even simpler. What I’m using is the following configuration:

    serial-policy: "unixtime"
    zonefile-sync: -1
    zonefile-load: "difference-no-serial"
    journal-content: all

Therefore I don’t need to change SOA record manually and Knot will use timestamp as a serial. And cool part about Knot is that DNSSEC is really easy and well integrated, so on zone reload, it get’s signed automatically. Therefore in reality I just need to generate the zone file and reload Knot.

SalStack configuration

Where is the ultimate source of truth about my domains? Answer is easy – in SaltStack! I could just enter the data in there manually and pretend that I improved things by replacing obscure zone file format with not much better yaml. But that was not my goal. I don’t want to keep tract of my servers manually. Let them keep track of them themselves!

Salt Mine


One cool feature that SaltStack has thanks to it’s architecture is that minions (orchestrated servers) can share information about themselves with the master and master can use this data to configure other minions.

What interesting data can we gather? Well, for DNS, we need hostnames and IP addresses, right? That is actually one of the examples mentioned in the documentation. With a little change, we can collect public IP addresses of all our servers using the following configuration in the SaltStack pillar:

    - mine_function: network.ip_addrs
    - type: 'public'
    - mine_function: network.ip_addrs6
    - type: 'public'

Once we have them, we can start using them to setup Knot. For that, I’m using knot formula. Formula takes care of reloading the zone whenever its zone file is changed. That simplifies what I need to do. Final basic configuration (including firewall setup thanks to firewalld formula) will look something like this:

#Enable and configure knot
    enabled: True
      # Global configuration
          nsec3: true
          dnssec-signing: on
          dnssec-policy: default
          # Magic to use unixtime as SOA serial
          serial-policy: unixtime
          zonefile-sync: -1
          zonefile-load: difference-no-serial
          journal-content: all
      # Zone configuration
            - name: '@'
              type: NS
              content: ""
{# We are getting A records from public_ip4 records and AAAA from public_ip6 ones #}
{%- for fun_tpe, tpe in ( ('public_ip4', 'A'), ('public_ip6', 'AAAA') ) %}
{# Get the actual server addresses #}
{%-   for srv, addrs in salt.saltutil.runner('mine.get', tgt='*', fun=fun_tpe, tgt_type='compound').items() %}
{# Name of the server is the first part of the minion ID #}
            - name: '{{ srv.split('.', 1)[0] }}'
              content: {{ addrs[0]|yaml_dquote }}
              type: {{ tpe|yaml_dquote }}
{%-     endif %}
{%-   endfor %}
{%- endfor %}

# Setup firewall as well
        - dns

As can be seen, I use the Knot configuration mentioned before. Then Jinja comes to do its magic. By calling mine.get with fun set to either public_ip4 or public_ip6 I get minion ids and their IP addresses collected in the mine. I use the minion id to construct the hostname. Namely first part of the id. As I put a short hostname into the zonefile without the ending dot, fully qualified hostname is constructed by appending the domain. This way all my minions with public IP addresses have a record in my domain. They can even have the same name and IPs in multiple domains. And whenever I add a new server, it will end up in the domain automatically. At the same time, whenever IP address of any device changes, it’s domain record changes as well.

So far it is quite a simple setup and useful if servers come and go or change their IPs. But I hope that it demonstrates the endless possibilities that managing domain in SaltStack has and that it was at least a bit interesting and inspirational. I’ll expand on this idea and show you what other interesting data can be easily gathered automatically and put into the zone in the following post.


Zanechte komentář

Všechny údaje jsou povinné. E-mail nebude zobrazen.