DEPRECATED - This doesn’t work properly
Please see this post for an updated version which works!
One of the advantages of using Consul for service discovery is that besides an HTTP API, you can also query it by DNS.
The DNS server is listening on port 8600
by default, and you can query both A records or SRV records from it. SRV records are useful as they contain additional properties (priority
, weight
and port
), and you can get multiple records back from a single query, letting you do load balancing client side:
$ dig @localhost -p 8600 consul.service.consul SRV +short
1 10 8300 vagrant1.node.dc1.consul.
1 14 8300 vagrant2.node.dc1.consul.
2 100 8300 vagrant3.node.dc1.consul.
A Records are also useful, as it means we should be able to treat services registered to Consul like any other domain - but it doesn’t work:
$ curl http://consul.service.consul:8500
curl: (6) Could not resolve host: consul.service.consul
The reason for this is that the system’s built-in DNS resolver doesn’t know how to query Consul. We can, however, configure it to forward any *.consul
requests to Consul.
Solution - Forward DNS queries to Consul
As I usually target Ubuntu based machines, this means configuring systemd-resolved
to forward to Consul. However, we want to keep Consul listening on it’s default port (8600
), and systemd-resolved
can only forward requests to port 53
, so we need also to configure iptables
to redirect the requests.
The steps are as follows:
- Configure
systemd-resolved
to forward.consul
TLD queries to the local consul agent - Configure
iptables
to redirect53
to8600
So let’s get to it!
1. Make iptables persistent
IPTables configuration changes don’t persist through reboots, so the easiest way to solve this is with the iptables-persistent
package.
Typically I am scripting machines (using [Packer] or [Vagrant]), so I configure the install to be non-interactive:
echo iptables-persistent iptables-persistent/autosave_v4 boolean false | sudo debconf-set-selections
echo iptables-persistent iptables-persistent/autosave_v6 boolean false | sudo debconf-set-selections
sudo DEBIAN_FRONTEND=noninteractive apt install -yq iptables-persistent
2. Update Systemd-Resolved
The file to change is /etc/systemd/resolved.conf
. By default it looks like this:
[Resolve]
#DNS=
#FallbackDNS=8.8.8.8 8.8.4.4 2001:4860:4860::8888 2001:4860:4860::8844
#Domains=
#LLMNR=yes
#DNSSEC=no
We need to change the DNS
and Domains
lines - either editing the file by hand, or scripting a replacement with sed
:
sudo sed -i 's/#DNS=/DNS=127.0.0.1/g; s/#Domains=/Domains=~consul/g' /etc/systemd/resolved.conf
The result of which is the file now reading like this:
[Resolve]
DNS=127.0.0.1
#FallbackDNS=8.8.8.8 8.8.4.4 2001:4860:4860::8888 2001:4860:4860::8844
Domains=~consul
#LLMNR=yes
#DNSSEC=no
By specifying the Domains
as ~consul
, we are telling resolvd to forward requests for the consul
TLD to the server specified in the DNS
line.
3. Configure Resolvconf too
For compatibility with some applications (e.g. curl
and ping
), we also need to update /etc/resolv.conf
to specify our local nameserver. You do this not by editing the file directly!
Instead, we need to add nameserver 127.0.0.1
to /etc/resolvconf/resolv.conf.d/head
. Again, I will script this, and as we need sudo
to write to the file, the easiest way is to use tee
to append the line and then run resolvconf -u
to apply the change:
echo "nameserver 127.0.0.1" | sudo tee --append /etc/resolvconf/resolv.conf.d/head
sudo resolvconf -u
Configure iptables
Finally, we need to configure iptables so that when systemd-resolved
sends a DNS query to localhost on port 53
, it gets redirected to port 8600
. We’ll do this for both TCP and UDP requests, and then use netfilter-persistent
to make the rules persistent:
sudo iptables -t nat -A OUTPUT -d localhost -p udp -m udp --dport 53 -j REDIRECT --to-ports 8600
sudo iptables -t nat -A OUTPUT -d localhost -p tcp -m tcp --dport 53 -j REDIRECT --to-ports 8600
sudo netfilter-persistent save
Verification
First, we can test that both Consul and Systemd-Resolved return an address for a consul service:
$ dig @localhost -p 8600 consul.service.consul +short
10.0.2.15
$ dig @localhost consul.service.consul +short
10.0.2.15
And now we can try using curl
to verify that we can resolve consul domains and normal domains still:
$ curl -s -o /dev/null -w "%{http_code}\n" http://consul.service.consul:8500/ui/
200
$ curl -s -o /dev/null -w "%{http_code}\n" http://google.com
301
End
There are also guides available on how to do this on Hashicorp’s website, covering other DNS resolvers too (such as BIND, Dnsmasq, Unbound).