Esporre una porta su un container Docker attivo


409

Sto cercando di creare un contenitore Docker che si comporta come una macchina virtuale completa. So di poter usare l'istruzione EXPOSE all'interno di un Dockerfile per esporre una porta e posso usare il -pflag con docker runper assegnare le porte, ma una volta che un container è effettivamente in esecuzione, c'è un comando per aprire / mappare le porte aggiuntive?

Ad esempio, supponiamo che io abbia un contenitore Docker che esegue sshd. Qualcun altro usa il container ssh e installa httpd. C'è un modo per esporre la porta 80 sul contenitore e mapparla sulla porta 8080 sull'host, in modo che le persone possano visitare il server Web in esecuzione nel contenitore, senza riavviarlo?


1
È possibile assegnare al contenitore un IP instradabile , quindi non è richiesto alcun mapping delle porte.
Matt,

Risposte:


331

Non è possibile farlo tramite Docker, ma è possibile accedere alla porta non esposta del contenitore dal computer host.

se hai un contenitore con qualcosa in esecuzione sulla sua porta 8000, puoi eseguirlo

wget http://container_ip:8000

Per ottenere l'indirizzo IP del contenitore, eseguire i 2 comandi:

docker ps

docker inspect container_name | grep IPAddress

Internamente, Docker esegue lo shell per chiamare iptables quando si esegue un'immagine, quindi forse alcune variazioni su questo funzioneranno.

per esporre la porta 8000 del contenitore sulla porta 8001 dei localhosts:

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

Un modo per risolverlo è impostare un altro contenitore con la mappatura delle porte desiderata e confrontare l'output di iptables-save comando (tuttavia, ho dovuto rimuovere alcune delle altre opzioni che impongono al traffico di passare attraverso la finestra mobile proxy).

NOTA: questo sta sovvertendo la finestra mobile, quindi dovrebbe essere fatto con la consapevolezza che potrebbe benissimo creare fumo blu

O

Un'altra alternativa è guardare l'opzione (new? Post 0.6.6?) -P - che utilizzerà le porte host casuali e quindi collegherà quelle.

O

con 0.6.5, potresti usare la funzione LINKs per far apparire un nuovo container che parla con quello esistente, con qualche ulteriore inoltro ai flag -p di quel container? (Non ho ancora usato i LINK)

O

con docker 0.11? è possibile utilizzare docker run --net host ..per collegare il proprio contenitore direttamente alle interfacce di rete dell'host (ovvero, net non è spaziato dal nome) e quindi tutte le porte aperte nel contenitore sono esposte.


6
Questo non sembra funzionare almeno con la finestra mobile 1.3.0. La regola DNAT DOCKER viene creata quando si esegue la finestra mobile con -p, ma l'aggiunta manuale non sembra consentire le connessioni. Eliminare stranamente la regola mentre un container è in esecuzione non sembra impedirne il funzionamento ...
silasdavis,

4
Grazie. Ero cullato dal senso di sicurezza che i porti non esposti erano al sicuro.
sabato

Automatizzare le cose e usare jq + sed, può essere 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 Invece di fatturare jqe sedpuoi usare l' -fopzione di docker inspect:CONTAINER_IP=$(docker inspect -f '{{ .NetworkSettings.IPAddress }}' container_name)
pwes

per coloro che preferiscono kmkeen.com/jshon jshon -e 0 -e NetworkSettings -e Networks -e bridge -e IPAddress -u
slf

138

