Cet article documente la chaîne qui valide et déploie le service DNS : les jeux de données de test, ce que la validation prouve réellement, et pourquoi la mise en production reste derrière un bouton.
Vue d'ensemble
flowchart LR
subgraph dev["Developpement"]
code["Dockerfile<br/>binaire d entree"]
end
subgraph build["Construction"]
docker["Build multi-stage"]
end
subgraph test["Tests"]
local["Test local"]
parallel["Test parallele<br/>sur le routeur"]
end
subgraph ci["Integration continue"]
lint["Analyse statique"]
validate["Validation config<br/>et zones"]
vues["Batterie de vues"]
end
subgraph prod["Production"]
transfer["Telechargement"]
swap["Bascule de tag"]
end
code --> docker --> local --> parallel --> transfer --> swap
docker --> lint
lint --> validate --> vues
Les zones ne sont pas écrites à la main
Les zones sont générées à partir de l'inventaire. Chaque hôte déclaré avec son adresse et son masque produit automatiquement le sous-réseau, la vue et les zones inverses correspondantes.
Les fichiers produits sont commités dans le dépôt, non pas comme source mais comme trace de ce qui est déployé : c'est ce qui permet de voir en revue de code qu'un changement d'inventaire fait apparaître ou disparaître une vue entière.
La clé de signature, elle, n'est jamais commitée. En local un exemplaire factice suffit ; en intégration continue, elle est générée aléatoirement à chaque exécution.
Ce que la validation prouve, et ce qu'elle ne prouvait pas
La validation monte la configuration dans un conteneur, vérifie la syntaxe et toutes les zones — directes comme inverses — puis démarre réellement le résolveur et l'interroge.
C'est nécessaire, et ça ne suffisait pas.
Dix-sept vues sur dix-neuf n'étaient jamais exercées
Le contrôle de configuration valide les zones. Une interrogation en boucle locale ne teste qu'une seule vue : celle que l'adresse source de la requête sélectionne.
Or chaque vue par sous-réseau n'autorise que les clients de son propre sous-réseau. Une requête venue d'ailleurs est refusée. Résultat : sur dix-neuf vues, dix-sept n'étaient jamais interrogées par la CI. Vérifié en retirant la zone interne d'une seule vue — la validation restait verte, et rien ne l'attrapait.
La parade consiste à poser une adresse du sous-réseau visé sur l'interface de boucle locale, puis à interroger en forçant l'adresse source. Le résolveur voit alors la requête comme venant de ce sous-réseau et sélectionne la vue correspondante. Chaque vue est ainsi exercée pour de bon.
Deux détails font la différence entre un test utile et un test décoratif :
- Le test distingue « je n'ai pas pu tester » de « une vue ne résout pas ». Les deux sortent en rouge — un test qui ne tourne pas ne doit jamais passer pour vert — mais le message dit lequel des deux s'est produit, sinon on cherche une panne de DNS là où il manque un privilège réseau au runner.
- Les noms interrogés sont passés en paramètre, pas codés dans le script. Le dépôt qui porte le script est public ; y inscrire une infrastructure réelle la ferait fuiter, et un défaut codé en dur finit toujours par pointer sur un hôte décommissionné sans que personne ne s'en aperçoive.
Tester en parallèle avant de basculer
Avant de toucher à la production, un conteneur de test tourne sur une adresse différente du routeur :
sequenceDiagram
participant Dev as Poste
participant VyOS as Routeur
participant Prod as Resolveur de production
participant Test as Resolveur de test
Dev->>VyOS: telecharger l image
Dev->>VyOS: demarrer sur une autre adresse
Note over Prod: toujours actif
Dev->>VyOS: interroger l instance de test
VyOS->>Test: requete
Test-->>VyOS: reponse
Dev->>VyOS: supprimer l instance de test
Dev->>VyOS: basculer le tag de production
Le conteneur de test ne reçoit aucun trafic réel : la zone de pare-feu n'autorise que l'adresse de production. Seules les requêtes émises depuis le routeur lui-même l'atteignent. C'est ce qui rend le test sans risque.
Deux workflows séparés, et c'est délibéré
La validation se déclenche sur les changements. Le déploiement, lui, ne part que sur un bouton — aucun événement automatique ne peut le lancer.
Pourquoi le déploiement reste manuel
Un déploiement réseau raté casse le DNS, et un DNS cassé isole le homelab — y compris la machine depuis laquelle on voudrait réparer. La porte manuelle n'est pas une précaution symbolique : c'est la seule chose qui garantit qu'un humain regarde le résultat de la validation avant que les zones ne partent.
La conséquence pratique est une règle sans exception : ces déploiements ne se lancent jamais à la main depuis un poste. Toujours par le pipeline, même quand on est pressé — et surtout quand on est pressé.
Trois points qui ont déjà mordu
Le job enchaîne la rotation de la clé, la synchronisation des zones, puis l'injection de la clé côté client ACME. Le détail vit dans le workflow ; ce qui mérite d'être retenu tient en trois pièges :
- La clé est exclue de la synchronisation des zones et déployée par une étape distincte, parce que les deux ne suivent pas le même cycle de vie.
- La synchronisation supprime les fichiers orphelins. C'est voulu : quand un sous-réseau disparaît, sa zone inverse doit disparaître aussi, sinon le résolveur continue de servir une zone qui ne correspond plus à rien.
- Les journaux de zone ne sont pas exclus. Les exclure désynchronise les zones dynamiques, celles que le client ACME met à jour.
Basculer une version d'image
Le routeur télécharge lui-même l'image : il n'y a pas d'archive à transporter. Le tag, en revanche, ne vit pas dans un fichier de configuration mais dans la configuration persistante du routeur — la bascule est donc une transaction de configuration, pas une copie de fichier.
ansible-playbook image-update.yaml -i inventories/production --tags check
ansible-playbook image-update.yaml -i inventories/production --tags pull
ansible-playbook image-update.yaml -i inventories/production --tags apply
La phase finale se résume à poser le tag puis à valider la transaction. La validation arrête l'ancien conteneur et démarre le nouveau : la coupure DNS se compte en quelques secondes. Le détail des phases est dans la mise à jour des images.
Vérification après déploiement
La liste tient en sept points, et chacun vérifie quelque chose que les autres ne couvrent pas :
- une résolution publique, qui prouve que la vue autoritative répond ;
- une résolution interne, qui prouve que les zones locales sont chargées ;
- une résolution inverse, qui est celle qu'on oublie de tester et qui casse le plus souvent ;
- la version masquée, qui prouve que le durcissement est bien appliqué ;
- le contrôle de santé du conteneur, qui doit sortir en succès ;
- l'identité du processus, qui doit être non privilégiée avec le bon PID 1 ;
- les journaux, qui ne doivent montrer aucune erreur au démarrage.
Liens
- Code source : Dépôt bind9-hardened
- Image Docker : Docker Hub