Aller au contenu
English
Compte : non connecté, synchronisation possible Mon apprentissage
Selectionner le thème
English

Isoler un conteneur du réseau : none ou réseau interne

Par mis à jour le

Améliorer cette page

Logo Docker

Deux demandes arrivent le même matin. La sécurité veut qu'un conteneur qui traite des fichiers de clients ne puisse rien joindre : ni l'extérieur, ni l'hôte, ni ses voisins. L'équipe applicative veut que sa base de données soit joignable par son application seulement, et par son nom. Les deux demandes disent « isolez-le », et ce n'est pas la même chose.

Un conteneur sans aucun réseau rend la base inutilisable. Un réseau confiné, lui, laisse encore passer les voisins. Cette page vous apprend à poser chaque isolation avec docker run --network none et docker network create --internal. Vous vérifiez ensuite, depuis l'intérieur du conteneur, qu'elle tient. C'est plus fiable que de relire la configuration.

  • Distinguer les deux isolations : aucun réseau, ou un réseau qui ne sort pas
  • Créer un réseau interne avec le sous-réseau de votre choix (la plage d'adresses que le réseau distribue à ses membres)
  • Constater l'absence de route qui fait l'isolation, au lieu de la croire
  • Repérer le piège des deux réseaux : un conteneur peut appartenir aux deux
  • Mesurer l'isolation par ce que le conteneur reçoit réellement

Docker doit être installé (installation de Docker). Vous devez connaître les commandes de base (lancer un conteneur) et savoir ce qu'est un réseau dédié, où les conteneurs se trouvent par leur nom (les réseaux Docker).

Les commandes de cette page sont écrites pour Docker Engine 29.9.2 et l'image alpine:3.24, dont le shell busybox fournit ip et nc. Les mesures interrogent aussi le témoin depuis l'hôte : il vous faut donc nc sur l'hôte lui-même (paquet netcat-openbsd sur Debian 13).

Quelles sont les deux façons d'isoler un conteneur ?

Section intitulée « Quelles sont les deux façons d'isoler un conteneur ? »

Vous pouvez retirer le réseau au conteneur, ou le placer sur un réseau qui ne mène nulle part.

Pour retirer le réseau, lancez le conteneur avec --network none. Il ne garde que son interface de bouclage, lo, qui lui sert seulement à se parler à lui-même.

Pour le placer sur un réseau fermé, créez d'abord ce réseau avec docker network create --internal. Les conteneurs de ce réseau ont une interface et se trouvent par leur nom. Ils n'ont aucune route vers l'extérieur.

Le schéma compare trois situations face à un même témoin, un service publié sur l'adresse de la machine. Ce témoin représente tout ce qui est hors du réseau des conteneurs. Pour chaque situation, regardez si le conteneur atteint le témoin, et s'il atteint son voisin.

Trois situations face à un témoin publié sur l'hôte : un conteneur sur le réseau par défaut sort et l'atteint ; un conteneur sans réseau n'a aucune interface hors loopback et ne l'atteint pas ; deux conteneurs sur un réseau interne se parlent par leur nom mais n'ont aucune route vers le témoin.Réseau interne cloisonSans réseau (none)Réseau par défautsortaucune interfacese parlent par leur nomaucune routebalisele témoin, publié sur l'hôteconteneur ordinaireanalyseurloopback seulregistrelecteur
Source · SVG
Échap pour fermer, + et − pour zoomer, défilement pour se déplacer. 100 %
PostureInterfaces hors loopbackJoint l'extérieurJoint un voisin par son nom
réseau par défautuneouinon
--network noneaucunenonsans objet
réseau interneunenonoui

Choisissez selon ce que le conteneur doit pouvoir faire. S'il doit parler à d'autres conteneurs, none est trop strict. S'il ne doit parler à personne, un réseau interne ne suffit pas, puisque ses voisins peuvent le joindre.

Comment couper complètement un conteneur du réseau ?

Section intitulée « Comment couper complètement un conteneur du réseau ? »

Lancez le conteneur avec --network none. Avant cela, installez un témoin, pour que la mesure ait un sens. Le témoin est un conteneur qui répond toujours la même ligne, publié sur le port 18310 de la machine. Sans lui, vous ne sauriez pas si votre conteneur isolé ne joint rien parce qu'il est isolé, ou parce que le témoin est tombé.

Fenêtre de terminal
docker run -d --name balise -p 18310:8080 alpine:3.24@sha256:be2656061b536c1883c4034790e21d0a09c5aaaf51968b89368a3c0371ea2c9a \
sh -c 'while true; do echo signal=balise | nc -l -p 8080; done'
HOTE=$(ip -o -4 route get 1.1.1.1 | awk '{for (i = 1; i <= NF; i++) if ($i == "src") print $(i + 1)}')
nc -w 3 "$HOTE" 18310

La deuxième commande calcule l'adresse de la machine à partir de sa route par défaut, pour ne pas l'écrire en dur. La troisième interroge le témoin depuis l'hôte. Elle doit afficher sa ligne. Sans cela, la suite ne prouverait rien.

signal=balise

Créez maintenant le conteneur sans réseau et regardez-le de l'intérieur.

Fenêtre de terminal
docker run -d --name analyseur --network none alpine:3.24@sha256:be2656061b536c1883c4034790e21d0a09c5aaaf51968b89368a3c0371ea2c9a sleep infinity
docker exec analyseur ip -o link
docker exec analyseur nc -w 3 "$HOTE" 18310
echo "code de nc = $?"
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1000\ link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
code de nc = 1

Vous avez fait deux observations différentes. La liste des interfaces ne contient que lo : elle lit la configuration. Le code 1 de nc montre que le conteneur n'a pas reçu la ligne du témoin : il éprouve le comportement.

Les deux concordent ici, mais l'une peut tromper là où l'autre ne le fait pas. La section sur les deux réseaux en donne un cas. Lisez aussi la réponse du témoin, et pas seulement le fait qu'une connexion s'établit. Un port publié accepte parfois la connexion avant que quoi que ce soit n'écoute derrière.

Comment confiner un groupe de conteneurs sans les couper entre eux ?

Section intitulée « Comment confiner un groupe de conteneurs sans les couper entre eux ? »

Créez un réseau interne, avec un sous-réseau que vous choisissez. Sans --subnet, Docker choisit un sous-réseau lui-même. En production, évitez de le lui laisser.

Fenêtre de terminal
docker network create --internal --subnet 172.30.14.0/24 cloison
docker network inspect cloison --format 'interne={{.Internal}} sous-reseau={{(index .IPAM.Config 0).Subnet}}'
interne=true sous-reseau=172.30.14.0/24

Le réseau est bien interne, et son sous-réseau est celui que vous avez demandé.

Lancez ensuite deux conteneurs directement sur ce réseau, sans passer par le réseau par défaut. registre répond une ligne sur le port 7000. lecteur essaie de joindre registre, puis le témoin.

Fenêtre de terminal
docker run -d --name registre --network cloison alpine:3.24@sha256:be2656061b536c1883c4034790e21d0a09c5aaaf51968b89368a3c0371ea2c9a \
sh -c 'while true; do echo signal=registre | nc -l -p 7000; done'
docker run -d --name lecteur --network cloison alpine:3.24@sha256:be2656061b536c1883c4034790e21d0a09c5aaaf51968b89368a3c0371ea2c9a sleep infinity
docker exec lecteur nc -w 3 registre 7000
docker exec lecteur nc -w 3 "$HOTE" 18310
echo "code de nc vers le témoin = $?"
signal=registre
code de nc vers le témoin = 1

Le premier nc reçoit la ligne de registre : lecteur joint son voisin par son nom. Un réseau créé par l'utilisateur, interne ou non, sait résoudre les noms de ses membres. La leçon sur les réseaux Docker détaille cette résolution des noms.

Le second nc échoue avec le code 1 : lecteur ne joint pas le témoin. La section suivante en donne la raison, que vous allez lire dans la table de routage du conteneur.

Un réseau interne repose d'abord sur une route qui n'existe pas. Aucun membre n'a de route par défaut. C'est la ligne de la table de routage qui dit où envoyer un paquet quand aucune autre ligne ne convient. Sans elle, le noyau n'a nulle part où envoyer un paquet destiné à l'extérieur.

La documentation de docker network create ajoute que Docker pose aussi des règles de pare-feu. Elles rejettent le trafic entre ce réseau et les autres.

Affichez la table de routage d'un membre du réseau interne, puis celle d'un conteneur sur le réseau par défaut.

Fenêtre de terminal
docker exec lecteur ip route
docker run --rm alpine:3.24@sha256:be2656061b536c1883c4034790e21d0a09c5aaaf51968b89368a3c0371ea2c9a ip route
172.30.14.0/24 dev eth0 scope link src 172.30.14.3
default via 172.17.0.1 dev eth0
172.17.0.0/16 dev eth0 scope link src 172.17.0.3

La première commande n'affiche que la route du sous-réseau 172.30.14.0/24. La seconde affiche les deux lignes suivantes. L'une d'elles est la ligne default, qui mène à la passerelle du réseau par défaut. Cette ligne manque dans lecteur, et cela suffit à expliquer l'échec que vous avez vu.

Cette protection est la plus simple à constater, puisque vous la lisez dans le conteneur. Le pare-feu de Docker, lui, ne se lit pas depuis le conteneur.

Ce confinement a deux limites. Les membres d'un même réseau interne se joignent tous. Et le réseau n'est pas coupé de l'hôte : d'après la documentation, l'hôte peut joindre l'adresse IP de chaque membre, et les membres peuvent joindre l'adresse de la passerelle du réseau. Un réseau interne empêche seulement les membres de sortir vers les autres réseaux.

Parce qu'un conteneur peut appartenir à deux réseaux à la fois. Rejoindre le réseau interne ajoute une appartenance, mais ne retire pas celle du réseau par défaut. Un conteneur rattaché aux deux garde la route du réseau par défaut, et il sort comme avant.

Pour confiner un conteneur, il faut donc deux gestes : le mettre sur le réseau interne, et le retirer de l'autre.

Vous allez le mesurer avec les objets de la page. balise et cloison existent déjà, et $HOTE vaut toujours l'adresse de la machine. Créez passerelle sur cloison, puis rattachez-le aussi au réseau par défaut, bridge.

Fenêtre de terminal
docker run -d --name passerelle --network cloison alpine:3.24@sha256:be2656061b536c1883c4034790e21d0a09c5aaaf51968b89368a3c0371ea2c9a sleep infinity
docker network connect bridge passerelle
docker container inspect passerelle --format '{{range $reseau, $v := .NetworkSettings.Networks}}{{println $reseau}}{{end}}'
docker exec passerelle nc -w 3 "$HOTE" 18310
echo "code de nc vers le témoin = $?"
bridge
cloison
signal=balise
code de nc vers le témoin = 0

La liste des réseaux contient bien cloison. Pourtant passerelle reçoit la ligne du témoin, avec le code 0. La configuration annonce un conteneur confiné, et le comportement montre le contraire.

Le schéma met côte à côte cette mesure et celle que vous allez faire après avoir détaché passerelle du réseau par défaut. Regardez la flèche rouge vers balise : elle existe tant que passerelle est sur bridge.

Deux états du conteneur passerelle, placés l'un sous l'autre. Rattaché aux deux réseaux, il joint registre sur le réseau cloison et il sort par bridge vers balise, le témoin sur l'hôte. Après docker network disconnect bridge, il joint toujours registre et n'a plus aucune route vers balise.Après docker networkdisconnect bridgeplus de routepasserelleregistreréseau cloisonbalisesur l'hôteRattaché aux deux réseauxsort par bridgepasserelleregistreréseau cloisonbalisesur l'hôte
Source · SVG
Échap pour fermer, + et − pour zoomer, défilement pour se déplacer. 100 %

Détachez maintenant passerelle du réseau par défaut, puis refaites les deux observations. Testez aussi qu'il joint toujours registre.

Fenêtre de terminal
docker network disconnect bridge passerelle
docker container inspect passerelle --format '{{range $reseau, $v := .NetworkSettings.Networks}}{{println $reseau}}{{end}}'
docker exec passerelle ip route
docker exec passerelle nc -w 3 "$HOTE" 18310
echo "code de nc vers le témoin = $?"
docker exec passerelle nc -w 3 registre 7000
cloison
172.30.14.0/24 dev eth0 scope link src 172.30.14.4
code de nc vers le témoin = 1
signal=registre

Seul cloison reste dans la liste. La ligne vide vient du println, qui ajoute un saut de ligne avant celui de la fin. La route default a disparu. La sonde vers le témoin échoue avec le code 1, sans aucun message : le nc de busybox est muet dans ce cas. Enfin passerelle joint toujours registre par son nom, car l'isolation ne touche pas les échanges internes.

Le plus simple reste de recréer le conteneur sous le même nom, avec --network cloison dès sa création. Il n'appartient alors jamais aux deux réseaux, même un instant.

Comment diagnostiquer une isolation qui ne tient pas ?

Section intitulée « Comment diagnostiquer une isolation qui ne tient pas ? »

Quatre symptômes reviennent.

Le premier est un conteneur « sans réseau » qui joint encore le témoin. Souvent, il a été recréé sans l'option, ou il utilise le réseau d'un autre conteneur (--network container:<nom>). Lisez docker container inspect analyseur --format '{{.HostConfig.NetworkMode}}' et comparez le résultat avec none.

Le deuxième est deux membres du réseau interne qui ne se parlent pas. Le nom est mal écrit, ou l'un des conteneurs est resté sur un autre réseau. Un nom ne se résout qu'entre membres d'un même réseau défini par l'utilisateur.

Le troisième est un membre du réseau interne qui sort encore. Il est presque toujours rattaché aussi à un second réseau. Listez ses réseaux avec la commande docker container inspect de la section précédente, puis détachez-le de l'autre avec docker network disconnect.

Le quatrième est un témoin qui ne répond pas. Toute mesure est alors sans valeur. Vérifiez-le depuis l'hôte avant de conclure quoi que ce soit.

Le lab vous donne des conteneurs qui sortent tous, dont un témoin que vous ne devez pas toucher. Vous devez obtenir les deux isolations sur la même machine. Un conteneur ne doit garder aucune interface réseau hors loopback. Deux conteneurs doivent être confinés sur un réseau interne dont le sous-réseau est imposé : ils se joignent par leur nom et n'atteignent jamais le témoin.

La validation lit l'état du système, jamais vos commandes. Elle regarde les interfaces dans le conteneur et vérifie ce qu'il reçoit du témoin. Elle contrôle aussi que le réseau est interne, avec le bon sous-réseau, et qu'aucun membre n'atteint le témoin.

Le témoin publie le port 18310 de la machine tant qu'il tourne. Supprimez-le avec les autres conteneurs de la page. Supprimez ensuite le réseau, qui ne peut disparaître qu'une fois vide de ses membres.

Fenêtre de terminal
docker rm -f balise analyseur registre lecteur passerelle
docker network rm cloison

Chaque commande affiche le nom de ce qu'elle supprime. Si vous avez fait le lab, ses conteneurs et son réseau portent les noms que le lab impose. Lisez-les dans docker ps -a et docker network ls, puis supprimez-les de la même façon.

  1. Il existe deux isolations. Un conteneur sans aucun interlocuteur se lance avec --network none. Un groupe dont les membres se parlent sans sortir se place sur un réseau interne.

  2. Un conteneur sans réseau ne garde que lo : aucune interface utile, aucun voisin, aucune sortie.

  3. Un réseau interne se crée avec --internal. Donnez-lui un --subnet explicite plutôt qu'un sous-réseau choisi par Docker.

  4. L'isolation tient d'abord à une route absente, que Docker complète par des règles de pare-feu. ip route dans un membre n'affiche aucune ligne default, et l'hôte peut encore joindre les membres.

  5. Les membres d'un réseau interne se trouvent par leur nom, comme sur n'importe quel réseau défini par l'utilisateur.

  6. Un conteneur peut appartenir à deux réseaux. Être sur le réseau interne ne suffit pas : il faut aussi avoir quitté l'autre.

  7. Mesurez la réponse, pas la connexion, contre un témoin dont vous avez vérifié qu'il répond. Sinon l'isolation « réussit » parce qu'il n'y a plus rien à joindre.

Vérifiez que l'essentiel de ce guide est acquis. Les questions portent uniquement sur ce qui vient d'être expliqué ici.

Contrôle de connaissances

Validez vos connaissances avec ce quiz interactif

6 questions
6 min.
70% requis

Informations

  • Le chronomètre démarre au clic sur Démarrer
  • Questions à choix multiples, vrai/faux et réponses courtes
  • Vous pouvez naviguer entre les questions
  • Les résultats détaillés sont affichés à la fin

Lance le quiz et démarre le chronomètre

Ce projet vous est utile ?

Les guides, formations, quiz et labs restent gratuits. Si vous les utilisez régulièrement, vous pouvez contribuer à leur maintenance et à leur développement.

Devenir membre de soutien (nouvel onglet) Soutenir une fois (nouvel onglet) En savoir plus

Suivez aussi le projet sur LinkedIn