Exposer un port sur un conteneur Docker actif

409

J'essaie de créer un conteneur Docker qui agit comme une machine virtuelle complète. Je sais que je peux utiliser l'instruction EXPOSE dans un Dockerfile pour exposer un port, et je peux utiliser l' -pindicateur avec docker runpour affecter des ports, mais une fois qu'un conteneur est réellement en cours d'exécution, existe-t-il une commande pour ouvrir / mapper des ports supplémentaires en direct?

Par exemple, supposons que j'ai un conteneur Docker qui exécute sshd. Quelqu'un d'autre utilise le conteneur ssh dans et installe httpd. Existe-t-il un moyen d'exposer le port 80 sur le conteneur et de le mapper sur le port 8080 sur l'hôte, afin que les utilisateurs puissent visiter le serveur Web exécuté dans le conteneur, sans le redémarrer?

reberhardt
la source
1
Vous pouvez donner au conteneur une IP routable , donc aucun mappage de port n'est requis.
Matt

Réponses:

331

Vous ne pouvez pas le faire via Docker, mais vous pouvez accéder au port non exposé du conteneur depuis la machine hôte.

si vous avez un conteneur avec quelque chose qui tourne sur son port 8000, vous pouvez exécuter

wget http://container_ip:8000

Pour obtenir l'adresse IP du conteneur, exécutez les 2 commandes:

docker ps

docker inspect container_name | grep IPAddress

En interne, Docker lance un shell pour appeler iptables lorsque vous exécutez une image, donc peut-être que certaines variations à ce sujet fonctionneront.

pour exposer le port 8000 du conteneur sur le port 8001 de vos hôtes locaux:

 iptables -t nat -A  DOCKER -p tcp --dport 8001 -j DNAT --to-destination 172.17.0.19:8000

