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 ! 😉