Leveraging Docker DNS

The introduction of user-defined networks signaled a big change in Docker networking. While the ability to provision custom networks was the big news, there were also major enhancements in name resolution. User-defined networks can benefit from what’s being named embedded DNS. The Docker engine itself now has the ability to provide name resolution to all of the containers. This is a marked improvement from the legacy solution where the only means for name resolution was external DNS or linking, which relied on the hosts file. In this recipe, we’ll walk through how to use and configure embedded DNS.

Getting ready

In this recipe, we’ll be demonstrating the configuration on a single Docker host. It is assumed that this host has Docker installed and that Docker is in its default configuration. We’ll be altering name resolution settings on the host, so you’ll need root-level access.

How to do it…

As mentioned, the embedded DNS system only works on user-defined Docker networks. That being said, let’s provision a user-defined network and then start a simple container on it:

user@docker1:~$ docker network create -d bridge mybridge1
0d75f46594eb2df57304cf3a2b55890fbf4b47058c8e43a0a99f64e4ede98f5f
user@docker1:~$ docker run -d -P --name=web1 --net=mybridge1 
jonlangemak/web_server_1
3a65d84a16331a5a84dbed4ec29d9b6042dde5649c37bc160bfe0b5662ad7d65
user@docker1:~$

As we saw in an earlier recipe, by default, Docker pulls the name resolution configuration from the Docker host and provides it to the container. This behavior can be changed by providing different DNS servers or search domains either at the service level or at container runtime. In the case of containers connected to a user-defined network, the DNS settings provided to the container are slightly different. For instance, let’s look at the resolv.conf file for the container we just connected to the user-defined bridge mybridge1:

user@docker1:~$ docker exec -t web1 more /etc/resolv.conf
search lab.lab
nameserver 127.0.0.11
options ndots:0
user@docker1:~$ 

Notice how the name server for this container is now 127.0.0.11. This IP address represents Docker’s embedded DNS server and will be used for any container, which is connected to a user-defined network. It is a requirement that any container connected to a user-defined network should use the embedded DNS server.

Containers not initially started on a user-defined network will get updated the moment they connect to a user-defined network. For instance, let’s start another container named web2 but have it use the default docker0 bridge:

user@docker1:~$ docker run -dP --name=web2 jonlangemak/web_server_2
d0c414477881f03efac26392ffbdfb6f32914597a0a7ba578474606d5825df3f
user@docker1:~$ docker exec -t web2 more /etc/resolv.conf
::::::::::::::
/etc/resolv.conf
::::::::::::::
# Dynamic resolv.conf(5) file for glibc resolver(3) generated by resolvconf(8)
#     DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN
nameserver 10.20.30.13
search lab.lab
user@docker1:~$

If we now connect the web2 container to our user-defined network, Docker will update the name server to reflect the embedded DNS server:

user@docker1:~$ docker network connect mybridge1 web2
user@docker1:~$ docker exec -t web2 more /etc/resolv.conf
search lab.lab
nameserver 127.0.0.11
options ndots:0
user@docker1:~$ 

Since both our containers are now connected to the same user-defined network, they can now reach each other by name:

user@docker1:~$ docker exec -t web1 ping web2 -c 2
PING web2 (172.18.0.3): 48 data bytes
56 bytes from 172.18.0.3: icmp_seq=0 ttl=64 time=0.107 ms
56 bytes from 172.18.0.3: icmp_seq=1 ttl=64 time=0.087 ms
--- web2 ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max/stddev = 0.087/0.097/0.107/0.000 ms

user@docker1:~$ docker exec -t web2 ping web1 -c 2
PING web1 (172.18.0.2): 48 data bytes
56 bytes from 172.18.0.2: icmp_seq=0 ttl=64 time=0.060 ms
56 bytes from 172.18.0.2: icmp_seq=1 ttl=64 time=0.119 ms
--- web1 ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max/stddev = 0.060/0.089/0.119/0.030 ms
user@docker1:~$

You’ll note that the name resolution is bidirectional, and it works inherently without the use of any links. That being said, with user-defined networks, we can still define links for the purpose of creating local aliases. For instance, let’s stop and remove both containers web1 and web2 and reprovision them as follows:

user@docker1:~$ docker run -d -P --name=web1 --net=mybridge1 
--link=web2:thesecondserver jonlangemak/web_server_1
fd21c53def0c2255fc20991fef25766db9e072c2bd503c7adf21a1bd9e0c8a0a
user@docker1:~$ docker run -d -P --name=web2 --net=mybridge1 
--link=web1:thefirstserver jonlangemak/web_server_2
6e8f6ab4dec7110774029abbd69df40c84f67bcb6a38a633e0a9faffb5bf625e
user@docker1:~$

The first interesting item to point out is that Docker lets us link to a container that did not yet exist. When we ran the container web1, we asked Docker to link it to the container web2. At that point, web2 didn’t exist. This is a notable difference in how links work with the embedded DNS server. In legacy linking, Docker needed to know the target container information prior to making the link. This was because it had to manually update the source container's host file and environmental variables. The second interesting item is that aliases are no longer listed in the container's hosts file. If we look at the hosts file on each container, we’ll see that the linking no longer generates entries:

