Introduction to CoreDNS in a Dockerised environment

23 Feb 2019

CoreDNS is a relatively new DNS server written in Go. CoreDNS was written keeping in mind the evolving needs of today and the ability to work well with cloud native applications. In Kubernetes 1.13, CoreDNS is the default cluster DNS server.

synopsis

Resources required

  • Windows / Linux or MAC with Virtualbox installed.
  • Ubuntu Server 16.04.4 LTS ISO image.

Prerequisites

  • Basic understanding of DNS
  • Basic understanding of Docker

Setting up the environment

Bind on Docker

Before we start with CoreDNS, let’s take a look at how to run Bind as a Docker container. Since we are already familiar with Bind, this should help us get some context on CoreDNS by providing us a comparison between CoreDNS and Bind.

To begin, we wll create an ubuntu container and install Bind in it.

Run the below command to deploy the Ubuntu container for Bind.

docker run -it -p 53:53/udp --name bind --hostname bind ubuntu bash

Your prompt should have changed to something similar to the below.

root@bind:/#

You are now in the container where you will install Bind.

Let’s proceed with the installation.

Run the following commands in the container to install Bind and related tools. We will also install Vim which will let s edit configuration file.

apt update
apt install bind9 bind9utils vim -y

We will be configuring Bind as a simple forwarding server.

Run the below command to edit the configuration file.

vi /etc/bind/named.conf.options

Edit the configuration file as below.

options {
        directory "/var/cache/bind";
         forwarders {
                10.192.3.10;
         };

        listen-on { any; };
};

Now, let’s start the DNS service.

service bind9 start

The expected output is as below.

root@bind:/# service bind9 start
 * Starting domain name service... bind9                                                                                                                                                                                                 [ OK] 

Detach from the container using the keyboard combination < CTRL+P Q >.

You should have detached from the container now.

Query the localhost for the A record of google.com.

The output should be as below.

 # dig @localhost google.com

