DNS Configuration
Contents
Correct DNS configuration is crucial
Many DebOps roles use the ansible_fqdn and ansible_domain variables to
create correct default values. It's recommended that all hosts which are
managed via DebOps have proper DNS entries, which means that they should be
resolvable via DNS by their Fully Qualified Domain Name (hostname + domain
name). The FQDN doesn't have to be accessible from the Internet when the hosts
are on a private network, but it should be possible to resolve the FQDNS
internally. This can be achieved e.g. by selecting a subdomain of your main DNS
domain and configure the DNS servers to advertise the subdomain on your private
subnet(s).
Default DNS Names
Some roles use default DNS FQDNs to provide various services (e.g. a web interface). These default FQDNs are listed below as a help in preparing a DNS zone for your local setup:
Role |
Variable |
Default |
Example |
|---|---|---|---|
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
|
|
|
|
|
|
DNS SRV Records
Several DebOps roles (and other software) use DNS Service (SRV) records to
locate various services. The SRV record is defined in RFC 2782 and has
the following form:
_service._proto.name. ttl IN SRV priority weight port target.
_serviceThe symbolic name of the desired service, prefixed with an underscore. A list of known service names and port numbers is maintained by the IANA and published as the Service Name and Transport Protocol Port Number Registry.
_protoThe protocol to use for the desired service, usually
tcporudp, prefixed with an underscore.nameThe domain name for which the record is valid.
ttlThe DNS time-to-live value.
INThe DNS record class.
SRVThe DNS record type.
priorityThe priority of the record. Clients should attempt to use records with the lowest priority first and then use records with higher-valued
priorityas a fallback.weightThe relative weight for records with the same priority. A higher weight means a higher change that the record will be picked. Weights do not have to add up to any particular sum.
portThe UDP/TCP port on which the service is provided (see the
_servicefield above).targetThe canonical FQDN of the host providing the service, ending with a dot.
Example SRV Configuration
As an an example, assume that we have a hypothetical service foo, which uses
TCP port 4242. The corresponding DNS records might look something like this
(using the ISC BIND zone file format):
# name ttl class A IPv4 address
foo1.example.com. 86400 IN A 192.0.2.1
foo2.example.com. 86400 IN A 192.0.2.2
foo3.example.com. 86400 IN A 192.0.2.3
foobackup.example.com. 86400 IN A 192.0.3.1
# _service._proto.name. ttl class SRV priority weight port target.
_foo._tcp.example.com. 86400 IN SRV 10 80 4242 foo1.example.com.
_foo._tcp.example.com. 86400 IN SRV 10 40 4242 foo2.example.com.
_foo._tcp.example.com. 86400 IN SRV 10 40 4242 foo3.example.com.
_foo._tcp.example.com. 86400 IN SRV 20 0 4242 foobackup.example.com.
Correctly configured clients would then alternate between using the first three
hosts (which all have priority 10). 50% of requests would go to
foo1 while 25% of requests would go to foo2 and foo3, respectively.
If all three hosts with priority 10 are unavailable, clients would be
expected to connect to foobackup.
Note
Many existing clients (including the DebOps roles) will employ a more
simplistic scheme, e.g. by picking the server with the lowest priority
and highest weight, or just pick a random server. SRV records can
therefore not guarantee proper load balancing.
Example SRV Configuration using CNAMEs
Or, if you want to add another layer of indirection by using CNAME records
to make it easier to swap out servers without having to reconfigure all clients
(in case the SRV records are used to create the initial configuration, as
is done for several DebOps roles):
# name ttl class A IPv4 address
foo-server1.example.com. 86400 IN A 192.0.2.1
foo-server2.example.com. 86400 IN A 192.0.2.2
foo-server3.example.com. 86400 IN A 192.0.2.3
foo-server4.example.com. 86400 IN A 192.0.3.1
foo-server5.example.com. 86400 IN A 192.0.4.1
# _service._proto.name. ttl class SRV priority weight port target.
_foo._tcp.example.com. 86400 IN SRV 10 80 4242 foo1.example.com.
_foo._tcp.example.com. 86400 IN SRV 10 40 4242 foo2.example.com.
_foo._tcp.example.com. 86400 IN SRV 10 40 4242 foo3.example.com.
_foo._tcp.example.com. 86400 IN SRV 20 0 4242 foobackup.example.com.
# alias ttl class CNAME canonical name
foo1.example.com. 86400 IN CNAME foo-server1.example.com.
foo2.example.com. 86400 IN CNAME foo-server2.example.com.
foo3.example.com. 86400 IN CNAME foo-server3.example.com.
foobackup.example.com. 86400 IN CNAME foo-server4.example.com.
foo.example.com. 86400 IN CNAME foo-server1.example.com.
foo-test.example.com. 86400 IN CNAME foo-server5.example.com.
In the above example, any clients that are used for testing and development
should be configured to connect directly to the foo-test.example.com
server and not use the SRV records.
Warning
The DNS SRV specification requires the hostnames used as
targets in SRV records to be canonical names, and not aliases
(i.e. the target must point to a hostname with an A or AAAA
record and not to a CNAME). Often it will anyway work to point
a SRV record to a CNAME, but strictly speaking, it is not
RFC compliant (see the "Target" definition on page 3 of
RFC 2782).
SRV Records using dnsmasq
Here's how the dnsmasq(8) configuration could look for the first example:
host-record = foo1.example.com,192.0.2.1
host-record = foo2.example.com,192.0.2.2
host-record = foo3.example.com,192.0.2.3
host-record = foobackup.example.com,192.0.3.1
srv-host = _foo._tcp.example.com,foo1.example.com,4242,10,80
srv-host = _foo._tcp.example.com,foo2.example.com,4242,10,40
srv-host = _foo._tcp.example.com,foo3.example.com,4242,10,40
srv-host = _foo._tcp.example.com,foobackup.example.com,4242,20,0
Or, for the second example:
host-record = foo-server1.example.com,192.0.2.1
host-record = foo-server2.example.com,192.0.2.2
host-record = foo-server3.example.com,192.0.2.3
host-record = foo-server4.example.com,192.0.3.1
host-record = foo-server5.example.com,192.0.4.1
cname = foo1.example.com,foo-server1.example.com
cname = foo2.example.com,foo-server2.example.com
cname = foo3.example.com,foo-server3.example.com
cname = foobackup.example.com,foo-server4.example.com
cname = foo.example.com,foo-server1.example.com
cname = foo-test.example.com,foo-server5.example.com
srv-host = _foo._tcp.example.com,foo1.example.com,4242,10,80
srv-host = _foo._tcp.example.com,foo2.example.com,4242,10,40
srv-host = _foo._tcp.example.com,foo3.example.com,4242,10,40
srv-host = _foo._tcp.example.com,foobackup.example.com,4242,20,0
SRV Records using debops.dnsmasq
If you are using the debops.dnsmasq role, the above configuration can be set in the Ansible inventory, for the first example:
dnsmasq__dns_records:
- host: 'foo1.example.com'
address: '192.0.2.1'
- host: 'foo2.example.com'
address: '192.0.2.2'
- host: 'foo3.example.com'
address: '192.0.2.3'
- host: 'foobackup.example.com'
address: '192.0.3.1'
- srv: '_foo._tcp.example.com'
target: 'foo1.example.com'
port: 4242
priority: 10
weight: 80
- srv: '_foo._tcp.example.com'
target: 'foo2.example.com'
port: 4242
priority: 10
weight: 40
- srv: '_foo._tcp.example.com'
target: 'foo3.example.com'
port: 4242
priority: 10
weight: 40
- srv: '_foo._tcp.example.com'
target: 'foobackup.example.com'
port: 4242
priority: 20
weight: 0
Or for the second example:
dnsmasq__dns_records:
- host: 'foo-server1.example.com'
address: '192.0.2.1'
- host: 'foo-server2.example.com'
address: '192.0.2.2'
- host: 'foo-server3.example.com'
address: '192.0.2.3'
- host: 'foo-server4.example.com'
address: '192.0.3.1'
- cname: 'foo1.example.com'
target: 'foo-server1.example.com'
- cname: 'foo2.example.com'
target: 'foo-server2.example.com'
- cname: 'foo3.example.com'
target: 'foo-server3.example.com'
- cname: 'foobackup.example.com'
target: 'foo-server4.example.com'
- cname: 'foo.example.com'
target: 'foo-server1.example.com'
- srv: '_foo._tcp.example.com'
target: 'foo1.example.com'
port: 4242
priority: 10
weight: 80
- srv: '_foo._tcp.example.com'
target: 'foo2.example.com'
port: 4242
priority: 10
weight: 40
- srv: '_foo._tcp.example.com'
target: 'foo3.example.com'
port: 4242
priority: 10
weight: 40
- srv: '_foo._tcp.example.com'
target: 'foo4.example.com'
port: 4242
priority: 20
weight: 0
dnsmasq__dhcp_hosts:
- name: 'foo-server5'
comment: 'Development foo server'
domain: 'example.com'
mac: '00:00:5e:00:53:04'
ip: '192.0.4.1'
cname: [ 'foo-test' ]
Note
The above example demonstrates how host addresses can be defined
either as a separate host record in dnsmasq__dns_records
or as part of a DHCP record in dnsmasq__dhcp_hosts.
SRV Records used by DebOps roles
The following table lists the DNS SRV records used for autoconfiguration by
various DebOps roles:
Role |
SRV |
Variable |
Fallback |
|---|---|---|---|
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
The DebOps dig_srv plugin
DebOps roles use a slightly modified version of the Ansible dig lookup
plugin to perform DNS SRV record lookups. The reason that a custom plugin
is used is that the Ansible version does not make it possible to distinguish
between errors which should halt the operation of a play (e.g. if the DNS server
returns SERVFAIL) and errors which should not (e.g. NXDOMAIN).
In addition, the ansible plugin does not sort the returned resource records,
meaning that idempotency is not ensured (unless the results are sorted manually)
in case several SRV records are returned.
The custom dig_srv plugin generally works in a manner similar to the
Ansible dig lookup plugin, but removes parameters which are not necessary
for looking up SRV resource records and also provides a slightly different
return value.
If we assume a role called bar, which wishes to lookup SRV records for
the service foo, using foo.<domain> and port 4242 as a fallback
(in case no SRV records are defined), the plugin would be called like this:
bar__domain: '{{ ansible_domain }}'
bar__foo_srv_rr: '{{ q("debops.debops.dig_srv", "_foo._tcp." + bar__domain,
"foo." + bar__domain, 4242) }}'
The return value from the lookup would be a list of YAML dictionaries, where
each dictionary corresponds to one SRV record. Something like this:
bar__foo_srv_rr:
- target: 'foo1.example.com'
class: 'IN'
owner: '_foo._tcp.example.com'
port: 4242
priority: 10
ttl: 86400
type: 'SRV'
weight: 40
dig_srv_src: 'dns'
target_port: 'foo1.example.com:4242'
- target: 'foo2.example.com'
class: 'IN'
owner: '_foo._tcp.example.com'
port: 4242
priority: 20
ttl: 86400
type: 'SRV'
weight: 40
dig_srv_src: 'dns'
target_port: 'foo2.example.com:4242'
The dig_srv_src field will be either dns if resource records were
returned by the DNS server and fallback otherwise. See
DNS SRV Records for a definition of the other fields. The
resource records will be sorted on priority, weight (reverse order,
i.e. higher weight first), target and port.
In case no SRV records are available, the lookup will return something like
this:
bar__foo_srv_rr:
- target: 'foo.example.com'
class: 'IN'
owner: '_foo._tcp.example.com'
port: 4242
priority: 0
ttl: 0
type: 'SRV'
weight: 0
dig_srv_src: 'fallback'
target_port: 'foo.example.com:4242'
Overriding DNS SRV Queries
The format described in the previous section can also be used to override the
variables used by the various roles to lookup the DNS SRV records
(generally named <role>__*_srv_rr, see SRV Records used by DebOps roles
for a list), for example if you plan to add DNS SRV records later:
# ansible/inventory/group_vars/all/bar.yml
bar__foo_srv_rr:
- target: 'foo.example.org'
port: '1234'
priority: '1'
Alternatively, most roles set separate variables on the basis of the results
of the SRV lookup. In such cases, it might also be more straightforward
to override these "dependent" variables straight away.
For example, the debops.nullmailer role performs the SRV lookup
using the nullmailer__smtp_srv_rr variable, which is then used
to create default values for nullmailer__relayhost and
nullmailer__smtp_port:
nullmailer__smtp_srv_rr: '{{ q("debops.debops.dig_srv"... }}'
nullmailer__relayhost: '{{ nullmailer__smtp_srv_rr[0]["target"] }}'
nullmailer__smtp_port: '{{ nullmailer__smtp_srv_rr[0]["port"] }}'
Which means that the autoconfiguration can also be overridden by setting these variables directly:
# ansible/inventory/group_vars/all/nullmailer.yml
nullmailer__relayhost: 'notfoo.example.com'
nullmailer__smtp_port: 42
Further Reading
The Wikipedia article on DNS Service (SRV) records
IANAs list of service names and port numbers