Docker : router un conteneur via un VPN et garder l'autre en direct
Donnez au tunnel son propre conteneur, laissez seulement les services qui en ont besoin partager cet espace de noms, puis tenez compte de ce qui suit le propriétaire : ports, localhost, DNS.
Un hôte, deux charges de travail et une exigence qui les sépare : un client de téléchargement qui doit sortir par un VPN commercial, et une interface d'administration qui doit continuer d'utiliser la connexion de l'hôte. Installer le client VPN sur l'hôte échoue immédiatement à l'exigence, car les deux charges partagent alors une seule sortie. Le modèle qui fonctionne réellement consiste à déplacer le tunnel dans son propre conteneur et à ne laisser que les charges qui en ont besoin rejoindre l'espace de noms réseau de ce conteneur. Tout le reste garde le chemin Docker ordinaire, et les détails qui suivent sont des conséquences de qui possède l'espace de noms.
Pourquoi un espace de noms partagé change les règles
Un conteneur qui rejoint l'espace de noms d'un autre n'obtient pas sa propre pile réseau. Il voit les mêmes interfaces, la même table de routage et le même loopback que le conteneur qu'il a rejoint, et c'est précisément pourquoi le modèle est utile : la charge résout et se connecte normalement, et le tunnel en dessous capture le trafic sans que l'application le sache. Le coût est que chaque réglage réseau a désormais un seul propriétaire, et le conteneur qui rejoint ne l'est pas.
Cette règle de propriété répond à la plupart des questions de configuration avant même qu'elles soient posées. Si un réglage vit dans la pile réseau, il appartient au conteneur sidecar. S'il décrit le processus, il reste avec la charge.
Le loopback est la partie qui surprend les gens. À l'intérieur du conteneur qui rejoint, localhost se résout vers le loopback du conteneur sidecar, et non vers un loopback privé qui lui serait propre, si bien qu'un service qui se lie à 127.0.0.1 en attendant l'isolation partage désormais cette adresse avec le conteneur du tunnel.
Publiez les ports sur le conteneur qui possède l'espace de noms
Le mappage de ports appartient au propriétaire de l'espace de noms, même lorsque le processus à l'écoute s'exécute dans l'autre conteneur. Déclarez le mappage sur le conteneur sidecar, et laissez la charge conserver sa propre configuration de processus intacte. Un mappage sur le service qui rejoint ne peut pas prendre effet, que le moteur le rejette ou l'ignore silencieusement selon la façon dont le conteneur est démarré, et c'est pourquoi une interface accessible il y a un instant disparaît dès que le modèle est appliqué.
services:
gluetun:
image: qmcgaw/gluetun
cap_add: [NET_ADMIN]
ports: ["8080:8080"]
client-vpn:
image: example/client
network_mode: "service:gluetun"
client-direct:
image: example/client
ports: ["8081:8080"]Lisez ce bloc comme trois décisions plutôt que trois services. Le conteneur sidecar possède le tunnel et le port publié ; le client qui rejoint ne déclare aucun réseau ; le client direct conserve son propre mappage sur le réseau par défaut. La capacité NET_ADMIN est ce qui permet au conteneur sidecar de créer des interfaces et de modifier des routes, et c'est la raison pour laquelle ce conteneur a besoin de plus de privilèges que les charges qu'il sert.
Le service qui reste direct garde son propre réseau
La charge qui ne doit pas passer par le tunnel conserve le pont par défaut ou un réseau défini par l'utilisateur qui lui est propre, avec son propre port publié et sa propre sortie. Il est tentant de la rattacher au réseau du conteneur sidecar pour que les deux conteneurs puissent se parler par nom, mais cette seule étape déplace silencieusement son trafic dans le tunnel. S'ils doivent s'atteindre, construisez un réseau défini par l'utilisateur séparé, rattachez-y les deux, et laissez le conteneur sidecar comme seule voie de sortie.
Gardez les deux questions séparées : quels conteneurs peuvent s'atteindre, et lequel sort par où. Les fusionner est la façon la plus courante pour ce design de redevenir un VPN à l'échelle de l'hôte.
Une matrice de pannes pour la première heure
La plupart des premières tentatives échouent de l'une de quatre façons, et chacune a une cause différente. Vérifiez-les dans cet ordre, car les deux premières expliquent presque tous les rapports.
| Symptom | Likely cause |
|---|---|
| Web UI unreachable after the change | Port mapping declared on the joining container instead of the sidecar |
| Both containers show the provider's exit address | The direct service was attached to the sidecar network |
| Internal names stop resolving | Resolver settings live on the host, not in the shared namespace |
| Health check fails but the app works | The check probes localhost, which now means the sidecar |
La quatrième ligne est celle que les gens diagnostiquent à tort comme un bug applicatif. Une sonde écrite contre le loopback propre à la charge teste désormais le conteneur du tunnel, donc elle échoue ou signale un service complètement erroné.
Quand un seul nom d'hôte a besoin du tunnel
Parfois un tunnel complet est trop : un nom interne doit passer par un VPN d'entreprise tandis qu'un socket local ou une connexion d'affichage continue de fonctionner. Un tunnel complet dans le conteneur casse ce chemin et tout ce qui est adressé à l'hôte, alors ne routez que le préfixe qui en a besoin. Un wrapper de routage divisé autour du client fait cela, en envoyant l'hôte nommé dans le tunnel et en laissant la route précédente intacte pour tout le reste.
Choisissez ceci quand vous n'avez besoin d'atteindre qu'un seul service et que vous ne cherchez pas à déplacer tout le trafic du conteneur. C'est un outil différent du conteneur sidecar et il répond à une autre question.
Vérifiez, puis faites-le survivre à un redémarrage
Demandez à chaque conteneur de quelle adresse il semble venir. Celui du tunnel devrait afficher l'adresse du fournisseur et le direct la sortie de l'hôte. Des réponses identiques signifient que le service direct a hérité du mauvais espace de noms ou qu'un VPN au niveau de l'hôte capture encore tout, et ces deux correctifs sont différents. Relancez la vérification après chaque modification de compose, car un seul bloc de service copié suffit à réintroduire le problème.
Trois choses ont tendance à casser plus tard. Le DNS suit le propriétaire de l'espace de noms, donc les réglages de résolveur et de domaine de recherche appartiennent au conteneur sidecar plutôt qu'à l'hôte. Un VPN avec redirection de ports change son port d'écoute à la reconnexion, donc le port redirigé du fournisseur et le port d'écoute côté conteneur doivent être réécrits dans la configuration du client ou dans le mappage de ports, généralement par un petit outil de mise à jour qui lit l'API du fournisseur. Et toute sonde écrite contre localhost interroge désormais le conteneur du tunnel plutôt que la charge.
Écrivez le modèle, y compris quel conteneur possède l'espace de noms et quels ports lui appartiennent. La personne suivante ajoutera un service, copiera le mauvais bloc et reproduira la première panne de cet article.