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.
_service
The 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.
_proto
The protocol to use for the desired service, usually
tcp
orudp
, prefixed with an underscore.name
The domain name for which the record is valid.
ttl
The DNS time-to-live value.
IN
The DNS record class.
SRV
The DNS record type.
priority
The priority of the record. Clients should attempt to use records with the lowest priority first and then use records with higher-valued
priority
as a fallback.weight
The 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.
port
The UDP/TCP port on which the service is provided (see the
_service
field above).target
The 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