Isoler l'environnement de développement apporte deux avantages critiques : jeter et recréer l'environnement sans toucher à sa station de developpement ; lancer plusieurs environnement de developpement / test en parallèle.
Docker Compose est ma technologie d'isolation d'env de dév préférée. J'entends l'arrivée de Kubernetes. Pour 2018, je reste sur Docker Compose en natif sur ma station.
Docker Compose est bien plus efficace que Vagrant. La configuration YAML est simple, bien plus que pour Kubernetes. Les performances sont meilleures. L'immutabilité rends les environnements réellement jetables. Monter le code édité directement dans le conteneur, c'est parfait. Malgré les efforts d'HashiCorp, connaître Vagrant ne sert pas beaucoup en production. Tandis que connaître les contraintes de Docker est un vrai plus.
Comme toujours, l'outils est pleins de potentiels, mais il faut huiler un peu tout ça. En premier lieu : le changement d'adresse IP. Comment retrouver l'IP de mes conteneurs ?
L'état de l'art
Docker s'est penché sur le problème et l'a découpé en deux. Entre conteneur d'un
même projet Compose, tout les conteneurs sont résolvables par leur nom. Ainsi le
service postgres
sera accessible avec PGHOST=postgres
.
Reste une autre problème : comment accéder au conteneur de l'extérieur ? La
solution officielle est d'exposer un port. Cette solution alloue un port de la
machine hôte (votre station de développement) et redirige vers un port du
conteneur exposé. Lorsque j'expose le port 5432 de mon conteneur postgres
, je
peux interroger ce conteneur avec PGHOST=localhost
.
Voici un petit scénario d'exemple :
$ cat docker-compose.yml
version: "2"
services:
postgres:
image: postgres:10-alpine
ports: [5432:5432]
shell:
image: alpine:latest
command: ["tail", "-f", "/dev/null"]
$ docker-compose up -d
Creating expose_shell_1 ... done
Creating expose_postgres_1 ... done
$ docker-compose ps
Name Command State Ports
----------------------------------------------------------------------------------
expose_postgres_1 docker-entrypoint.sh postgres Up 0.0.0.0:5432->5432/tcp
expose_shell_1 tail -f /dev/null Up
$ psql -h $(hostname -i) -p 5432 -U postgres -c 'SELECT version();'
version
---------------------------------------------------------------------------------------
PostgreSQL 10.3 on x86_64-pc-linux-musl, compiled by gcc (Alpine 6.4.0) 6.4.0, 64-bit
(1 ligne)
$ docker-compose exec shell getent hosts postgres
172.19.0.2 postgres postgres
$
Ça ne va pas !
Tant qu'on a qu'un Postgres, ça va. J'utilise le port 5432 de ma machine et je suis bon.
Or, j'utilise Postgres pour plusieurs projets : ldap2pg, temBoard, etc. Et pour chaque projet, j'apprécie d'avoir un environnement de développement, un environnement de test pour relire les PR et un pour reproduire rapidement un bug. Ça fait beaucoup de monde pour un seul port !
Pourquoi devrais-je éteindre les autres projets avant de passer à un autre ? Une solution est d'allouer un port différent à chaque projet : l'un sur 5432, l'autre sur 15432, le dernier sur 5433. Quand c'est géré automatiquement en prod, c'est très bien. Mais il faut reconnaître ses limites pour le développement. Ça devient vite la guerre des ports.
Je n'ai pas envie de chercher le port 5433 ou 15432. Avec cette solution, je galère presque autant que pour chercher l'IP du conteneur et interroger directement le conteneur.
En fait, résoudre une adresse IP avec un nom mémorisable et stable, ça existe depuis longtemps : ça s'appelle DNS. Plusieurs solutions existent pour résoudre les conteneurs en DNS. Je vais vous présenter dnsdock.
Le domaine .docker
dnsdock est un petit serveur DNS, qui résouds les noms de domaines en regardant ce qui se passe dans le moteur Docker.
S'il y a bien un service dont on veut une IP stable, c'est bien le serveur DNS ! On va donc exposer le port 53 de ce conteneur 53 sur une IP stable, accessible à notre dnsmasq central. Prenons par exemple l'IP du moteur Docker lui-même.
$ docker run --detach --restart always -v /var/run/docker.sock:/var/run/docker.sock --name dnsdock -p 172.17.0.1:53:53/udp aacebedo/dnsdock
$ host dnsdock.docker 172.17.0.1
Using domain server:
Name: 172.17.0.1
Address: 172.17.0.1#53
Aliases:
dnsdock.docker has address 172.17.0.2
dnsdock.docker mail is handled by 0 dnsdock.docker.
$
Chic ! Reste plus qu'à indiquer à notre dnsmasq d'orienter toutes les demandes
de résolution de .docker
vers cette IP :
# cat >> /etc/dnsmasq.d/docker.conf
server=/docker/172.17.0.1
^D
# systemctl restart dnsmasq
# getent hosts dnsdock.docker
172.17.0.2 dnsdock.docker
#
Bingo ! Et comme notre dnsmasq est utilisé par nos
conteneurs, on peut résoudre le domaine .docker
dans les conteneurs également !
$ docker run --rm -it --name toto debian:jessie getent hosts dnsdock.docker
172.17.0.2 dnsdock.docker
$
Paramétrer le nom de domaines des conteneurs
Docker Compose nomme un conteneur à partir du nom du projet compose, du nom du
service et un numéro. Par exemple : temboard_postgres_1
. On peut donc résoudre
temboard_postgres_1.docker
. dnsdock résoud aussi le nom de l'image, par
exemple : postgres.docker
retourne l'IP de tout les conteneurs postgres
.
Vous pouvez ajouter vous-même des alias (CNAME en terme DNS) en ajoutant des
labels
au conteneur.
$ docker run --rm -it --detach --label com.dnsdock.alias=madebian.docker debian:jessie tail -f /dev/null
f09517b55898776149db53b302920a538cad1afd0793476ba991e786af075725
$ getent hosts madebian.docker
172.17.0.5 madebian.docker
$ docker kill f09517b55898776149db53b302920a538cad1afd0793476ba991e786af075725
Donc en pratique, j'ajoute des
alias
dans les docker-compose.yml
des projets pour rendre ça plus mémorisable. On
peut utiliser la variable COMPOSE_PROJECT_NAME
comme sous domaine et ainsi
avoir un domaine par projet compose !
Conclusion
Cette solution ne fonctionne qu'avec Docker en natif et non avec Docker Machine.
dnsmasq
et dnsdock
permettent d'oublier que le conteneur est jetable et de
travailler en parrallèle sur plusieurs projets. Comment faire ça avec minikube ?
Le domaine .docker
redonne de la stabilité à la configuration de
développement, le vrai point faible du développement avec docker.