Ecco cosa farei:

  • Commettere il contenitore live.
  • Esegui nuovamente il contenitore con la nuova immagine, con le porte aperte (consiglierei di montare un volume condiviso e di aprire anche la porta 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

31
La parte fondamentale della mia domanda è che ciò deve avvenire senza riavviare il contenitore ... Il passaggio al nuovo contenitore può conservare i file, ma ucciderà efficacemente tutti i processi in esecuzione e sarà simile a un riavvio su una macchina fisica. Devo farlo senza che ciò accada. Grazie comunque!
Reberhardt,

Tutto a posto. lo confronterei di più però con l'avvio di un'istanza parallela. dal momento che entrambi sono in esecuzione (vecchi e nuovi), potrebbe essere più semplice eseguire il proxy al nuovo contenitore, una volta eseguite le migrazioni necessarie.
Bosky101,

Sì, le istanze parallele e il proxy inverso sono alcuni dei motivi principali per cui amo Docker. Tuttavia, in questo scenario, devo preservare tutti i processi in esecuzione nel contenitore che potrebbero essere stati avviati tramite SSH. Mentre gli eseguibili verranno conservati nel commit dell'immagine e nell'avvio di un'istanza parallela, gli eseguibili stessi non verranno avviati e qualsiasi cosa nella RAM andrà persa.
Reberhardt,

6
Perché corri sudo dockere non solo docker?
Thiago Figueiro,

Questo suggerimento mi ha aiutato molto perché ho installato tonnellate di pacchetti in una rete lenta. Eseguendo docker commitero pronto a testare nuovamente l'applicazione invece di passare ore a reinstallare tutto.
Gustavohenke,

49

Sebbene non sia possibile esporre una nuova porta di un contenitore esistente , è possibile avviare un nuovo contenitore nella stessa rete Docker e indurlo a inoltrare il traffico al contenitore originale.

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

Esempio lavorato

Avviare un servizio Web in ascolto sulla porta 80, ma non esporre la sua porta interna 80 (oops!):

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

Trova il suo IP di rete Docker:

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

Avviare verb/socatcon la porta 8080 esposta e farlo inoltrare il traffico TCP alla porta 80 dell'IP:

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

Ora puoi accedere a pastebin su http: // localhost: 8080 / , e le tue richieste vanno a socat:1234cui lo inoltra pastebin:80e la risposta percorre lo stesso percorso al contrario.


7
Abbastanza intelligente! Tuttavia, probabilmente migliore da usare verb/socat:alpine, poiché la sua immagine ha il 5% dell'impronta (a meno che non si verifichino incompatibilità libc o DNS ).
jpaugh,

3
C'è anchealpine/socat
Jamby il

3
Risposta eccellente. Semplice, una fodera che lo farà senza alcun trucco sporco.
Bruno Brant,

2
Questo ha funzionato perfettamente per me, grazie! Ho dovuto aggiungere il --net myfoldername_defaultmio verb/socatcomando di avvio da quando ho avviato il contenitore non esposto in una composizione docker che crea una rete.
emazzotta,

35

Gli hack di IPtables non funzionano, almeno su Docker 1.4.1.

Il modo migliore sarebbe eseguire un altro contenitore con la porta esposta e inoltrare con socat. Questo è ciò che ho fatto per connettermi (temporaneamente) al database 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

2
Gli hack iptables dovrebbero essere eseguiti dal computer host, non dal contenitore docker. In sostanza, inoltra le richieste a determinate porte sull'host nelle porte contenitore docker appropriate. Non è affatto specifico per la finestra mobile e puoi farlo su host completamente diversi. Puoi aggiungere la formattazione del codice al tuo file Docker? Sembra abbastanza utile.
AusIV

1
Febbraio 2016: con Docker 1.9.1, questa è stata l'unica soluzione che ha funzionato con successo per me. Nessuna delle soluzioni IPTables ha funzionato. Vale la pena consigliare di utilizzare la stessa FROMimmagine di base del proprio contenitore DB, per un uso efficiente delle risorse.
Excalibur,

4
Potresti voler provare apline / socat . Viene fornito con socat preinstallato e accetta le opzioni socat come comando, quindi non è necessario scrivere un file Docker.
trkoch,

35

Ecco un'altra idea. Utilizzare SSH per eseguire il port forwarding; questo ha il vantaggio di funzionare anche in OS X (e probabilmente Windows) quando l'host Docker è una macchina virtuale.

docker exec -it <containterid> ssh -R5432:localhost:5432 <user>@<hostip>

4
nel mio caso l'immagine docker in esecuzione non ha ssh binario
Radu Toader

Devo creare una chiave SSH sul mio computer e inserirla nel contenitore della finestra mobile per questo?
Ottaviano,

@octavian ssh-keygen -t rsa all'interno del contenitore. aggiungi la chiave del pub a authorized_keys sull'host
Luca W

8

Ho dovuto affrontare questo stesso problema e sono stato in grado di risolverlo senza arrestare nessuno dei miei contenitori in esecuzione. Questa è una soluzione aggiornata a febbraio 2016, utilizzando Docker 1.9.1. Comunque, questa risposta è una versione dettagliata della risposta di @ ricardo-branco, ma più in profondità per i nuovi utenti.

Nel mio scenario, volevo collegarmi temporaneamente a MySQL in esecuzione in un contenitore e poiché altri contenitori di applicazioni sono collegati ad esso, arrestare, riconfigurare e rieseguire il contenitore del database non è stato un inizio.

Dato che mi piacerebbe accedere al database MySQL esternamente (da Sequel Pro tramite tunneling SSH), userò la porta 33306sul computer host. (Non3306 , nel caso in cui sia in esecuzione un'istanza di MySQL esterna.)

Circa un'ora di ottimizzazione di iptables si è rivelata inutile, anche se:

Passo dopo passo, ecco cosa ho fatto:

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

Modifica dockerfile, inserendo questo all'interno:

# 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

Quindi crea l'immagine:

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

Quindi eseguirlo, collegandolo al contenitore in esecuzione. (Utilizzare -dinvece di -rmmantenerlo in background fino a quando non viene esplicitamente arrestato e rimosso. In questo caso, lo voglio solo temporaneamente.)

docker run -it --rm --name=db-33306 --link the_live_db_container:db -p 33306:33306  your-namespace/db-expose-33306

Puoi usare direttamente il verbo / socat o l'immagine alpine / socat. Vedi verbo / socat
pjotr_dolphin,

7

Per aggiungere alla soluzione di risposta accettata iptables , ho dovuto eseguire altri due comandi sull'host per aprirlo al mondo esterno.

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: stavo aprendo la porta https (443), il mio IP interno docker era 172.17.0.2

Nota 2: queste regole e temporrary e dureranno solo fino al riavvio del contenitore


Questo ha funzionato per me ... tuttavia ci sono stati alcuni Cavat più tardi. Come indicato nella nota 2, questo si interromperà al riavvio dei contenitori, quindi potrebbe trovarsi su un IP diverso. Ma soprattutto la finestra mobile non ha idea di queste voci iptable, quindi non le rimuoverà quando successivamente riavvierai completamente il servizio e otterrai la finestra mobile per farlo correttamente. Il risultato sono state più voci iptable esattamente identiche, che hanno causato un errore con errori o indicazioni minimi o nulli sulla causa. Una volta che sono state stabilite regole extra, il problema è scomparso. In altre parole, controlla MOLTO attentamente le tue tabelle IP, dopo ogni modifica.
Anthony

5

Puoi usare SSH per creare un tunnel ed esporre il tuo contenitore nel tuo host.

Puoi farlo in entrambi i modi, dal container all'host e dall'host al container. Ma hai bisogno di uno strumento SSH come OpenSSH in entrambi (client in uno e server in un altro).

Ad esempio, nel contenitore, puoi farlo

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

È possibile trovare l'indirizzo IP del contenitore da questa riga (nel contenitore):

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

Quindi nell'host, puoi semplicemente fare:

sudo ssh -NfL 80:0.0.0.0:80 root@172.17.0.2

Questo ha funzionato per me, con una piccola correzione ... questo comando ha funzionato: sudo ssh -NfL 25252: 172.17.0.50: 22 $ USER @ localhost
Alexander Guo

3

Nel caso in cui nessuna risposta funzioni per qualcuno, controlla se il contenitore di destinazione è già in esecuzione nella rete docker:

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

Salvalo per dopo nella variabile $NET_NAME:

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

In caso affermativo, è necessario eseguire il contenitore proxy nella stessa rete.

Quindi cerca l'alias per il contenitore:

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

Salvalo per dopo nella variabile $ALIAS:

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

Ora esegui socatun contenitore nella rete $NET_NAMEper passare alla porta $ALIASesposta (ma non pubblicata) del contenitore ed:

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

2

È possibile utilizzare una rete overlay come Weave Net , che assegnerà un indirizzo IP univoco a ciascun contenitore ed esporrà implicitamente tutte le porte a ogni parte del contenitore della rete.

Weave fornisce anche l'integrazione della rete host . È disabilitato per impostazione predefinita ma, se si desidera accedere anche agli indirizzi IP del contenitore (e tutte le sue porte) dall'host, è possibile eseguire semplicemente eseguireweave expose .

Informativa completa: lavoro a Weaveworks.


2

C'è un comodo wrapper HAProxy.

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

Questo crea un HAProxy nel contenitore di destinazione. vai tranquillo.



1

Leggi prima la risposta di Ricardo . Questo ha funzionato per me.

Tuttavia, esiste uno scenario in cui questo non funzionerà se il container in esecuzione è stato avviato utilizzando la finestra mobile di composizione. Questo perché docker-compose (sto eseguendo docker 1.17) crea una nuova rete. Il modo di affrontare questo scenario sarebbe

docker network ls

Quindi aggiungere quanto segue docker run -d --name sqlplus --link db:db -p 1521:1521 sqlplus --net network_name


0

Non è possibile eseguire il mapping delle porte in tempo reale, ma esistono diversi modi in cui è possibile fornire a un contenitore Docker ciò che equivale a un'interfaccia reale come avrebbe una macchina virtuale.

Interfacce Macvlan

Docker ora include un driver di rete Macvlan . Ciò collega una rete Docker a un'interfaccia del "mondo reale" e consente di assegnare gli indirizzi di tali reti direttamente al contenitore (come una modalità con bridge di macchine virtuali).

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

pipeworkpuò anche mappare una vera interfaccia in un contenitore o configurare una sottointerfaccia nelle versioni precedenti di Docker.

IP di routing

Se hai il controllo della rete puoi instradare reti aggiuntive verso l'host Docker da utilizzare nei contenitori.

Quindi assegnare quella rete ai container e configurare l'host Docker per instradare i pacchetti tramite la rete docker.

Interfaccia host condivisa

L' --net hostopzione consente all'interfaccia host di essere condivisa in un contenitore, ma questa probabilmente non è una buona configurazione per eseguire più contenitori su un host a causa della natura condivisa.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.