Docker et les réseaux

Docker est probablement l’une des « technologies » les plus importantes de ces dernières années. Bien que s’appuyant sur des briques et des principes qui existaient depuis longtemps (en particulier LXC), l’équipe de Docker a réussi à s’imposer là ou tous les autres ont échoué, en grande partie grâce à sa simplicité (de façade).

Mais en essayant de tout simplifier pour les débutants ou les développeurs, ils ont parfois fait des choix techniques plus que discutables.

Pour un développeur qui travail dans son coin ce n’est pas gênant et ça rend les choses plus simples, mais quand vient l’heure de la mise en production, de gros problèmes peuvent survenir.

Heureusement on peut maintenant (ça n’a pas toujours été le cas) changer tout ou partie de ce comportement, c’est ce qu’on va voir ici.

C’est le cas par exemple avec la configuration et le comportement réseau par défaut, c’est une horreur :

  • il ajoute ses propres chaines iptables à Netfilter sans tenir compte de l’existant :
    • il part donc du principe qu’on utilise iptables et pas nftables par exemple
    • si on doit modifier nos règles, il faut faire attention à ne pas supprimer celles de Docker
    • certaines règles de Docker peuvent créer des trous de sécurité
  • il NAT tout le trafic IPv4
    • c’est plus lent
    • ça empêche plusieurs protocoles de fonctionner
  • il change vos options de routage en IPv6
    • activation du routage sur toutes les interfaces
    • et ajout de routes
  • il lance son « docker-proxy » pour gérer le trafic loopback :
    • lancé en user-land, donc c’est généralement plus lent
    • et on perd tout contrôle sur une partie du trafic
  • il scan vite fait/mal fait le réseau pour s’allouer un /16 (généralement le 172.17.0.0/16) sans tenir compte du reste de votre réseau :
    • si vous utilisez ce plan d’adressage ailleurs dans votre réseau, certains conteneurs ne fonctionneront pas
  • on ne peut pas configurer les ip des conteneurs :
    • ça complexifie la gestion des règles de sécurité
    • idem si vous souhaitez utiliser un loadbalancer ou un reverse proxy

La liste est trop longue pour relever tout ce qui ne peut pas fonctionner dans un réseau d’entreprise avec la configuration par défaut.

Pour ma part, j’aurai préféré que Docker ne s’occupe pas du tout de la couche réseau ou face le minimum nécessaire.

Pour la suite les modifications seront valables pour une Debian 9.1 avec systemd, mais ça devrait être transposable facilement sur votre distribution (je n’ai pas regardé le comportement ni la configuration sur MacOS ou Windows par contre). Pour simplifier la lecture j’ai supprimé les réglages IPv6 car le fonctionnement est identique.

Paramètres de lancement

La première chose à faire est de désactiver tout ou partie de ce qui touche au réseau lors du lancement de docker.

mkdir /etc/systemd/system/docker.service.d

Puis on créé un fichier pour modifier les options de lancement :

[Service]
ExecStart=
ExecStart=/usr/bin/dockerd -H fd:// --iptables=false --ip-forward=false --ip-masq=false --userland-proxy=false --bip=10.0.1.1/24 --fixed-cidr=10.0.1.0/24 --dns=10.0.0.1 --dns-search=example.fr

Vous trouverez le détails des options ici : https://docs.docker.com/engine/reference/commandline/dockerd/

On demande à systemd de prendre en compte les changements et on relance Docker :

systemctl daemon-reload
systemctl restart docker.service

Les conteneurs lancés sans options réseau auront une adresse en 10.0.1.0/24 en bridge sur l’interface docker0.

Pour information, il n’est pas possible de spécifier l’adresse d’un conteneur dans le mode par défaut. Pour trouver l’adresse de votre conteneur, vous pouvez utiliser docker inspect :

docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' $ID

Avec $ID l’identifiant (ou le nom) de votre conteneur.

À vous de créer les règles de NAT nécessaires pour rendre votre conteneur accessible depuis l’extérieur.

Création d’un réseau

Dans le mode par défaut (comme ci dessus), les conteneurs ne sont pas accessibles directement par les autres machines du réseau, il faut passer par du NAT. De même, on ne peut pas leur affecter une adresse IP prédéterminée.

On va donc créer un « réseau docker ».

Docker permet de créer et d’utiliser plus types de réseaux (parfois avec des variantes) :

  • none : pas de réseau (sauf l’interface loopback)
  • host : on partage la pile IP de l’hôte (donc les ports …)
  • default : l’interface du conteneur est en bridge sur l’interface de docker
  • bridge : idem sauf qu’on peut spécifier l’interface
  • overlay : c’est aussi un bridge mais partagé entre plusieurs hôtes (VXLAN)
  • macvlan : cette fois ci l’hôte partage son interface avec les conteneurs (l’interface porte plusieurs adresses mac)
  • ipvlan L2 : idem mais ne nécessite pas plusieurs adresses mac
  • ipvlan L3 : ici le trafic est routé par l’interface parente

Chacun des modes de fonctionnement présente des avantages et des inconvénients, il convient donc de bien connaitre ses besoins, ceux des conteneurs et le fonctionnement des différents modes.

Avec mode macvlan par exemple, les conteneurs ne peuvent pas communiquer avec l’hôte (du moins pas directement) car le noyau Linux n’aime pas avoir plusieurs adresses mac sur une même interface.

Vous trouverez plus de détails ici : https://hicu.be/

Pour l’exemple on va créer un réseau de type bridge (c’est le mode par défaut) :

docker network create --subnet 10.0.2.0/24 --gateway=10.0.2.1 --ip-range 10.0.2.128/25 dockerbridge

Pour créer un réseau de type macvlan, il suffit d’ajouter le l’option –driver :

docker network create --driver macvlan --subnet 10.0.3.0/24 --gateway=10.0.3.1 --ip-range 10.0.3.128/25 dockermacvlan

On peut aussi attacher ce réseau à une interface en particulier (par défaut c’est l’interface docker0 qui est utilisée) avec -o parent=<interface>, par exemple :

docker network create --driver macvlan --subnet 10.0.4.0/24 --gateway=10.0.4.1 --ip-range 10.0.4.128/25 -o parent=dummy0 dockermacvlan

Et pour créer un conteneur dans l’un de ces réseaux, c’est facile :

docker run -d --rm --ip 10.0.2.142 --network dockerbridge image

Ici le conteneur sera dans le réseau dockerbridge et aura une ip fixe (sinon il aurait eu la première IP disponible du range 10.0.2.128/25).

Il y aurait beaucoup de choses à ajouter sur la configuration réseau de Docker, de prime abord elle est pénible voir bloquante pour beaucoup d’usage, mais avec très peu de configuration on peut arriver à un résultat satisfaisant.