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.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- 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
Prérequis
Section intitulée « Prérequis »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.
| Posture | Interfaces hors loopback | Joint l'extérieur | Joint un voisin par son nom |
|---|---|---|---|
| réseau par défaut | une | oui | non |
--network none | aucune | non | sans objet |
| réseau interne | une | non | oui |
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é.
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" 18310La 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=baliseCréez maintenant le conteneur sans réseau et regardez-le de l'intérieur.
docker run -d --name analyseur --network none alpine:3.24@sha256:be2656061b536c1883c4034790e21d0a09c5aaaf51968b89368a3c0371ea2c9a sleep infinitydocker exec analyseur ip -o linkdocker exec analyseur nc -w 3 "$HOTE" 18310echo "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:00code de nc = 1Vous 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.
docker network create --internal --subnet 172.30.14.0/24 cloisondocker network inspect cloison --format 'interne={{.Internal}} sous-reseau={{(index .IPAM.Config 0).Subnet}}'interne=true sous-reseau=172.30.14.0/24Le 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.
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 infinitydocker exec lecteur nc -w 3 registre 7000docker exec lecteur nc -w 3 "$HOTE" 18310echo "code de nc vers le témoin = $?"signal=registrecode de nc vers le témoin = 1Le 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.
Pourquoi un réseau interne ne sort-il jamais ?
Section intitulée « Pourquoi un réseau interne ne sort-il jamais ? »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.
docker exec lecteur ip routedocker run --rm alpine:3.24@sha256:be2656061b536c1883c4034790e21d0a09c5aaaf51968b89368a3c0371ea2c9a ip route172.30.14.0/24 dev eth0 scope link src 172.30.14.3default via 172.17.0.1 dev eth0172.17.0.0/16 dev eth0 scope link src 172.17.0.3La 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.
Pourquoi mon conteneur confiné sort-il encore ?
Section intitulée « Pourquoi mon conteneur confiné sort-il encore ? »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.
docker run -d --name passerelle --network cloison alpine:3.24@sha256:be2656061b536c1883c4034790e21d0a09c5aaaf51968b89368a3c0371ea2c9a sleep infinitydocker network connect bridge passerelledocker container inspect passerelle --format '{{range $reseau, $v := .NetworkSettings.Networks}}{{println $reseau}}{{end}}'docker exec passerelle nc -w 3 "$HOTE" 18310echo "code de nc vers le témoin = $?"bridgecloison
signal=balisecode de nc vers le témoin = 0La 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.
Détachez maintenant passerelle du réseau par défaut, puis refaites les deux observations. Testez aussi qu'il joint toujours registre.
docker network disconnect bridge passerelledocker container inspect passerelle --format '{{range $reseau, $v := .NetworkSettings.Networks}}{{println $reseau}}{{end}}'docker exec passerelle ip routedocker exec passerelle nc -w 3 "$HOTE" 18310echo "code de nc vers le témoin = $?"docker exec passerelle nc -w 3 registre 7000cloison
172.30.14.0/24 dev eth0 scope link src 172.30.14.4code de nc vers le témoin = 1signal=registreSeul 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.
Mettre en pratique
Section intitulée « Mettre en pratique »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.
Comment nettoyer après les mesures ?
Section intitulée « Comment nettoyer après les mesures ? »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.
docker rm -f balise analyseur registre lecteur passerelledocker network rm cloisonChaque 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.
À retenir
Section intitulée « À retenir »-
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. -
Un conteneur sans réseau ne garde que
lo: aucune interface utile, aucun voisin, aucune sortie. -
Un réseau interne se crée avec
--internal. Donnez-lui un--subnetexplicite plutôt qu'un sous-réseau choisi par Docker. -
L'isolation tient d'abord à une route absente, que Docker complète par des règles de pare-feu.
ip routedans un membre n'affiche aucune lignedefault, et l'hôte peut encore joindre les membres. -
Les membres d'un réseau interne se trouvent par leur nom, comme sur n'importe quel réseau défini par l'utilisateur.
-
Un conteneur peut appartenir à deux réseaux. Être sur le réseau interne ne suffit pas : il faut aussi avoir quitté l'autre.
-
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.
Contrôle de connaissances
Section intitulée « Contrôle de connaissances »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
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
Vérification
(0/0)Score par domaine
Un domaine regroupe plusieurs guides et reçoit plusieurs questions : son score est plus fiable qu'un score par thème, qui porte souvent sur une seule question.
Bilan
Points forts
Aucun sujet réussi à 80 % sur au moins deux questions pour l’instant.
Points faibles
Aucun sujet sous le seuil de réussite.
À faire ensuite
Des indices pour retenter votre chance ?
Continuer le cours : Épreuve : exposer une application sans exposer sa base
Nouveau quiz complet avec des questions aléatoires
Retravailler uniquement les questions ratées
Retour à la liste des certifications