Une façon de résoudre ce problème est de configurer un autre conteneur avec le mappage de port souhaité et de comparer la sortie de la commande iptables-save (cependant, j'ai dû supprimer certaines des autres options qui forcent le trafic à passer par le docker Procuration).

REMARQUE: ceci est en train de renverser le docker, donc devrait être fait en sachant qu'il peut bien créer de la fumée bleue

OU

Une autre alternative consiste à rechercher l'option (nouvelle? Post 0.6.6?) -P - qui utilisera des ports hôtes aléatoires, puis les câbler.

OU

avec 0.6.5, vous pourriez utiliser la fonction LINKs pour faire apparaître un nouveau conteneur qui parle au conteneur existant, avec quelques relais supplémentaires aux drapeaux -p de ce conteneur? (Je n'ai pas encore utilisé de LIENS)

OU

avec docker 0,11? vous pouvez utiliser docker run --net host ..pour attacher votre conteneur directement aux interfaces réseau de l'hôte (c'est-à-dire que net n'est pas à espacement de nom) et ainsi tous les ports que vous ouvrez dans le conteneur sont exposés.

SvenDowideit
la source
6
Cela ne semble pas fonctionner avec docker 1.3.0 au moins. La règle DOCKER DNAT est créée lors de l'exécution de docker avec -p, mais l'ajout manuel ne semble pas autoriser les connexions. La suppression étrange de la règle alors qu'un conteneur est en cours d'exécution ne semble pas non plus l'empêcher de fonctionner ...
silasdavis
4
Merci. J'ai été bercé par un sentiment de sécurité que les ports non exposés étaient sûrs.
seanmcl
Automatiser les choses et utiliser jq + sed, cela peut être utile: CONTAINER_IP=$(docker inspect container_name | jq .[0].NetworkSettings.IPAddress | sed -r 's/\"([^\"]+)\"/\1/''])'); iptables -t nat -A DOCKER -p tcp --dport 8001 -j DNAT --to-destination ${CONTAINER_IP}:8000
ericson.cepeda
5
@ ericson.cepeda Au lieu de la facturation jqet sedvous pouvez utiliser l' -foption de docker inspect:CONTAINER_IP=$(docker inspect -f '{{ .NetworkSettings.IPAddress }}' container_name)
pwes
pour ceux qui préfèrent kmkeen.com/jshon jshon -e 0 -e NetworkSettings -e Networks -e bridge -e IPAddress -u
slf
138

Voici ce que je ferais:

  • Validez le conteneur actif.
  • Exécutez à nouveau le conteneur avec la nouvelle image, avec les ports ouverts (je recommande de monter un volume partagé et d'ouvrir également le port ssh)
sudo docker ps 
sudo docker commit <containerid> <foo/live>
sudo docker run -i -p 22 -p 8000:80 -m /data:/data -t <foo/live> /bin/bash
bosky101
la source
31
La partie clé de ma question est que cela doit se produire sans redémarrer le conteneur ... Le passage au nouveau conteneur peut conserver des fichiers, mais tuera efficacement tous les processus en cours d'exécution et sera similaire à un redémarrage sur une machine physique. Je dois faire ça sans que cela se produise. Merci quand même!
reberhardt
bien. je le comparerais plus au démarrage d'une instance parallèle. étant donné que les deux sont maintenant en cours d'exécution (anciens et nouveaux), il peut être plus facile de proxy vers le nouveau conteneur, une fois les migrations nécessaires effectuées.
bosky101
Oui, les instances parallèles et le proxy inverse sont quelques-unes des principales raisons pour lesquelles j'aime Docker. Cependant, dans ce scénario, je dois conserver tous les processus en cours d'exécution dans le conteneur qui peuvent avoir été démarrés via SSH. Bien que les exécutables soient conservés lors de la validation de l'image et du démarrage d'une instance parallèle, les exécutables eux-mêmes ne seront pas démarrés et tout ce qui se trouve dans la RAM sera perdu.
reberhardt
6
Pourquoi courez-vous sudo dockeret pas seulement docker?
Thiago Figueiro
Cette astuce m'a beaucoup aidé car j'ai installé des tonnes de packages dans un réseau lent. En exécutant, docker commitj'étais prêt à tester à nouveau l'application au lieu de passer des heures à tout réinstaller.
gustavohenke
49

Bien que vous ne puissiez pas exposer un nouveau port d'un conteneur existant , vous pouvez démarrer un nouveau conteneur dans le même réseau Docker et le faire transférer le trafic vers le conteneur d'origine.

# docker run \
  --rm \
  -p $PORT:1234 \
  verb/socat \
    TCP-LISTEN:1234,fork \
    TCP-CONNECT:$TARGET_CONTAINER_IP:$TARGET_CONTAINER_PORT

Exemple travaillé

Lancez un service Web qui écoute sur le port 80, mais n'exposez pas son port interne 80 (oups!):

# docker run -ti mkodockx/docker-pastebin   # Forgot to expose PORT 80!

Trouvez son IP réseau Docker:

# docker inspect 63256f72142a | grep IPAddress
                    "IPAddress": "172.17.0.2",

Lancez verb/socatavec le port 8080 exposé et faites-le transférer le trafic TCP vers le port 80 de cette IP:

# docker run --rm -p 8080:1234 verb/socat TCP-LISTEN:1234,fork TCP-CONNECT:172.17.0.2:80

Vous pouvez maintenant accéder à pastebin sur http: // localhost: 8080 / , et vos demandes sont envoyées à celui socat:1234qui les transmet pastebin:80, et la réponse parcourt le même chemin en sens inverse.

RobM
la source
7
Assez intelligent! Pourtant, probablement mieux à utiliser verb/socat:alpine, car son image a 5% de l'empreinte (sauf si vous rencontrez des incompatibilités libc ou DNS ).
jpaugh
3
Il y a aussialpine/socat
Jamby
3
Excellente réponse. Simple, une doublure qui le fera sans aucun piratage sale.
Bruno Brant
2
Cela a parfaitement fonctionné pour moi, merci! J'ai dû ajouter --net myfoldername_defaultà ma verb/socatcommande de lancement depuis que j'ai démarré le conteneur non exposé dans une composition de docker qui crée un réseau.
emazzotta
35

Les hacks IPtables ne fonctionnent pas, du moins sur Docker 1.4.1.

La meilleure façon serait d'exécuter un autre conteneur avec le port exposé et de relayer avec socat. Voici ce que j'ai fait pour me connecter (temporairement) à la base de données avec SQLPlus:

docker run -d --name sqlplus --link db:db -p 1521:1521 sqlplus

Dockerfile:

FROM debian:7

RUN apt-get update && \
    apt-get -y install socat && \
    apt-get clean

USER nobody

CMD socat -dddd TCP-LISTEN:1521,reuseaddr,fork TCP:db:1521
Ricardo Branco
la source
2
Les hacks iptables sont censés s'exécuter à partir de la machine hôte, pas du conteneur Docker. Il s'agit essentiellement de transmettre des demandes à certains ports de l'hôte dans les ports de conteneur Docker appropriés. Ce n'est pas spécifique au docker du tout, et vous pouvez le faire sur des hôtes complètement différents. Pouvez-vous ajouter une mise en forme de code à votre fichier Docker? Cela semble assez utile.
AusIV
1
Février 2016: sous Docker 1.9.1, c'était la seule solution qui fonctionnait avec succès pour moi. Aucune des solutions IPTables n'a fonctionné. Il est conseillé d'utiliser la même FROMimage de base que votre conteneur DB, pour une utilisation efficace des ressources.
Excalibur
4
Vous voudrez peut-être essayer apline / socat . Il est livré avec socat préinstallé et accepte les options socat en tant que commande afin que vous n'ayez pas du tout besoin d'écrire un Dockerfile.
trkoch
35

Voici une autre idée. Utilisez SSH pour effectuer la redirection de port; cela a l'avantage de fonctionner également sous OS X (et probablement Windows) lorsque votre hôte Docker est une machine virtuelle.

docker exec -it <containterid> ssh -R5432:localhost:5432 <user>@<hostip>
Scott Carlson
la source
4
dans mon cas, l'image docker qui s'exécutait n'a pas de ssh binaire
Radu Toader
Dois-je créer une clé SSH sur ma machine et la mettre dans le conteneur Docker pour cela?
octavian
@octavian ssh-keygen -t rsa inside container. ajouter la clé de pub à authorized_keys sur l'hôte
Luke W
8

J'ai dû faire face à ce même problème et j'ai pu le résoudre sans arrêter aucun de mes conteneurs en cours d'exécution. Il s'agit d'une solution à jour en février 2016, utilisant Docker 1.9.1. Quoi qu'il en soit, cette réponse est une version détaillée de la réponse de @ ricardo-branco, mais plus en profondeur pour les nouveaux utilisateurs.

Dans mon scénario, je voulais me connecter temporairement à MySQL fonctionnant dans un conteneur, et puisque d'autres conteneurs d'application y sont liés, l'arrêt, la reconfiguration et la réexécution du conteneur de base de données n'étaient pas un démarreur.

Puisque je souhaite accéder à la base de données MySQL en externe (à partir de Sequel Pro via le tunneling SSH), je vais utiliser le port 33306sur la machine hôte. (Non 3306, juste au cas où une instance MySQL externe est en cours d'exécution.)

Environ une heure de peaufinage des iptables s'est avérée infructueuse, même si:

Étape par étape, voici ce que j'ai fait:

mkdir db-expose-33306
cd db-expose-33306
vim Dockerfile

Modifiez dockerfile, en plaçant ceci à l'intérieur:

# Exposes port 3306 on linked "db" container, to be accessible at host:33306
FROM ubuntu:latest # (Recommended to use the same base as the DB container)

RUN apt-get update && \
    apt-get -y install socat && \
    apt-get clean

USER nobody
EXPOSE 33306

CMD socat -dddd TCP-LISTEN:33306,reuseaddr,fork TCP:db:3306

Construisez ensuite l'image:

docker build -t your-namespace/db-expose-33306 .

Ensuite, exécutez-le, en vous liant à votre conteneur en cours d'exécution. (Utilisez -dau lieu de -rmpour le garder en arrière-plan jusqu'à ce qu'il soit explicitement arrêté et supprimé. Je veux seulement qu'il fonctionne temporairement dans ce cas.)

docker run -it --rm --name=db-33306 --link the_live_db_container:db -p 33306:33306  your-namespace/db-expose-33306
Excalibur
la source
Vous pouvez utiliser directement le verbe / socat ou l'image alpine / socat. Voir verbe / socat
pjotr_dolphin
7

Pour ajouter à la solution de réponse acceptée iptables , j'ai dû exécuter deux commandes supplémentaires sur l'hôte pour l'ouvrir au monde extérieur.

HOST> iptables -t nat -A DOCKER -p tcp --dport 443 -j DNAT --to-destination 172.17.0.2:443
HOST> iptables -t nat -A POSTROUTING -j MASQUERADE -p tcp --source 172.17.0.2 --destination 172.17.0.2 --dport https
HOST> iptables -A DOCKER -j ACCEPT -p tcp --destination 172.17.0.2 --dport https

Remarque: J'ouvrais le port https (443), mon IP interne de docker était 172.17.0.2

Remarque 2: ces règles et temporaires et ne dureront que jusqu'au redémarrage du conteneur

dstj
la source
Cela a fonctionné pour moi ... mais il y a eu quelques Cavat plus tard. Selon la note 2, cela s'arrêtera si les conteneurs redémarrent, il se peut alors qu'ils soient sur une IP différente. Mais plus important encore, Docker n'a aucune idée de ces entrées iptables, donc ne les supprimera pas lorsque vous redémarrerez ultérieurement le service et que Docker le fera correctement. Le résultat a été plusieurs entrées iptables qui étaient exactement les mêmes, ce qui l'a fait échouer avec peu ou pas d'erreurs ou d'indication sur la cause. Une fois que des règles supplémentaires ont été décidées, le problème a disparu. En d'autres termes, regardez très attentivement vos tables IP, après tout changement.
anthony
5

Vous pouvez utiliser SSH pour créer un tunnel et exposer votre conteneur dans votre hôte.

Vous pouvez le faire des deux manières, du conteneur à l'hôte et de l'hôte au conteneur. Mais vous avez besoin d'un outil SSH comme OpenSSH dans les deux (client dans un et serveur dans un autre).

Par exemple, dans le conteneur, vous pouvez faire

$ yum install -y openssh openssh-server.x86_64
service sshd restart
Stopping sshd:                                             [FAILED]
Generating SSH2 RSA host key:                              [  OK  ]
Generating SSH1 RSA host key:                              [  OK  ]
Generating SSH2 DSA host key:                              [  OK  ]
Starting sshd:                                             [  OK  ]
$ passwd # You need to set a root password..

Vous pouvez trouver l'adresse IP du conteneur à partir de cette ligne (dans le conteneur):

$ ifconfig eth0 | grep "inet addr" | sed 's/^[^:]*:\([^ ]*\).*/\1/g'
172.17.0.2

Ensuite, dans l'hôte, vous pouvez simplement faire:

sudo ssh -NfL 80:0.0.0.0:80 [email protected]
tonne
la source
Cela a fonctionné pour moi, avec une petite correction ... cette commande a fonctionné: sudo ssh -NfL 25252: 172.17.0.50: 22 $ USER @ localhost
Alexander Guo
3

Dans le cas où aucune réponse ne fonctionne pour quelqu'un - vérifiez si votre conteneur cible est déjà en cours d'exécution sur le réseau docker:

CONTAINER=my-target-container
docker inspect $CONTAINER | grep NetworkMode
        "NetworkMode": "my-network-name",

Enregistrez-le pour plus tard dans la variable $NET_NAME:

NET_NAME=$(docker inspect --format '{{.HostConfig.NetworkMode}}' $CONTAINER)

Si oui, vous devez exécuter le conteneur proxy sur le même réseau.

Recherchez ensuite l'alias du conteneur:

docker inspect $CONTAINER | grep -A2 Aliases
                "Aliases": [
                    "my-alias",
                    "23ea4ea42e34a"

Enregistrez-le pour plus tard dans la variable $ALIAS:

ALIAS=$(docker inspect --format '{{index .NetworkSettings.Networks "'$NET_NAME'" "Aliases" 0}}' $CONTAINER)

Maintenant, exécutez socatdans un conteneur du réseau $NET_NAMEpour établir un pont vers le port $ALIASexposé (mais non publié) du conteneur ed:

docker run \
    --detach --name my-new-proxy \
    --net $NET_NAME \
    --publish 8080:1234 \
    alpine/socat TCP-LISTEN:1234,fork TCP-CONNECT:$ALIAS:80
ktalik
la source
2

Vous pouvez utiliser un réseau de superposition comme Weave Net , qui attribuera une adresse IP unique à chaque conteneur et exposera implicitement tous les ports à chaque partie de conteneur du réseau.

Weave fournit également une intégration au réseau hôte . Il est désactivé par défaut mais, si vous souhaitez également accéder aux adresses IP du conteneur (et à tous ses ports) à partir de l'hôte, vous pouvez exécuter simplement exécuter weave expose.

Divulgation complète: je travaille chez Weaveworks.

fons
la source
2

Il existe un emballage HAProxy pratique.

docker run -it -p LOCALPORT:PROXYPORT --rm --link TARGET_CONTAINER:EZNAME -e "BACKEND_HOST=EZNAME" -e "BACKEND_PORT=PROXYPORT" demandbase/docker-tcp-proxy

Cela crée un HAProxy dans le conteneur cible. peasy facile.

kwerle
la source
1

Lisez d' abord la réponse de Ricardo . Cela a fonctionné pour moi.

Cependant, il existe un scénario où cela ne fonctionnera pas si le conteneur en cours d'exécution a été lancé à l'aide de docker-compose. C'est parce que docker-compose (j'exécute docker 1.17) crée un nouveau réseau. La manière d'aborder ce scénario serait

docker network ls

Ensuite, ajoutez ce qui suit docker run -d --name sqlplus --link db:db -p 1521:1521 sqlplus --net network_name

ice.nicer
la source
0

Il n'est pas possible de faire un mappage de port en direct, mais il existe plusieurs façons de donner à un conteneur Docker ce qui équivaut à une interface réelle comme une machine virtuelle.

Interfaces Macvlan

Docker inclut désormais un pilote réseau Macvlan . Cela attache un réseau Docker à une interface "du monde réel" et vous permet d'attribuer ces adresses de réseaux directement au conteneur (comme un mode ponté de machines virtuelles).

docker network create \
    -d macvlan \
    --subnet=172.16.86.0/24 \
    --gateway=172.16.86.1  \
    -o parent=eth0 pub_net

pipeworkpeut également mapper une interface réelle dans un conteneur ou configurer une sous-interface dans les anciennes versions de Docker.

IP de routage

Si vous contrôlez le réseau, vous pouvez router des réseaux supplémentaires vers votre hôte Docker pour les utiliser dans les conteneurs.

Ensuite, vous affectez ce réseau aux conteneurs et configurez votre hôte Docker pour acheminer les paquets via le réseau Docker.

Interface hôte partagée

L' --net hostoption permet à l'interface hôte d'être partagée dans un conteneur, mais ce n'est probablement pas une bonne configuration pour exécuter plusieurs conteneurs sur le même hôte en raison de la nature partagée.

Mat
la source