Exponer un puerto en un contenedor Docker en vivo

409

Estoy tratando de crear un contenedor Docker que actúe como una máquina virtual completa. Sé que puedo usar la instrucción EXPOSE dentro de un Dockerfile para exponer un puerto, y puedo usar la -pbandera con docker runpara asignar puertos, pero una vez que un contenedor se está ejecutando, ¿hay un comando para abrir / asignar puertos adicionales en vivo?

Por ejemplo, supongamos que tengo un contenedor Docker que ejecuta sshd. Alguien más está usando el contenedor ssh's e instala httpd. ¿Hay alguna manera de exponer el puerto 80 en el contenedor y asignarlo al puerto 8080 en el host, para que las personas puedan visitar el servidor web que se ejecuta en el contenedor, sin reiniciarlo?

reberhardt
fuente
1
Puede darle al contenedor una IP enrutable , por lo que no se requiere asignación de puertos.
Matt

Respuestas:

331

No puede hacer esto a través de Docker, pero puede acceder al puerto no expuesto del contenedor desde la máquina host.

si tiene un contenedor que tiene algo ejecutándose en su puerto 8000, puede ejecutar

wget http://container_ip:8000

Para obtener la dirección IP del contenedor, ejecute los 2 comandos:

docker ps

docker inspect container_name | grep IPAddress

Internamente, Docker se abre para llamar a iptables cuando ejecuta una imagen, por lo que tal vez alguna variación al respecto funcione.

para exponer el puerto 8000 del contenedor en su puerto localhosts 8001:

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

Una forma de resolver esto es configurar otro contenedor con el mapeo de puertos que desee y comparar la salida del comando iptables-save (aunque tuve que eliminar algunas de las otras opciones que obligan al tráfico a pasar por la ventana acoplable apoderado).

NOTA: esto está subvirtiendo la ventana acoplable, por lo que debe hacerse con la conciencia de que bien puede crear humo azul

O

Otra alternativa es buscar la opción (nueva? Post 0.6.6?) -P, que utilizará puertos de host aleatorios y luego los conectará.

O

con 0.6.5, podría usar la función LINKs para abrir un nuevo contenedor que se comunique con el existente, con alguna transmisión adicional a las banderas -p de ese contenedor. (Todavía no he usado LINKs)

O

con docker 0.11? puede usar docker run --net host ..para adjuntar su contenedor directamente a las interfaces de red del host (es decir, net no está espaciado de nombres) y, por lo tanto, todos los puertos que abre en el contenedor están expuestos.

SvenDowideit
fuente
66
Esto no parece funcionar con Docker 1.3.0 al menos. La regla DOCKER DNAT se crea cuando se ejecuta docker con -p, pero agregarla manualmente no parece permitir conexiones. Por extraño que parezca, eliminar la regla mientras se está ejecutando un contenedor tampoco parece que deje de funcionar ...
silasdavis
44
Gracias. Fui arrullado por una sensación de seguridad de que los puertos no expuestos estaban a salvo.
seanmcl
Automatizar cosas y usar jq + sed, puede ser útil: 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
55
@ ericson.cepeda En lugar de facturar jqy sedpuede usar la -fopción de docker inspect:CONTAINER_IP=$(docker inspect -f '{{ .NetworkSettings.IPAddress }}' container_name)
pwes
para aquellos que prefieren kmkeen.com/jshon jshon -e 0 -e NetworkSettings -e Networks -e bridge -e IPAddress -u
slf
138

