CoreDNS comme DNS resolver
Configuration et déploiement de CoreDNS conteneurisé comme DNS resolver

Pour faire simple CoreDNS est un DNS server/forwarder écris en Go, fortement extensibles grâce à son architecture. Il reste malgré tout simple (un seul binaire, ayant pour but de réalisé une seule fonction), a une empreinte des plus réduites et données accès à toutes fonctions qu’on pourrait attendre d’un outils production-ready.
En quoi c’est bien?
Pour ma part, j’aime la simplicité et que ce soit dans un en environnement local (mon homelab) ou cloud je préfère utiliser des outils simples que des outils faciles. Je peux ainsi non seulement m’assurer de comprendre et maîtriser les outils que je déploie (pouvant ainsi intervenir en cas de problème) mais aussi de m’assurer de l’empreint machine (que ce soit pour des raisons de coût d’infrastructure ou d’empreinte carbone).
Lire et comprendre les sources est très simple. Ainsi que développer des plugins pour des cas très spécifiques.
Pour ma part je n’exploite pas une infrastructure très complexe, au plus quelques centaines de machines sur un petit nombre de clouds ou on-premises. Les équipes clouds sont aussi contraintes.
Déployer CoreDNS
Mon contexte
Parlons de mon homelab, qui correspond pas mal aux infrastructures commerclales sur lesquelles j’intervient/conseille.
Mon homelab est fait d’un petit mainframe (majoritairement pour l’usage de GPU) et un cluster ARM (raspberry-like pour ou je déploie la majorité de mes services).
A cela viens s’ajouter des services OVH et scaleway pour des usages plus spécifiques (me permettant de pas exposer mon homelab directement).
Niveau ingress j’utilise un LB L7 qui permet l’accès depuis le monde extérieur et des LB L4 pour l’accès local.
Enfin, pour la communication entre l’on-premise et le cloud, je passe par un service egress pour sécuriser/auditer le traffic sortant.
Et comme je suis tout seul, je préfère utiliser les capacité natives des outils que je déploie pour le discovery de services ou machines.
Déploiement naÏf
On va déployer CoreDNS dans un conteneur et configurer notre local machine pour l’utiliser comme serveur dns interne. On ira assez vite, comme vous trouverez des centaines de tuto sur cet exemple ultra-simpliste.
Commençant par écrire le fichier de config de base:
0.:53 {
1 errors #Logs
2 health { #Health
3 lameduck 15s
4 }
5 prometheus :9153 # La route pour exposer les metrique de CoreDNS.
6 cache 30 # Cache des requêtes DNS
7 forward . 8.8.8.8 9.9.9.9
8 reload
9}
Pour faire simple: Tous les plugins sont désactivés par defaut. Nous devons les activer et les configurer si besoin.
- erreurs: permet de configurer la manière dont on expose les erreurs. Si nous activons seulement le plugins, les erreurs seront publiées sur la sortie standard.
- heath: expose la route
/health
pour connaître l’état de santé du service. Par défault, le service l’expose sur le port 8080. La réponse a pour code 200 si le service est sain. La configurationlameduck DURATION
permet de mettre un delais avant que d’éteindre le serrvice. - prometheus: expose la route pour exporter les metrics prometheus. Par défault le port 9153 est utilisé
- cache: configure le cache des records.
- forward: pertmet de définir un serveur DNS en amont pour résoudre les demandes en cas de manque de configuration locale. Ici on point vers ceux de google.
- reload: recharge le fichier de configuration en cas de modification.
On démarre CoreDNS
Comme dis plus haut, nous allons utiliser un conteneur pour notre service. J’utilise pour ma part podman
mais c’est exactement la même chose sur docker
.
0# podman run --rm -d -n name coredns -p 9153:9153 -p 53:53/tcp -p 53:53/tcp --network host -v /opt/coredns:/etc/coredns docker.io/coredns/coredns:1.11.1 -conf /etc/coredns
Je rentrerai pas dans les détails ici, sortant du scope de CoreDNS. On expose le port 53 en tcp et udp, qui est le port par défaut pour les appel à server DNS. J’utilse le network host, qui n’est pas nécessaire, seulement pour m’assurer que je lance pas sans faire exprès un autre serveur à sa place et car je suis en mode rootless (le port étant bloqué).
Comme toujours, n’utilisez JAMAIS la version latest sur un service qui doit run sur le long terme. Prenez une version et utilisez là!
Enfin, on donne à coreDNS le chemin vers sa configuration.
Vous pouvez simplement vous assurer que le service run bien avec dig
pour intérroger coreDNS.
0# dig @127.0.0.1 google.com
Vous devriez voir un résultat du type:
0; <<>> DiG 9.18.28-0ubuntu0.22.04.1-Ubuntu <<>> @127.0.0.1 google.com
1; (1 server found)
2;; global options: +cmd
3;; Got answer:
4;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 45400
5;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
6
7;; OPT PSEUDOSECTION:
8; EDNS: version: 0, flags:; udp: 1232
9;; QUESTION SECTION:
10;google.com. IN A
11
12;; ANSWER SECTION:
13google.com. 265 IN A 172.217.20.206
14
15;; Query time: 20 msec
16;; SERVER: 127.0.0.1#53(127.0.0.1) (UDP)
17;; WHEN: Thu Feb 13 16:47:56 CET 2025
Vous pouvez voir que l’ip du serveur DNS utilisée (ici @127.0.0.1
vue qu ele conteneur run en local) ainsi que l’ip de google.com 172.217.20.206
.
Configurer le local pour utiliser le service CoreDNS
Sur la plus part des OS que je déploie sur mes machines (non-serveur), utilisent system-d. Nous allons devoir désactiver le stub resolver de systemd et rerouter vers notre serveur dns. Sachez qu’en utilisant netplan on peut arriver au même résultat.
Nous allons modifier le fichier /etc/systemd/resolved.conf
pour ajouter l’ip de notre server DNS, changer l’option pour l’utiliser pour tous les donmaines, désactiver le stub.
VOus pouvez ici aussi ajouter un fallback au cas ou le serveur CoreDNS est tombé.
16[Resolve]
17# Some examples of DNS servers which may be used for DNS= and FallbackDNS=:
18# Cloudflare: 1.1.1.1#cloudflare-dns.com 1.0.0.1#cloudflare-dns.com 2606:4700:4700::1111#cloudflare-dns.com 2606:4700:4700::1001#cloudflare-dns.com
19# Google: 8.8.8.8#dns.google 8.8.4.4#dns.google 2001:4860:4860::8888#dns.google 2001:4860:4860::8844#dns.google
20# Quad9: 9.9.9.9#dns.quad9.net 149.112.112.112#dns.quad9.net 2620:fe::fe#dns.quad9.net 2620:fe::9#dns.quad9.net
21DNS=127.0.0.1
22DNSStubListener=no
23#FallbackDNS=
24#Domains=
25#DNSSEC=no
26#DNSOverTLS=no
27#MulticastDNS=no
28#LLMNR=no
29#Cache=no-negative
30#CacheFromLocalhost=no
31#DNSStubListenerExtra=
32#ReadEtcHosts=yes
33#ResolveUnicastSingleLabel=no
Il suffit maintenant de relancer le service pour appliquer les changement
0# sudo systemctl restart systemd-resolved
Et voila.
Il suffit maintenant de valider avec dig
sans spécifier l’ip du serveur DNS et on doit avoir la même réponse qu’avant.
0$dig google.com
Configuration de systemd pour rendre le service persistant
Nous allons créer le fichier de service pour CoreDNS. Nous pourrons ainsi gérer directement avec systemctl. Ce n’est pas nécessaire, et vous pouvez vous assurer que le service redémarre en passant le flag à votre gestinaire de conteneur (–restart always ou autre).
Nous allons créer un utilisateur spécifique pour le service.
16# useradd coredns -s /sbin/nologin -c 'coredns running account'
Et maintenant le fichier de service. Vous pouvez soit le faire directement grâce à podman-generate-systemd. A nouveau, vue que le sujet est hors CoreDNS, je ne m’étendrais pas sur le fichier de service. La doc à ce sujet est pléthorique.
16[Unit]
17Description= Podman container for CoreDNS service
18Documentation=man:podman-generate-systemd(1)
19After=network.target
20
21[Service]
22Environment=PODMAN_SYSTEMD_UNIT=%n
23Restart=on-failure
24TimeoutStopSec=70
25ExecStartPre=/bin/rm -f %t/%n.ctr-id
26ExecStart=/usr/bin/podman run \
27 --cidfile=%t/%n.ctr-id \
28 --cgroups=no-common \
29 --rm \
30 --sdnotify=common \
31 --replace \
32 --name=coredns \
33 -p 53:53/udp \
34 -p 53:53/tcp \
35 -p 9153:9153 \
36 -v /opt/coredns:/etc/coredns \
37 --network host \
38 -d docker.io/coredns/coredns:1.11.1 -conf /etc/coredns
39ExecStop=/usr/bin/podman stop \
40 --ignore 10 \
41 --cidfile=%t/%n.ctr-id
42ExecStopPost=/usr/bin/podman rm -f --ignore 10 --cidfile=%t/%n.ctr-id
43Type=notify
44NotifyAccess=all
45
46[Install]
47Wantedby=default.target
On n’a plus qu’à activer et démarrer le service/
Et c’est tout. Vous avez un serveur de DNS en local… qui pointe seulement sur les serveur DNS de google…
Ajoutons des configurations locales pour rendre le tout utile
L’utilité jusqu’ici est nulle. Ajoutons quelques config statiques:
Configuration de ZONE
Pour le rendre plus utile ajoutons des hosts custom, qui seront statiques. Pour cela nous allons ajouter des fichiers de zone à la config de CoreDNS.
La manière la pluys bûcheronne c’est les ajouter directement dans le Corefile sous le plugin hosts
0.:53 {
1 errors #Logs
2 health { #Health
3 lameduck 15s
4 }
5 prometheus :9153 # La route pour exposer les metrique de CoreDNS.
6 cache 30 # Cache des requêtes DNS
7 forward . 8.8.8.8 9.9.9.9
8 reload
9 hosts {
10 10.0.0.1 exemple.local
11 10.0.0.2 test.local
12 10.0.0.3 dev.local
13 }
14}
Maintenant on est au même niveau qu’avec notre fichier /etc/hosts… Merde on a fait tout ça pour ça?
Ajoutons des zones avec nos configuration
D’abord on va modifier le Corefile pour ajouter les zones et relier les fichiers de config.
0.:53 {
1 errors #Logs
2 health { #Health
3 lameduck 15s
4 }
5 prometheus :9153 # La route pour exposer les metrique de CoreDNS.
6 cache 30 # Cache des requêtes DNS
7 forward . 8.8.8.8 9.9.9.9
8 reload
9}
10
11services.local:53 {
12 file /etc/coredns/zones/db.services.local
13 acl {
14 allow net 10.0.0.0/24
15 block net 10.1.0.0/24
16 filter net 0.0.0.0/0
17 }
18}
Cette fois, on vient contraindre les rêquetes en les matchant à services.local
.
Grâce au plugin acl
on peut définir les subnets spécific pouvant ou pas requêter CoreDNS.
Ici on permet tous les appels depuis le CIDR 10.0.0.0/24.
On block tous ce qui vient du CIDR 10.1.0.0/24, envoyant une réponse REFUSED.
Enfin le filter nous permet de bloquer les appel depuis l’extérieur en renvoyant une réponse NOERROR.
il nous reste plus qu’à finir finement notre zone dans le fichier /opt/coredns/zones/db.services.local
(qui est visible pour CoreDNS dans son conteneur sur /etc/coredns/zones/db.services.local )
0$TTL 3600
1$ORIGIN services.local.
2@ IN SOA ns.services.local. contact.services.local. (
3 2019121301 ; serial
4 1d ; refresh
5 2h ; retry
6 4w ; expire
7 1h ; nxdomain ttl
8 )
9 IN NS ns.services.local.
10@ IN A 192.168.1.10
11* IN A 192.168.1.10
12@ IN A 10.10.100.10
13* IN A 10.10.100.10
Je vais pas rentrer dans la définition du fichier, car on sort en partie de CoreDNS. Du coup google est ton ami.
Pour toutes les requetes qui match services.local
on va utiliser
Autres plugins
Comme dis plus haut l’une des valeurs ajoutés est l’extensibilité des plugins ainsi on pourra par exemple:
Pour l’automatisation: On peut utiliser un plugin pour remplir les records avec les données des services Nomad (il est un standard de K8). Ou en local en se basant sur les données du moteur de conteneur.
Grâce au TLS, DNSSEC et ACL on peut renforcer la sécurité, en implémentant simplement du Zero Trust.
Et au vue de sa simplicité, déployer un plugin pour un besoin spécifique est rapide et facile.
Mot de la fin
CoreDNS fut pour moi un bénédiction. J’ai pu virer consul/bind/dsnmasq, sans perdre en sécurité ou fonctionalité (celles que j’utiliser). De plus grâce à sa simplicité, j’ai presque jamais eu de problème. Et lorsque j’en ai eu, le debug fut bien plus simple.
Et le tout pour un prix à tout épreuve au vue de son empreinte. L’essayer c’est l’adopter!