Connexion à un service sur un hôte Docker à partir d'un conteneur Docker
Lorsque vous utilisez le mode de mise en réseau par défaut bridge , vous devez faire en sorte qu'un service de l'hôte Docker écoute (également) docker0.
Si vous avez un problème avec Docker et que vous effectuez une recherche sur Internet, vous tomberez presque certainement sur la question suivante : Comment puis-je me connecter à localhost ? Ce que les gens veulent dire, c'est : Comment puis-je me connecter à un service sur l'hôte Docker à partir d'un conteneur Docker . Lorsque j'ai commencé à utiliser Docker , j'ai également eu du mal à résoudre ce problème.
J'ai utilisé Linux, Ubuntu, et quand finalement host.docker.internal a été disponible pour Linux beaucoup de gens ont pensé que tous leurs problèmes étaient terminés. Mais rien n'a changé. Le problème reste le même.
Il existe de nombreux articles sur Internet à ce sujet, et celui-ci en est un autre. Je ne parlerai que du mode réseau par défaut bridge , et non du mode 'network=host'.
Outils utilisés
Dans cet article, j'utilise quelques outils en ligne de commande, que vous voudrez peut-être installer. Pour obtenir la dernière version, ou les ajouter au dépôt sur votre machine :
sudo apt update
netcat
Utilisé pour créer un service d'écoute.
sudo apt install netcat
netstat
Utilisé pour afficher les listeners sur l'hôte.
sudo install net-tools
nmap
Utilisé pour scanner (pour les ports ouverts).
sudo apt install nmap
Services d'écoute et mise en réseau
Créons un service d'écoute dans une fenêtre de terminal, pour écouter le port 2345 sur localhost, où localhost est 127.0.0.1. Nous ajoutons l'option -k (keep open), pour empêcher netcat de se terminer lorsque vous vous déconnectez :
netcat -k -l 127.0.0.1 2345
Vérifiez que nous avons un service netcat en écoute :
sudo netstat -tunlp
Ou, si vous avez une très longue liste :
sudo netstat -tunlp | grep 2345
Résultat :
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
...
tcp 0 0 127.0.0.1:2345 0.0.0.0:* LISTEN 2961983/netcat
...
Cela signifie que netcat est en écoute sur IP address 127.0.0.1 sur le port 2345.
Démarrez maintenant un conteneur Busybox Docker dans une autre fenêtre de terminal :
docker run -it --add-host=host.docker.internal:host-gateway --rm busybox
Et ensuite, dans le conteneur, essayez d'accéder à notre service d'écoute :
telnet host.docker.internal 2345
Résultat :
telnet: can't connect to remote host (172.17.0.1): Connection refused
Si telnet ne répond pas, vérifiez l'UFW.
Il peut également arriver que telnet se bloque et réponde après un temps très long avec :
telnet: can't connect to remote host (172.17.0.1): Connection timed out
Dans ce cas, la demande de Docker est probablement bloquée par UFW (Uncomplicated Firewall). Vous pouvez vérifier si UFW bloque la demande en regardant les entrées UFW dans /var/log/syslog. Dans une autre fenêtre de terminal, tapez ce qui suit :
tail -f /var/log/syslog | grep UFW
Dans le conteneur Docker , relancez la commande telnet . Si l'UFW bloque votre demande, vous verrez des messages comme :
Aug 11 09:48:05 myra kernel: [247791.035929] [UFW BLOCK] IN=docker0 OUT= PHYSIN=vethe3b7837 MAC=02:42:80:b6:f4:ea:02:42:ac:11:00:02:08:00 SRC=172.17.0.2 DST=172.17.0.1 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=9274 DF PROTO=TCP SPT=35690 DPT=2345 WINDOW=64240 RES=0x00 SYN URGP=0
L'information importante ici est :
SRC=172.17.0.2 <--- SOURCE
DST=172.17.0.1 <--- DESTINATION
SPT=35690 <--- SOURCE PORT
DPT=2345 <--- DESTINATION PORT
Notez que le port 2345 est bloqué par UFW. Vous pouvez faire l'une des deux choses suivantes :
- Désactiver UFW pendant ces tests, ou bien
- Ajouter une règle à UFW pour autoriser l'accès au port 2345 :
sudo ufw allow from 172.17.0.0/16 to any port 2345.
Cela permet aux réseaux 172.17.x.y d'accéder au port 2345.
Pour le moment, je vous suggère de désactiver l'UFW. Après avoir fait cela, telnet devrait répondre comme mentionné précédemment :
telnet: can't connect to remote host (172.17.0.1): Connection refused
Ok, alors pourquoi la connexion est refusée ?
Pour cela, vous devez comprendre quelques principes de base du réseau. Le service d'écoute dit qu'il écoute 'Local Address' : 127.0.0.1:2345. Et telnet dans le conteneur Docker dit : can't connect to remote host (172.17.0.1).
Notez que 127.0.0.1 et 172.17.0.1 sont sur des réseaux différents. Cela signifie qu'une connexion ne peut jamais être établie !
Extrait de l'article 'How to Connect to Localhost Within a Docker Container', voir les liens ci-dessous :
Exactement. Cela signifie que notre service d'écoute ne doit pas seulement écouter 127.0.0.1 mais aussi 172.17.0.1.
Redémarrons notre service d'écoute, et faisons en sorte qu'il écoute 172.17.0.1 :
netcat -l 172.17.0.1 2345
Démarrez à nouveau telnet dans le conteneur Docker :
telnet host.docker.internal 2345
Résultat :
Connected to host.docker.internal
Bingo ! Cela signifie que le service sur l'hôte Docker doit également écouter docker0. Vous pouvez taper du texte et il sera répercuté par netcat.
Nous avons également appris que host.docker.internal n'est PAS équivalent à localhost.
host.docker.internal est juste un nom pour docker0. Docker0 est une interface virtuelle, une bridge, créée par Docker, et host.docker.internal est le nom (de domaine) de la docker0 bridge. Tous les conteneurs Docker sont connectés par défaut au docker0 bridge . Bien que la IP address de la docker0 soit souvent la 172.17.0.1, elle peut être n'importe quoi dans la plage privée définie par la RFC 1918. C'est pourquoi il ne faut pas utiliser la IP address mais se référer à la docker0 comme host.docker.internal.
+-------------+ +-------------+
| container A | | container B |
+-------------+ +-------------+
| |
| |
+------------------------------+
+----| docker0 |---+
| +------------------------------+ |
| |
| Host |
| |
+---------------------------------------+
|
network
Encore une fois, comment puis-je me connecter à un service sur localhost à partir d'un conteneur Docker ?
Lorsque le service d'écoute était à l'écoute sur 127.0.0.1 , nous ne pouvions pas l'atteindre depuis notre conteneur docker. Mais lorsque le service d'écoute était à l'écoute sur 172.17.0.1 , nous pouvions nous connecter depuis notre conteneur Docker.
En d'autres termes, la cause du problème est que le service sur l'hôte n'écoute PAS sur 172.17.0.1.
Dans Linux, la plupart des services peuvent écouter toutes les interfaces, tous les réseaux, en utilisant l'adresse '0.0.0.0'. Nous pouvons lancer netcat avec ou sans IP address '0.0.0.0', c'est la même chose :
netcat -l 2345
ou,
netcat -l 0.0.0.0 2345
Vérification des ports d'écoute
sudo netstat -tunlp | grep 2345
Résultat :
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
...
tcp 0 0 0.0.0.0:2345 0.0.0.0:* LISTEN 1606615/netcat
...
Local Address '0.0.0.0' signifie qu'il écoutera tous les réseaux, y compris 127.0.0.1 et 172.17.0.1.
Démarrez telnet dans le conteneur :
telnet host.docker.internal 2345
Résultat :
Connected to host.docker.internal
Comme prévu, la connexion a réussi.
Bien, mais que dois-je faire pour me connecter à un service sur localhost à partir d'un conteneur Docker ?
Il s'agit de s'assurer que le service sur localhost écoute (également) 172.17.0.1, ou docker0. Sur Ubuntu, la IP address de docker0 est 172.17.0.1, mais Docker dit qu'elle peut aussi être choisie au hasard, voir aussi ci-dessus.
Cela signifie que redémarrer Docker peut donner un docker0 IP address différent, peut-être pas aujourd'hui, mais peut-être dans quelques mois, ou l'année prochaine( ?). Comme nous ne pouvons pas nous fier à la docker0 IP address, la seule autre option est de faire en sorte que votre service sur l'hôte écoute la '0.0.0.0'. Il est possible de définir les bridge IP address et subnet, mais cela dépasse le cadre de cet article.
Pour accéder à MySQL sur l'hôte Docker depuis un conteneur, vous pouvez modifier une ligne dans /etc/mysql/my.cnf :
From :
bind-address = 127.0.0.1
A :
bind-address = 0.0.0.0
Notez qu'il existe des solutions sur Internet qui font que le service sur l'hôte Docker écoute sur l'hôte docker0 IP address. Ils comptent sur le fait que cette IP address ne changera pas. A moins que vous ne sachiez ce que vous faites, je ne peux pas vous le recommander.
Créer un réseau Docker distinct pour le service sur l'hôte
Vous pouvez isoler le service sur l'hôte en créant un réseau Docker pour lui, par exemple :
docker network create --driver=bridge --subnet=172.17.33.0/24 --gateway=172.17.33.10 my_subnet
Démarrez le service sur l'hôte :
netcat -k -l 172.17.33.10 2345
Puis démarrez le conteneur Docker :
docker run -it --add-host=my_host:172.17.33.10 --network=my_subnet --rm busybox
Et accéder au service depuis le conteneur Docker :
telnet my_host 2345
Accéder à un service sur l'hôte Docker en utilisant un Unix socket
Enfin, il existe un moyen totalement différent d'accéder à un service sur l'hôte Docker , mais cela dépend de la prise en charge de ce service. Certains services utilisent un 'unix socket' ou un fichier. Par exemple, MySQL utilise le fichier :
/run/mysqld/mysqld.sock
Cela signifie que nous pouvons également accéder à MySQL depuis un conteneur en utilisant le mappage de volume, dans Docker-Compose :
volumes:
# connect to mysql via unix socket
- /var/run/mysqld:/var/run/mysqld
Cela fonctionne bien. J'ai des applications sur l'hôte et des applications dans le conteneur Docker , toutes utilisant MariaDb sur l'hôte.
Docker IP addresses et règles UFW
Ce point est important car vous utilisez probablement l'UFW pour contrôler l'accès depuis et vers la machine. Comme mentionné ci-dessus, chaque fois qu'un conteneur est créé, la IP address peut changer et certaines règles UFW ne fonctionneront plus !
Par défaut, Docker attribue un IP address à un conteneur provenant de l'une des plages de réseaux suivantes pour les réseaux bridge :
- 172.17.0.0/16
- 172.18.0.0/16
- 172.19.0.0/16
- 172.20.0.0/14
- 172.24.0.0/14
- 172.28.0.0/14
- 192.168.0.0/16
Cela signifie que si nous avons spécifié une règle UFW avec '172.17.0.0/16', cette règle ne fonctionnera plus si Docker assigne la fois suivante un IP address du réseau '192.168.0.0/16'.
Une façon de résoudre ce problème est de spécifier nous-mêmes les pools d'adresses par défaut pour les réseaux Docker , comme je l'ai décrit dans le post 'Docker containers suddenly using 192.168.0.0/16 instead of 172.17.0.0/16: services lost', voir les liens ci-dessous.
Nous pouvons spécifier des pools d'adresses par défaut pour nos réseaux Docker en créant le fichier (il n'était pas là) :
/etc/docker/daemon.json
Cet exemple indique à Docker de n'utiliser que IP address du réseau 172.17.0.0/16 :
{
"default-address-pools":
[
{"base":"172.17.0.0/16","size":24}
]
}
Analysez toujours vos ports après avoir effectué des modifications !
Après avoir effectué des changements, scannez toujours les ports de votre machine et du serveur de production. En particulier, lorsque vous utilisez Docker , vous devez vous assurer qu'aucun port n'a été ouvert par erreur !
Si nous voulons le faire nous-mêmes, nous pouvons utiliser la commande nmap . Le problème est que les résultats dépendent de la façon dont vous vous connectez à Internet. Vous devez vérifier la firewall sur votre machine et la firewall sur votre point d'accès Internet. De plus, le firewall de votre fournisseur d'accès Internet peut bloquer les connexions. La seule façon d'analyser les ports ouverts de votre serveur de production est d'utiliser un autre serveur (VPS) sur le même réseau, ou sur Internet, qui n'a pas de restrictions de sortie.
sudo apt install nmap
Un exemple de cette commande pour scanner les ports 80-10000 :
nmap -p 80-10000 www.example.com
Vous pouvez également utiliser le IP address de votre machine de développement, et exécuter nmap sur cette machine, mais j'ai constaté que les résultats ne sont pas corrects.
nmap -p 80-10000 <your-development-machine-ip-address>
Il a montré que le port 0.0.0.0:2345 était ouvert. L'analyse à partir d'un autre ordinateur de mon réseau local n'a pas montré cela.
Résumé
Dans cet article, j'ai expliqué comment vous pouvez vous connecter à un service sur un hôte Docker à partir d'un conteneur Docker . Il y a des limitations mais la plupart du temps, c'est possible. Faites attention aux Docker assignées, elles peuvent changer à chaque fois qu'un conteneur est créé. Et vérifiez toujours les ports ouverts de vos systèmes après avoir effectué des modifications !
Liens / crédits
Change default docker0 bridge ip address
https://stackoverflow.com/questions/52225493/change-default-docker0-bridge-ip-address
Connecting Docker to localhost PostgreSQL on an Ubuntu 18.04 Host
https://gregtczap.com/blog/docker-postgres-ubuntu-localhost
Docker containers suddenly using 192.168.0.0/16 instead of 172.17.0.0/16: services lost
https://www.peterspython.com/en/blog/docker-containers-suddenly-using-192-168-0-0-16-instead-of-172-17-0-0-16-services-lost
From inside of a Docker container, how do I connect to the localhost of the machine?
https://stackoverflow.com/questions/24319662/from-inside-of-a-docker-container-how-do-i-connect-to-the-localhost-of-the-mach
How to Connect to Localhost Within a Docker Container
https://www.howtogeek.com/devops/how-to-connect-to-localhost-within-a-docker-container
What's the `docker0` there?
https://stackoverflow.com/questions/63948879/whats-the-docker0-there
En savoir plus...
Docker
Récent
- Graphique de séries temporelles avec Flask, Bootstrap et Chart.js
- Utiliser IPv6 avec Microk8s
- Utilisation de Ingress pour accéder à RabbitMQ sur un cluster Microk8s
- Galerie vidéo simple avec Flask, Jinja, Bootstrap et JQuery
- Planification de base des tâches avec APScheduler
- Un commutateur de base de données avec HAProxy et HAProxy Runtime API
Les plus consultés
- Utiliser PyInstaller et Cython pour créer un exécutable Python
- Réduire les temps de réponse d'un Flask SQLAlchemy site web
- Utilisation des Python's pyOpenSSL pour vérifier les certificats SSL téléchargés d'un hôte
- Connexion à un service sur un hôte Docker à partir d'un conteneur Docker
- Utiliser UUIDs au lieu de Integer Autoincrement Primary Keys avec SQLAlchemy et MariaDb
- SQLAlchemy : Utilisation de Cascade Deletes pour supprimer des objets connexes