user@docker1:~$ docker exec -t web1 more /etc/resolv.conf
search lab.lab
nameserver 127.0.0.11
options ndots:0
user@docker1:~$ docker exec -t web1 more /etc/hosts
…<Additional output removed for brevity>… 
172.18.0.2      9cee9ce88cc3
user@docker1:~$
user@docker1:~$ docker exec -t web2 more /etc/hosts
…<Additional output removed for brevity>… 
172.18.0.3      2d4b63452c8a
user@docker1:~$

All of the resolution is now occurring in the embedded DNS server. This includes keeping track of defined aliases and their scope. So even without host records, each container is able to resolve the other containers alias through the embedded DNS server:

user@docker1:~$ docker exec -t web1 ping thesecondserver -c2
PING thesecondserver (172.18.0.3): 48 data bytes
56 bytes from 172.18.0.3: icmp_seq=0 ttl=64 time=0.067 ms
56 bytes from 172.18.0.3: icmp_seq=1 ttl=64 time=0.067 ms
--- thesecondserver ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max/stddev = 0.067/0.067/0.067/0.000 ms

user@docker1:~$ docker exec -t web2 ping thefirstserver -c 2
PING thefirstserver (172.18.0.2): 48 data bytes
56 bytes from 172.18.0.2: icmp_seq=0 ttl=64 time=0.062 ms
56 bytes from 172.18.0.2: icmp_seq=1 ttl=64 time=0.042 ms
--- thefirstserver ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max/stddev = 0.042/0.052/0.062/0.000 ms
user@docker1:~$

The aliases created have a scope that is local to the container itself. For instance, a third container on the same user-defined network is not able to resolve the aliases created as part of the links:

user@docker1:~$ docker run -d -P --name=web3 --net=mybridge1 
jonlangemak/web_server_1
d039722a155b5d0a702818ce4292270f30061b928e05740d80bb0c9cb50dd64f
user@docker1:~$ docker exec -it web3 ping thefirstserver -c 2
ping: unknown host
user@docker1:~$ docker exec -it web3 ping thesecondserver -c 2
ping: unknown host
user@docker1:~$

You’ll recall that legacy linking also automatically created a set of environmental variables on the source container. These environmental variables referenced the target container and any ports it might be exposing. Linking in user-defined networks does not create these environmental variables:

user@docker1:~$ docker exec web1 printenv
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=4eba77b66d60
APACHE_RUN_USER=www-data
APACHE_RUN_GROUP=www-data
APACHE_LOG_DIR=/var/log/apache2
HOME=/root
user@docker1:~$ 

As we saw in the previous recipe, keeping these variables up to date wasn’t achievable even with legacy links. That being said, it’s not a total surprise that the functionality doesn’t exist when dealing with user-defined networks.

In addition to providing local container resolution, the embedded DNS server also handles any external requests. As we saw in the preceding example, the search domain from the Docker host (lab.lab in my case) was still being passed down to the containers and configured in their resolv.conf file. The name server learned from the host becomes a forwarder for the embedded DNS server. This allows the embedded DNS server to process any container name resolution requests and hand off external requests to the name server used by the Docker host. This behavior can be overridden either at the service level or by passing the --dns or --dns-search flag to a container at runtime. For instance, we can start two more instances of the web1 container and specify a specific DNS server in either case:

user@docker1:~$ docker run -dP --net=mybridge1 --name=web4 
--dns=10.20.30.13 jonlangemak/web_server_1
19e157b46373d24ca5bbd3684107a41f22dea53c91e91e2b0d8404e4f2ccfd68
user@docker1:~$ docker run -dP --net=mybridge1 --name=web5 
--dns=8.8.8.8 jonlangemak/web_server_1
700f8ac4e7a20204100c8f0f48710e0aab8ac0f05b86f057b04b1bbfe8141c26
user@docker1:~$

Note

Note that web4 would receive 10.20.30.13 as a DNS forwarder even if we didn’t specify it explicitly. This is because that’s also the DNS server used by the Docker host and when not specified the container inherits from the host. It is specified here for the sake of the example.

Now if we try to resolve a local DNS record on either container, we can see that in the case of web1 it works since it has the local DNS server defined, whereas the lookup on web2 fails because 8.8.8.8 doesn’t know about the lab.lab domain:

user@docker1:~$ docker exec -it web4 ping docker1.lab.lab -c 2
PING docker1.lab.lab (10.10.10.101): 48 data bytes
56 bytes from 10.10.10.101: icmp_seq=0 ttl=64 time=0.080 ms
56 bytes from 10.10.10.101: icmp_seq=1 ttl=64 time=0.078 ms
--- docker1.lab.lab ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max/stddev = 0.078/0.079/0.080/0.000 ms

user@docker1:~$ docker exec -it web5 ping docker1.lab.lab -c 2
ping: unknown host
user@docker1:~$
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset