Aller au contenu

Problèmes avec le Docker Network Bridge

·1187 mots·6 mins·
Anup
Auteur
Anup
Sommaire
Photo d'illustration de l'article de Shubham Dhage sur Unsplash
Résolution et apprentissage par l’exemple des problématiques d’adressage automatique des bridges crée par Docker.

Problématique
#

TL;DR
#

Par défaut, les conteneurs lancés sans configuration réseau particulière auront pour effet de créer un bridge et donc un sous-réseau (par stack). Ce dernier peut poser problème dans le cas où ces réseaux sont utilisés ailleurs.

On voit donc ici comment reprendre le contrôle dessus.

Blabla
#

J’ai remarqué que, depuis un moment, lorsque que me connecte en VPN, je n’arrive pas à accéder à une de mes machines. Cependant, j’y arrive en passant par une autre machine (en rebond)…
Cela est donc un problème de routage.

En VPN, j’utilise le WireGuard de ma Freebox qui me propose des IPs exclusivement sur le réseau 192.168.27.64/27.
Or, il s’avère que, sur cette fameuse machine, j’ai un bridge qui utilise l’adresse 192.168.16.1/20 !

Information

Network: 192.168.16.0/20
HostMin: 192.168.16.1
HostMax: 192.168.31.254
Broadcast: 192.168.31.255

De ce fait, un paquet vers mon réseau WireGuard ne sortira pas de ce serveur car j’ai déjà une route locale vers ce réseau !

ip route

[…]
192.168.16.0/20 dev br-75064ab12d5d proto kernel scope link src 192.168.16.1
[…]

Le coupable est Docker qui semble utiliser par défaut les sous-réseaux suivants pour ces bridges :

  • 172.17.[0-31].1/16
  • 192.168.[n*16].1/20 (192.168.[0,16,32,…,240].1/20)

dockerd est assez intelligent pour éviter les réseaux déjà utilisés sur l’hôte mais ne sait pas aller plus loin.

Expérimentation
#

Base
#

Afin de reproduire au plus prêt mon environnement de production, j’ai monté une VM Ubuntu 20.04 LTS sur mon PC (VirtualBox).

J’ai installé Docker et le plugin Docker Compose afin de pouvoir créer de multiple conteneur dans le but de simuler plusieurs applications qui tournent en même temps.

Pour cela, j’ai choisis de lancer des conteneurs Nginx. De cette manière, il sera facile de vérifier le fonctionnement des services en utilisant un navigateur Web.

Information

Pour l’ensemble des tests, j’ai tous fait en utilisateur root :

sudo -i

Voici comment j’ai généré 40 conteneurs Nginx en Docker Compose :

mkdir -pv /srv/dockers
cd /srv/dockers

for i in $(seq 8080 8120); do \
  echo $i; \
  mkdir -pv ${i}; \
  echo "services:" > ./${i}/compose.yml; \
  echo "  nginx_${i}:" >> ./${i}/compose.yml; \
  echo "    image: nginx:latest" >> ./${i}/compose.yml; \
  echo "    container_name: nginx_${i}" >> ./${i}/compose.yml; \
  echo "    restart: unless-stopped" >> ./${i}/compose.yml; \
  echo "    ports:" >> ./${i}/compose.yml; \
  echo "      - ${i}:80" >> ./${i}/compose.yml; \
done

Remarque

Ici, on crée donc 40 dossiers selon un nommage allant de 8080 à 8120 avec, dans chacun d’entre eux, un fichier Docker Compose d’un conteneur Nginx qui publie sur le port équivalent au nom du dossier.

N’ayant aucune spécification network:, Docker va donc crée un réseau en bridge pour chaque stack.

Test 1
#

On va bêtement lancer tous les conteneurs et voir ce qui se passe :

cd /srv/dockers
for i in $(seq 8080 8120); do \
  cd $i; \
  pwd; \
  docker compose up -d; \
  cd ..; \
done

On voit que ça bloque lors de la création du réseau 8109_default car il n’y a plus de sous-réseau disponible via la configuration de base du daemon Docker (dockerd).

Pour visualiser les interfaces :

ip a | grep "br-" | grep inet

Avant de passer à la suite, on détruit tous les conteneurs :

cd /srv/dockers
for i in $(seq 8080 8120); do \
  cd $i; \
  pwd; \
  docker compose down; \
  cd ..; \
done

Test 2
#

La documentation Docker présente des paramètres qu’on peut utiliser pour contrôler notre bridge.

Je les mets donc en place :

cat << EOF > /etc/docker/daemon.json
{
  "bip": "10.20.1.1/16",
  "fixed-cidr": "10.20.1.0/17"
}
EOF

Et on applique :

systemctl restart docker

En faisant un ip a show docker0, on voit bien que notre bridge docker0 à bien pris la nouvelle adresse.

Ainsi, on peut donc relancer nos conteneurs pour voir mais faisons-le avec un seul pour le moment :

cd /srv/dockers/8080/
docker compose up -d

En regardant l’adresse IP du bridge qui s’est créé :

ip a | grep "br-" | grep inet

inet 172.17.0.1/16 brd 172.17.255.255 scope global br-9eaa90793383

On se rend compte qu’il n’utilise pas le réseau que nous avons décrit dans le fichier de configuration.

Par curiosité, j’ai fait quelques tests qui sont tous fonctionnels :

curl http://127.0.0.1:8080
curl http://10.20.1.1:8080
curl http://172.17.0.1:8080

Mauvaise piste, cela ne règle donc pas notre problème…

On coupe donc notre service avant de commencer un nouveau test :

cd /srv/dockers/8080/
docker compose down

Test 3
#

Je n’ai rien trouvé de probant pour régler mon problème sur la documentation Docker.
Cependant, j’ai trouvé la mention du paramètre default-address-pools sur leur forum.

On va donc tester cela !

cat << EOF > /etc/docker/daemon.json
{
    "default-address-pools": [
        { "base":"10.20.0.0/16","size":24 }
    ]
}
EOF

systemctl restart docker
ip a show docker0

Bizarrement, le bridge par défaut n’a pas changé d’adresse et est resté en 10.20.0.1/24

On relance donc tous les conteneurs :

cd /srv/dockers
for i in $(seq 8080 8120); do \
  cd $i; \
  pwd; \
  docker compose up -d; \
  cd ..; \
done

Et là tous se lance bien ! On a bien tous les réseaux (ip a | grep "br-" | grep inet).

J’en déduis que, le pool d’adresse défini sur la base de 10.20.0.0/16 avec une taille de 24 permet donc a chaque bridge crée d’avoir un sous-réseau en /24 compris dans le /16.
Les bridges créent utiliseront donc les réseaux 10.20.[0..255].0/24 soit assez pour créer 256 (8 bits entre 16 (/16) et 24 (/24)) bridges avec 254 (/24) conteneurs possibles par stack !

Application
#

Sur mon serveur de production, je reproduis donc la même chose :

Je down tous mes conteneurs :

cd /srv/dockers
for i in $(ls); do \
  cd $i; \
  pwd; \
  docker compose down; \
  cd ..; \
done

Remarque

Oui je sais, se code est dégueulasse !

Et je crée donc mon fichier (qui, pour ma part, n’existe pas) :

Avertissement

Attention, le bloc de code ci-dessous a pour effet d’écraser le contenu du fichier /etc/docker/daemon.json s’il existe.

Si c’est le cas pour vous, éditer simplement votre fichier en vous inspirant de ce qu’il y a ci-dessous.
C’est du JSON donc faite attention aux virgules !

cat << EOF > /etc/docker/daemon.json
{
  "bip": "10.20.1.1/16",
  "fixed-cidr": "10.20.1.0/17"
}
EOF

systemctl restart docker
cd /srv/dockers
for i in $(ls); do \
  cd $i; \
  pwd; \
  docker compose up -d; \
  cd ..; \
done

Remarque

Une fois de plus, oui, se code est dégueulasse !

On vérifie :

ip a | grep "br-" | grep inet

Et c’est OK !

Conclusion
#

Nous avons donc vu ici comment modifier l’adresse IP du bridge docker0 et de ceux dynamiquement créer.

Je ne comprends pas pourquoi cela n’est pas très bien documenté sur la documentation officielle.
Après quelques recherches, je l’ai trouvé ici, donc dans une partie IPv6 où je n’aurais jamais pensé chercher.
C’est vraiment dommage que cela ne soit pas plus simple à trouver…

Du coup, cela a bien réglé mon problème d’accès au travers du VPN !

En espérant que ça puisse en aider certains ! 😉