Esto es lo que haría:

  • Cometer el contenedor vivo.
  • Vuelva a ejecutar el contenedor con la nueva imagen, con los puertos abiertos (recomendaría montar un volumen compartido y abrir también el puerto 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
fuente
31
La parte clave de mi pregunta es que esto debe suceder sin reiniciar el contenedor ... Cambiar al nuevo contenedor puede mantener archivos, pero matará efectivamente cualquier proceso en ejecución y será similar a un reinicio en una máquina física. Necesito hacer esto sin que eso suceda. ¡Gracias de cualquier forma!
reberhardt
bien. Sin embargo, lo compararía más con comenzar una instancia paralela. dado que ambos se están ejecutando (antiguo y nuevo), puede ser más fácil proxy al nuevo contenedor, una vez que se realizan las migraciones necesarias.
bosky101
Sí, las instancias paralelas y el proxy inverso son algunas de las principales razones por las que amo a Docker. Sin embargo, en este escenario, necesito preservar todos los procesos en ejecución en el contenedor que pueden haberse iniciado a través de SSH. Si bien los ejecutables se conservarán al confirmar la imagen e iniciar una instancia paralela, los ejecutables en sí no se iniciarán y se perderá cualquier cosa en la RAM.
reberhardt
66
¿Por qué corres sudo dockery no solo docker?
Thiago Figueiro
Este consejo me ayudó mucho porque instalé toneladas de paquetes en una red lenta. Al ejecutar docker commit, estaba listo para probar la aplicación nuevamente en lugar de pasar horas para reinstalar todo.
gustavohenke
49

Si bien no puede exponer un nuevo puerto de un contenedor existente , puede iniciar un nuevo contenedor en la misma red de Docker y hacer que reenvíe el tráfico al contenedor original.

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

Ejemplo trabajado

Inicie un servicio web que escuche en el puerto 80, pero no exponga su puerto interno 80 (¡Uy!):

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

Encuentra su IP de red Docker:

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

Inicie verb/socatcon el puerto 8080 expuesto y haga que reenvíe el tráfico TCP al puerto 80 de esa IP:

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

Ahora puede acceder a pastebin en http: // localhost: 8080 / , y sus solicitudes van a socat:1234donde lo reenvía pastebin:80, y la respuesta recorre la misma ruta en reversa.

RobM
fuente
77
¡Muy inteligente! Sin embargo, probablemente sea mejor usarlo verb/socat:alpine, ya que su imagen tiene el 5% de la huella (a menos que se encuentre con incompatibilidades de libc o DNS ).
jpaugh
3
También hayalpine/socat
Jamby
3
Excelente respuesta Simple, un revestimiento que lo hará sin ningún truco sucio.
Bruno Brant
2
Esto funcionó perfectamente para mí, ¡gracias! Tuve que agregar --net myfoldername_defaulta mi verb/socatcomando de lanzamiento desde que comencé el contenedor no expuesto en una composición acoplable que crea una red.
emazzotta
35

Los hacks de IPtables no funcionan, al menos en Docker 1.4.1.

La mejor manera sería ejecutar otro contenedor con el puerto expuesto y retransmitir con socat. Esto es lo que he hecho para conectarme (temporalmente) a la base de datos con 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
fuente
2
Se supone que los hacks de iptables se ejecutan desde la máquina host, no desde el contenedor acoplable. Básicamente se trata de reenviar solicitudes a ciertos puertos en el host a los puertos apropiados del contenedor acoplable. No es específico de Docker en absoluto, y puede hacerlo en hosts completamente diferentes. ¿Se puede agregar formato de código a su archivo Docker? Parece bastante útil
AusIV
1
Febrero de 2016: ejecutando Docker 1.9.1, esta fue la única solución que funcionó con éxito para mí. Ninguna de las soluciones de IPTables funcionó. Vale la pena recomendar usar la misma FROMimagen base que su contenedor de base de datos, para un uso eficiente de los recursos.
Excalibur
44
Es posible que desee probar apline / socat . Viene con socat preinstalado y acepta las opciones de socat como comando, por lo que no necesita escribir un Dockerfile en absoluto.
Trkoch
35

Aquí hay otra idea. Use SSH para hacer el reenvío de puertos; Esto tiene la ventaja de trabajar también en OS X (y probablemente en Windows) cuando su host Docker es una VM.

docker exec -it <containterid> ssh -R5432:localhost:5432 <user>@<hostip>
Scott Carlson
fuente
44
en mi caso, la imagen de Docker que se estaba ejecutando no tiene ssh binario
Radu Toader
¿Necesito crear una clave SSH en mi máquina y ponerla en el contenedor acoplable para esto?
octaviano
@octavian ssh-keygen -t rsa dentro del contenedor. agregar clave de pub a claves_autorizadas en el host
Luke W
8

Tuve que lidiar con este mismo problema y pude resolverlo sin detener ninguno de mis contenedores en ejecución. Esta es una solución actualizada a partir de febrero de 2016, utilizando Docker 1.9.1. De todos modos, esta respuesta es una versión detallada de la respuesta de @ ricardo-branco, pero en mayor profundidad para los nuevos usuarios.

En mi caso, quería conectarme temporalmente a MySQL que se ejecuta en un contenedor, y dado que otros contenedores de aplicaciones están vinculados a él, detener, reconfigurar y volver a ejecutar el contenedor de la base de datos no fue un iniciador.

Como me gustaría acceder a la base de datos MySQL externamente (desde Sequel Pro a través de túneles SSH), voy a usar el puerto 33306en la máquina host. (No3306 , solo en caso de que se esté ejecutando una instancia externa de MySQL).

Aproximadamente una hora de ajuste de iptables resultó infructuoso, aunque:

Paso a paso, esto es lo que hice:

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

Editar dockerfile, colocando esto dentro:

# 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

Luego construye la imagen:

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

Luego ejecútelo, vinculándolo a su contenedor en ejecución. (Use en -dlugar de -rmmantenerlo en segundo plano hasta que se detenga y elimine explícitamente. Solo quiero que se ejecute temporalmente en este caso).

docker run -it --rm --name=db-33306 --link the_live_db_container:db -p 33306:33306  your-namespace/db-expose-33306
Excalibur
fuente
Puede usar el verbo / socat o la imagen alpine / socat directamente. Ver verbo / socat
pjotr_dolphin
7

Para agregar a la solución de respuesta aceptada iptables , tuve que ejecutar dos comandos más en el host para abrirlo al mundo exterior.

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

Nota: Estaba abriendo el puerto https (443), mi IP interna de Docker era 172.17.0.2

Nota 2: Estas reglas son temporales y solo durarán hasta que se reinicie el contenedor

dstj
fuente
Esto funcionó para mí ... sin embargo, hubo algunos Cavat más tarde. Según la nota 2, esto se detendrá si los contenedores se reiniciaron, entonces puede estar en una IP diferente. Pero más importantemente, Docker no tiene idea de estas entradas de iptable, por lo que no las eliminará cuando más tarde reinicie completamente el servicio y haga que Docker lo haga correctamente. El resultado fueron múltiples entradas de iptable que eran exactamente iguales, lo que causó que fallara con pocos o ningún error o indicación de la causa. Una vez que se dictaminaron las reglas adicionales, el problema desapareció. En otras palabras, revise sus tablas IP MUY cuidadosamente, después de cualquier cambio.
Anthony
5

Puede usar SSH para crear un túnel y exponer su contenedor en su host.

Puede hacerlo de ambas maneras, de contenedor a host y de host a contenedor. Pero necesita una herramienta SSH como OpenSSH en ambos (cliente en uno y servidor en otro).

Por ejemplo, en el contenedor, puedes hacer

$ 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..

Puede encontrar la dirección IP del contenedor desde esta línea (en el contenedor):

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

Luego, en el host, puedes hacer:

sudo ssh -NfL 80:0.0.0.0:80 [email protected]
tonelada
fuente
Esto funcionó para mí, con una pequeña corrección ... este comando funcionó: sudo ssh -NfL 25252: 172.17.0.50: 22 $ USER @ localhost
Alexander Guo
3

En caso de que ninguna respuesta funcione para alguien, verifique si su contenedor de destino ya se está ejecutando en la red de acopladores:

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

Guárdelo para más adelante en la variable $NET_NAME:

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

En caso afirmativo, debe ejecutar el contenedor proxy en la misma red.

A continuación, busque el alias del contenedor:

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

Guárdelo para más adelante en la variable $ALIAS:

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

Ahora ejecute socaten un contenedor en la red $NET_NAMEpara conectar al $ALIASpuerto expuesto (pero no publicado) del contenedor 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
fuente
2

Puede usar una red superpuesta como Weave Net , que asignará una dirección IP única a cada contenedor y expondrá implícitamente todos los puertos a cada parte del contenedor de la red.

Weave también proporciona integración de red de host . Está deshabilitado de forma predeterminada, pero si desea acceder también a las direcciones IP del contenedor (y todos sus puertos) desde el host, puede ejecutar simplemente ejecutarweave expose .

Divulgación completa: trabajo en Weaveworks.

fons
fuente
2

Hay un práctico envoltorio HAProxy.

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

Esto crea un HAProxy para el contenedor de destino. pan comido.

kwerle
fuente
1

Lea primero la respuesta de Ricardo . Esto funcionó para mí.

Sin embargo, existe un escenario en el que esto no funcionará si el contenedor en ejecución se inició utilizando docker-compose. Esto se debe a que docker-compose (estoy ejecutando docker 1.17) crea una nueva red. La forma de abordar este escenario sería

docker network ls

Luego agregue lo siguiente docker run -d --name sqlplus --link db:db -p 1521:1521 sqlplus --net network_name

ice.nicer
fuente
0

No es posible hacer un mapeo de puertos en vivo, pero hay varias maneras en que puede darle a un contenedor Docker lo que equivale a una interfaz real como lo haría una máquina virtual.

Interfaces Macvlan

Docker ahora incluye un controlador de red Macvlan . Esto conecta una red Docker a una interfaz del "mundo real" y le permite asignar las direcciones de esas redes directamente al contenedor (como un modo puenteado de máquinas virtuales).

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

pipeworkTambién puede asignar una interfaz real a un contenedor o configurar una interfaz secundaria en versiones anteriores de Docker.

IP de enrutamiento

Si tiene control de la red, puede enrutar redes adicionales a su host Docker para usar en los contenedores.

Luego asigna esa red a los contenedores y configura su host Docker para enrutar los paquetes a través de la red docker.

Interfaz de host compartida

La --net hostopción permite que la interfaz del host se comparta en un contenedor, pero probablemente no sea una buena configuración para ejecutar varios contenedores en un host debido a la naturaleza compartida.

Mate
fuente