Noms d'hôte docker
Utilisant Docker quotidiennement et travaillant sur plusieurs projets, je souhaitais harmoniser les configurations et utiliser des noms de domaines locaux pour accéder à mes conteneurs.
Il y a plusieurs solutions existantes pour faire ça, comme Traefik ou DNS Proxy Server.
Traefik
fonctionne bien, apporte beaucoup de fonctionnalités, mais n'est pas très léger pour une utilisation sur un poste de travail.
DNS Proxy Server
fonctionne pas trop mal mais pose problème sur la résolution des noms de domaines externes aux conteneurs. Un simple ping google.fr
ne passe pas si on utilise pas le réseau en mode bridge.
Ne trouvant pas mon bonheur dans ces solutions, j'ai décidé de créer un petit script shell qui se chargerai de maintenir à jour le fichier /etc/hosts
.
Le principe est simple : écouter les événements de Docker lorsque des conteneurs sont démarrés, arrêtés, détruits ou tués, et mettre à jour le fichier /etc/hosts
avec la liste des conteneurs en fonctionnement, en se basant sur la configuration hostname des conteneurs et leur IP assignée.
Écouter les événements Docker
Créons un script bash :
echo '!#/bin/bash' > docker-hosts.sh && chmod +x docker-hosts.sh
Pour écouter les événements Docker, j'ai trouvé ce gist que j'ai adapté :
function listen_docker_events() {
docker events --filter 'event=start' --filter 'event=stop' --filter 'event=kill' --filter 'event=destroy' | while read event
do
#update_hosts
done
}
Ajoutons des marqueurs d'emplacement dans /etc/hosts
afin de cloisonner la mise à jour des IPs. J'ai mis des marqueurs largement inspirés des marqueurs des recettes flex.
###> docker-hosts ###
###< docker-hosts ###
Dès qu'un conteneur est démarré ou arrêté, on mettra à jour la liste de correspondance des IPs et des noms d'hôtes dans notre fichier /etc/hosts
.
Mise à jour de /etc/hosts
Pour la mise à jour du fichier /etc/hosts
nous allons créer une fonction nommée update_hosts
.
function update_hosts() {
CONTAINERS=$(docker ps -q | awk '{ print $1 }')
HOST_PLACEHOLDER_START='###> docker\-hosts ###'
HOST_PLACEHOLDER_END='###< docker\-hosts ###'
HOST_ITEMS=''
while read -r CONTAINER; do
HOST_ITEMS+=$(echo -e $(docker inspect --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}\t\t{{ .Config.Hostname }}' $CONTAINER))
HOST_ITEMS+='\n'
done <<< "$CONTAINERS"
sed -i -e "/${HOST_PLACEHOLDER_START}/,/${HOST_PLACEHOLDER_END}/c\\${HOST_PLACEHOLDER_START}\n${HOST_ITEMS}${HOST_PLACEHOLDER_END}" /etc/hosts
}
Décomposons cette fonction :
CONTAINERS=$(docker ps -q | awk '{ print $1 }')
On récupère la liste des identifiants des conteneurs en cours de fonctionnement et on la stocke dans la variable CONTAINERS
.
HOST_PLACEHOLDER_START='###> docker\-hosts ###'
HOST_PLACEHOLDER_END='###< docker\-hosts ###'
HOST_ITEMS=''
On définis les marqueurs qui serviront au remplacement et la variable qui contiendra la liste de correspondance des IPs et des noms d'hôte (HOST_ITEMS
).
while read -r CONTAINER; do
# ...
done <<< "$CONTAINERS"
On boucle sur la liste des identifiants des conteneurs en cours de fonctionnement.
HOST_ITEMS+=$(echo -e $(docker inspect --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}\t\t{{ .Config.Hostname }}' $CONTAINER))
À l'aide de la commande docker inspect
, on récupère les informations utiles de chaque conteneur : Adresse IP et nom d'hôte à l'aide du format suivant :
{{ if gt (len .NetworkSettings.Networks) 1 }}
{{ range \$name,\$network := .NetworkSettings.Networks }}
{{ \$hasUnderscore := gt (len (split \$name "_")) 0 }}
{{ if \$hasUnderscore }}
{{ range \$part := (split \$name "_") }}
{{ if eq \$part "default" }}
{{ \$network.IPAddress }}
{{ end }}
{{ end }}
{{ else }}
{{ .NetworkSettings.Networks.IPAddress }}
{{ end }}
{{ end }}
{{ else }}
{{ range .NetworkSettings.Networks }}
{{ .IPAddress }}
{{ end }}
{{ end }}
\t\t
{{ .Config.Hostname }}
On remplace tout le contenu situé entre le marqueur de début ###> docker-hosts ###
et le marqueur de fin ###< docker-hosts ###
par la liste des IPs et noms d'hôtes de la variable HOST_ITEMS
.
sed -i -e "/${HOST_PLACEHOLDER_START}/,/${HOST_PLACEHOLDER_END}/c\\${HOST_PLACEHOLDER_START}${HOST_ITEMS}${HOST_PLACEHOLDER_END}" /etc/hosts
/etc/hosts
n'est inscriptible qu'en ayant les droits root, on pourra tester le script en essayant sur un fichier host de test situé dans le même répertoire que le script : cat /etc/hosts > ./hosts
Ensuite, on remplace dans la commande sed
le fichier sur lequel le remplacement se fera :
sed -i -e "[...]" ./hosts
Déclarer le daemon
Déclarons un service systemd pour démarrer automatiquement ce script lors du démarrage de l'OS.
# /etc/systemd/system/docker-host.service
[Unit]
Description=Docker host daemon
After=docker.service
[Service]
ExecStart=/bin/bash <CHEMIN_VERS_VOTRE_SCRIPT>/docker-hosts.sh
Restart=on-failure
[Install]
WantedBy=multi-user.target
On active ensuite le service et on le lance :
sudo systemctl enable docker-host.service
sudo systemctl start docker-host.service
Exemple avec docker-compose
On utilise un docker-compose.yml
avec 2 services basiques :
version: '3'
services:
hello-world-1:
image: strm/helloworld-http
container_name: hello-world-1
hostname: hello-world-1.papsou
hello-world-2:
image: strm/helloworld-http
container_name: hello-world-2
hostname: hello-world-2.papsou
Notre fichier /etc/hosts
ressemble à ça avant le lancement :
# /etc/hosts
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
# Some fixed IP
# [...]
###> docker-hosts ###
###< docker-hosts ###
On lance ces 2 services :
docker-compose up -d --build
Creating hello-world-1 ... done
Creating hello-world-2 ... done
Et voici le fichier après le lancement :
# /etc/hosts
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
# Some fixed IP
# [...]
###> docker-hosts ###
192.168.48.3 hello-world-2.papsou
192.168.48.2 hello-world-1.papsou
###< docker-hosts ###
On accède donc au premier service hello-world-1
en se rendant sur http://hello-world-1.papsou
et sur hello-world-2
en se rendant sur http://hello-world-2.papsou
.
On coupe les services :
docker-compose down && cat /etc/hosts
Stopping hello-world-2 ... done
Stopping hello-world-1 ... done
Removing hello-world-2 ... done
Removing hello-world-1 ... done
Removing network hello-world_default
Et voici le contenu de /etc/hosts
après la coupure :
# /etc/hosts
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
# Some fixed IP
# [...]
###> docker-hosts ###
###< docker-hosts ###
Le script final
#!/bin/bash
function update_hosts() {
CONTAINERS=$(docker ps -q | awk '{ print $1 }')
HOST_PLACEHOLDER_START='###> docker\-hosts ###'
HOST_PLACEHOLDER_END='###< docker\-hosts ###'
HOST_ITEMS=''
INSPECT_FORMAT_STRING=$(cat << EOT
{{ if gt (len .NetworkSettings.Networks) 1 }}
{{ range \$name,\$network := .NetworkSettings.Networks }}
{{ \$hasUnderscore := gt (len (split \$name "_")) 0 }}
{{ if \$hasUnderscore }}
{{ range \$part := (split \$name "_") }}
{{ if eq \$part "default" }}
{{ \$network.IPAddress }}
{{ end }}
{{ end }}
{{ else }}
{{ .NetworkSettings.Networks.IPAddress }}
{{ end }}
{{ end }}
{{ else }}
{{ range .NetworkSettings.Networks }}
{{ .IPAddress }}
{{ end }}
{{ end }}
\t\t
{{ .Config.Hostname }}
EOT
)
while read -r CONTAINER; do
HOST_ITEMS+=$(echo -e $(docker inspect --format "${INSPECT_FORMAT_STRING}" $CONTAINER))
HOST_ITEMS+='\n'
done <<< "$CONTAINERS"
sed -i -e "/${HOST_PLACEHOLDER_START}/,/${HOST_PLACEHOLDER_END}/c\\${HOST_PLACEHOLDER_START}\n${HOST_ITEMS}${HOST_PLACEHOLDER_END}" /etc/hosts
}
function listen_docker_events() {
docker events --filter 'event=start' --filter 'event=stop' --filter 'event=kill' --filter 'event=destroy' | while read event
do
update_hosts
done
}
listen_docker_events