; <<>> DiG 9.11.3-1ubuntu1.2-Ubuntu <<>> @localhost google.com
; (2 servers found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 28564
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
; COOKIE: 50d02d3da9df35d4f87516ce5be9febb259bf180bbc6261a (good)
;; QUESTION SECTION:
;google.com.                    IN      A

;; ANSWER SECTION:
google.com.             2       IN      A       172.217.6.46

;; Query time: 6 msec
;; SERVER: ::1#53(::1)
;; WHEN: Mon Nov 12 22:29:15 UTC 2018
;; MSG SIZE  rcvd: 83

We have now run Bind from a Docker container and tested the server.

Let’s move on to CoreDNS.

Before we move on, we will need to kill the Bind container to free up UDP port 53 on the host.

Run the below command to kill the container.

docker kill bind

Confirm that the container is no longer running with the ‘docker ps’ command. The output should be as below.

#  docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

Installing CoreDNS

CoreDNS does provide a docker image but we will be looking at how to run it manually from an Ubuntu container.

  • Deploy an Ubuntu container with the name coredns-manual. Ensure that ports 53 on the host is mapped to the container.

The below command deploys the container.

docker run -it -p 53:53/udp --name coredns-manual --hostname coredns ubuntu bash

Your prompt should have changed to something similar to the below.

root@coredns:/#
  • To install new packages, you must first update the package manager for Ubuntu. The package manage is ‘apt’. You can update ‘apt’ with the command ‘apt update’. The command and output are below.
root@coredns:/# apt update
Installing CoreDNS

Below are the steps to install CoreDNS

Precompiled binary executable file for CoreDNS can be obtained from the CoreDNS Github repository.

apt install wget vim -y
wget https://github.com/coredns/coredns/releases/download/v1.2.5/coredns_1.2.5_linux_amd64.tgz
mkdir ~/coredns
mv coredns_1.2.5_linux_amd64.tgz ~/coredns
cd ~/coredns
tar -xvzf coredns_1.2.5_linux_amd64.tgz
rm -f coredns_1.2.5_linux_amd64.tgz

This should produce an executable file named coredns.

root@coredns:~/coredns# ls -l
total 50440
-rwxr-xr-x 1 www-data www-data 39737920 Oct 24 20:10 coredns

Run the executable file.

The output should be as below.

root@coredns:~/coredns# ./coredns
.:53
2018/10/31 19:25:54 [INFO] CoreDNS-1.2.5
2018/10/31 19:25:54 [INFO] linux/amd64, go1.11.1, 204537b
CoreDNS-1.2.5
linux/amd64, go1.11.1, 204537b

Notice that the execution does not complete and go the prompt “root@coredns:~/coredns#” CoreDNS server is now running.

Test the CoreDNS server

Detach from the container using the keyboard combination < CTRL+P Q >.

Now you should have detached from the container and shuold be in the prompt of the VM.

Run the command ‘docker ps’. Output should be as below(except for the Container ID).

# docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS                                      NAMES
858520e82135        ubuntu              "bash"              4 hours ago         Up 17 minutes       0.0.0.0:53->53/udp, 0.0.0.0:443->443/tcp   coredns-manual

Under PORTS, “0.0.0.0:53->53/udp” shows that UDP port 53 on the host is mapped to UDP port 53 on the container.

Perform a few DNS queries against localhost. Notice that the response always looks similar to this.

 # dig @localhost example.com

; <<>> DiG 9.11.3-1ubuntu1.2-Ubuntu <<>> @localhost example.com
; (2 servers found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 25683
;; flags: qr aa rd; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 3
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
; COOKIE: cbc94e442b78ed38 (echoed)
;; QUESTION SECTION:
;example.com.                  IN      A

;; ADDITIONAL SECTION:
example.com.           0       IN      A       172.17.0.1
_udp.example.com.      0       IN      SRV     0 0 59496 .

;; Query time: 0 msec
;; SERVER: ::1#53(::1)
;; WHEN: Wed Oct 31 19:41:50 UTC 2018
;; MSG SIZE  rcvd: 117


This is expected. Since no configuration has been done, CoreDNS loads a plugin called whoami that responds with the IP address and port of the client.

Configuring CoreDNS
Corefile

Configuration parameters for CoreDNS are defined in a file named ‘Corefile’. CoreDNS is designed to run multiple server instances on the same host. These servers can run on different ports or the same port. A server block is a block of configuration statements in the corefile which define a server. Zone statements are configured in server blocks. If there are multiple server blocks listening on the same port (eg: port 53), the server will send a response from the server block with the zone with the closest match to the domain in the queried.

Below is a sample server block

test.com{

}

By default the server listens on port 53. To configure the server to listen on another port, say 1053, define the server block as below.

test.com:53 {

}
Plugins

CoreDNS works by using plugins for different functionality. Plugin are programs that increase the functionality of CoreDNS. Plugins can be in-tree(from CoreDNS team) or external plugins(developed by the community or third parties).

Here are a few plugins which we will be working with:

  • file: Reads zone data from a zone file.
  • log: Logs queries. By default queries are logged to STDOUT.
  • forward: Forward queries to a forwarder.

These are in-tree plugins and don’t require any additional setup. These plugins can be invoked by calling them from the ‘Corefile’ as seen below.

test.com:53 {
    log
}

CoreDNS by default does not support recursively resolving queries. The ‘forward’ in-tree plugin can be used to forward queries to a forwarder. There are external plugins that can bring this functionality to CoreDNS

To get started attach to the container ‘coredns-manual’.

#docker attach coredns-manual

Stop the running process using the keyboard combination <CTRL + C>.

Confirm that the working directory is ‘/root/coredns’.

root@coredns:~/coredns# pwd
/root/coredns

If not, change directory to /root/coredns.

cd /root/coredns

Create a file named ‘Corefile’.

vi Corefile

Insert the below text into the file.

test.com {
    file db.test.com
}

Create a file named db.test.com and insert the below text into the file.

$ORIGIN test.com.
@       3600 IN SOA ns1.test.com. admin.test.com. (
                                2017042745 ; serial
                                7200       ; refresh (2 hours)
                                3600       ; retry (1 hour)
                                1209600    ; expire (2 weeks)
                                3600       ; minimum (1 hour)
                                )

        3600 IN NS ns1.test.com.
        3600 IN NS ns2.test.com.

www     IN A     10.0.0.1
ns1     IN A     10.10.10.10
ns2     IN A     20.20.20.20

Start CoreDNS with the below command.

./coredns -conf Corefile

Detach from the container using the keyboard combination < CTRL+P Q >.

Query localhost for www.test.com. You should be able to see output as below.

 #  dig @localhost www.test.com                                                                                                     1 ↵

; <<>> DiG 9.11.3-1ubuntu1.2-Ubuntu <<>> @localhost www.test.com
; (2 servers found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 51555
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 2, ADDITIONAL: 0

;; QUESTION SECTION:
;www.test.com.                  IN      A

;; ANSWER SECTION:
www.test.com.           3600    IN      A       10.0.0.1

;; AUTHORITY SECTION:
test.com.               3600    IN      NS      ns1.test.com.
test.com.               3600    IN      NS      ns2.test.com.

;; Query time: 2 msec
;; SERVER: ::1#53(::1)
;; WHEN: Thu Nov 01 20:16:07 UTC 2018
;; MSG SIZE  rcvd: 126

In the above excersise, we used the file plugin. The statement file db.test.com is to use the file ‘db.test.com’ as the zone file for the zone ‘test.com’.

Now we will look at the log plugin.

To see the ‘log’ plugin in action, let’s look at the logs for the ‘coredns-manual’ container. The command and output are below.

# docker logs coredns-manual --tail 10
root@coredns:~/coredns# ./coredns -conf Corefile
test.com.:53
2018/11/02 13:30:31 [INFO] CoreDNS-1.2.5
2018/11/02 13:30:31 [INFO] linux/amd64, go1.11.1, 204537b
CoreDNS-1.2.5
linux/amd64, go1.11.1, 204537b

Attach to the container again.

docker attach coredns-manual

Stop the running process using the keyboard combination <CTRL + C>.

Edit the file ‘Corefile’ and to match the below.


test.com {
    file db.test.com
    log
}
~

Start CoreDNS server.

./coredns -conf Corefile

Detach from the container using the keyboard combination < CTRL+P Q >.

Query localhost as above for www.test.com.

Now, check the logs for the container again.

You should see logs as below.

root@coredns:~/coredns#./coredns -conf Corefile
test.com.:53
2018/11/02 13:39:19 [INFO] CoreDNS-1.2.5
2018/11/02 13:39:19 [INFO] linux/amd64, go1.11.1, 204537b
CoreDNS-1.2.5
linux/amd64, go1.11.1, 204537b
172.17.0.1:60210 - [02/Nov/2018:13:48:05 +0000] 24028 "A IN www.test.com. udp 54 false 4096" NOERROR qr,aa,rd,ra 126 0.004729567s

Note that the logs now include query logs.

172.17.0.1:60210 - [02/Nov/2018:13:48:05 +0000] 24028 "A IN www.test.com. udp 54 false 4096" NOERROR qr,aa,rd,ra 126 0.004729567s

We are able to see these logs in docker logs since CoreDNS is running in a docker container. If CoreDNS was running on the host and not in a container, these logs would be printed to STDOUT.

Now, we will configure the forward plugin.

The forward plugin can forward queries to a specific set of servers. The plugin can also load balance using different methods between these servers.

We will configure a global forwarder and a forwarder for a specific domain name using the plugin.

Query localhost for google.com. The response should be as below.

# dig @localhost google.com                                                                                                        1 ↵

; <<>> DiG 9.11.3-1ubuntu1.2-Ubuntu <<>> @localhost google.com
; (2 servers found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: REFUSED, id: 4455
;; flags: qr rd; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
; COOKIE: d7358f9f438c2648 (echoed)
;; QUESTION SECTION:
;google.com.                    IN      A

;; Query time: 0 msec
;; SERVER: ::1#53(::1)
;; WHEN: Fri Nov 02 14:09:53 UTC 2018
;; MSG SIZE  rcvd: 51

Note that the query response is REFUSED.

Attach to the container again.

docker attach coredns-manual

Stop the running process using the keyboard combination <CTRL + C>.

Edit the file ‘Corefile’ and to match the below.

test.com {
    file db.test.com
    log
}

. {
  forward . 10.192.3.10
}

Start CoreDNS server.

./coredns -conf Corefile

Detach from the container using the keyboard combination < CTRL+P Q >.

Query localhost for google.com. The response should be as below.

) # dig @localhost google.com                                                                                                      127 ↵

; <<>> DiG 9.11.3-1ubuntu1.2-Ubuntu <<>> @localhost google.com
In ; (2 servers found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 52732
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;google.com.                    IN      A

;; ANSWER SECTION:
google.com.             36      IN      A       216.58.192.14

;; Query time: 12 msec
;; SERVER: ::1#53(::1)
;; WHEN: Fri Nov 02 14:13:49 UTC 2018
;; MSG SIZE  rcvd: 65

The query was forwarded the 10.192.3.10 and resolved.

Below are the newly added entries in the ‘Corefile’.

. {
  forward . 10.192.3.10
}

The zone name here is ‘.’ (root). In the plugin statement we say ‘forward . 10.192.3.10’ forward all queries under root to 10.192.3.10.

Now query localhost for www.test.com. The query is resolving from the authoritative zone.

This is because, since both server blocks listen on the default port (53), the query for www.test.com is resolved from the server block for test.com and anything else would be resolved from the server block for root.

Now we will forward queries for a particular domain.

Below are the steps.

  • Copy the coredns executable, the Corefile and the file db.test.com from the container to the host.
# docker cp coredns-manual:/root/coredns/coredns .
#  docker cp coredns-manual:/root/coredns/Corefile .
#  docker cp coredns-manual:/root/coredns/db.test.com .
  • Confirm that the files have been copied.
#  ls -l
total 38816
-rwxr-xr-x 1 root root 39737920 Oct 24 20:10 coredns
-rw-r--r-- 1 root root       73 Nov  2 14:11 Corefile
-rw-r--r-- 1 root root      367 Nov  1 20:25 db.test.com
  • Modify the Corefile as below.
example.com {
    file db.example.com
    log
}
  • Rename db.test.com to db.example.com
mv db.test.com db.example.com
  • Replace all instances of the word ‘test.com’ in db.example.com with ‘example.com’
sed -i 's/test.com/example.com/g' db.example.com
  • Create a new container named ‘coredns-forwarder’.
docker run -itd --name coredns-forwarder ubuntu bash
  • Confirm that the container has been created.
#  docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS                                      NAMES
b91d9140f02b        ubuntu              "bash"              4 seconds ago       Up 3 seconds                                                   coredns-forwarder
858520e82135        ubuntu              "bash"              47 hours ago        Up About an hour    0.0.0.0:53->53/udp, 0.0.0.0:443->443/tcp   coredns-manual 
  • Copy the files to the new container.
docker cp coredns coredns-forwarder:/root/
docker cp Corefile coredns-forwarder:/root/
docker cp db.example.com coredns-forwarder:/root/
  • Confirm that the files have been copied.
#  docker exec coredns-forwarder ls /root
Corefile
coredns
db.example.com
  • Attach to the container ‘coredns-forwarder’
docker attach coredns-forwarder
 
  • Change directory to /root and run the CoreDNS server
root@b91d9140f02b:/# cd /root
root@b91d9140f02b:~# ./coredns -conf Corefile
example.com.:53
2018/11/02 14:39:23 [INFO] CoreDNS-1.2.5
2018/11/02 14:39:23 [INFO] linux/amd64, go1.11.1, 204537b
CoreDNS-1.2.5
linux/amd64, go1.11.1, 204537b
  • Detach from the container using the keyboard combination < CTRL+P Q >.

  • Find the IP address of the ‘coredns-forwarder’ container. This is done by inspecting the defaulr docker network (bridge).

#  docker network inspect bridge | grep -A 3 coredns-forwarder
                "Name": "coredns-forwarder",
                "EndpointID": "09020d084787b9dcff41f82f8c6be6d6d415dbea3ad53e64b66e8cc2411d4eae",
                "MacAddress": "02:42:ac:11:00:03",
                "IPv4Address": "172.17.0.3/16",
                
  • Attach to the container ‘coredns-manual’.
# docker attach coredns-manual
  • Stop the running CoreDNS server with the keyboard combination < CTRL + C >.

  • Edit the Corefile to match below but make sure that the IP address of the forwarder matches the IP address of ‘coredns-forwarder’.

test.com {
    file db.test.com
    log
}

. {
  forward . 10.192.3.10
}

example.com {
  forward example.com 172.17.0.3
}
  • Start CoreDNS server
./coredns -conf Corefile
  • Detach from the container using the keyboard combination < CTRL+P Q >.

  • Query localhost for www.example.com

#  dig @localhost www.example.com                                                                                            1 ↵

; <<>> DiG 9.11.3-1ubuntu1.2-Ubuntu <<>> @localhost www.example.com
; (2 servers found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 47364
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 2, ADDITIONAL: 0

;; QUESTION SECTION:
;www.example.com.               IN      A

;; ANSWER SECTION:
www.example.com.        3600    IN      A       10.0.0.1

;; AUTHORITY SECTION:
example.com.            3600    IN      NS      ns1.example.com.
example.com.            3600    IN      NS      ns2.example.com.

;; Query time: 0 msec
;; SERVER: ::1#53(::1)
;; WHEN: Fri Nov 02 16:58:20 UTC 2018
;; MSG SIZE  rcvd: 144

Since port 53 on the host is mapped to port 53 on the container coredns-manual, any queries on port 53 to localhost lands on this container. The query is forwarded from this container to the container ‘coredns-forwarder’ where an authoritative server for the zone ‘example.com’ is running. This is how the query is resolved.

Serving Protocols

Currently CoreDNS accepts three different protocols: plain DNS, DNS over TLS and DNS over gRPC. The protocol must me specified on the server block in the Corefile.

To learn more

If you are interested in learning more about the topics discussed here, here are a few places to visit.