.docker

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.