Offenlegen eines Ports in einem Live-Docker-Container

408

Ich versuche, einen Docker-Container zu erstellen, der sich wie eine voll funktionsfähige virtuelle Maschine verhält. Ich weiß, dass ich die EXPOSE-Anweisung in einer Docker-Datei verwenden kann, um einen Port verfügbar zu machen, und ich kann das -pFlag mit verwenden docker run, um Ports zuzuweisen. Gibt es jedoch einen Befehl zum Öffnen / Zuordnen zusätzlicher Ports, wenn ein Container tatsächlich ausgeführt wird?

Angenommen, ich habe einen Docker-Container, auf dem sshd ausgeführt wird. Jemand anderes, der den Container ssh verwendet, installiert httpd. Gibt es eine Möglichkeit, Port 80 auf dem Container verfügbar zu machen und ihn Port 8080 auf dem Host zuzuordnen, damit Benutzer den im Container ausgeführten Webserver besuchen können, ohne ihn neu zu starten?

reberhardt
quelle
1
Sie können dem Container eine routbare IP-Adresse geben , sodass keine Portzuordnung erforderlich ist.
Matt

Antworten:

331

Sie können dies nicht über Docker tun, aber Sie können vom Host-Computer aus auf den nicht exponierten Port des Containers zugreifen.

Wenn Sie einen Container haben, dessen Port 8000 ausgeführt wird, können Sie ihn ausführen

wget http://container_ip:8000

Führen Sie die folgenden 2 Befehle aus, um die IP-Adresse des Containers abzurufen:

docker ps

docker inspect container_name | grep IPAddress

Intern ruft Docker iptables auf, wenn Sie ein Image ausführen. Daher funktioniert möglicherweise eine Variation davon.

So legen Sie den Port 8000 des Containers auf dem Port 8001 Ihres lokalen Hosts frei:

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

Eine Möglichkeit, dies herauszufinden, besteht darin, einen anderen Container mit der gewünschten Portzuordnung einzurichten und die Ausgabe des Befehls iptables-save zu vergleichen (ich musste jedoch einige der anderen Optionen entfernen, die den Datenverkehr über das Docker erzwingen Proxy).

HINWEIS: Dies untergräbt den Docker und sollte daher mit dem Bewusstsein erfolgen, dass möglicherweise blauer Rauch entsteht

ODER

Eine andere Alternative besteht darin, die Option (neu? Post 0.6.6?) -P zu suchen, bei der zufällige Host-Ports verwendet werden, und diese dann zu verkabeln.

ODER

Mit 0.6.5 können Sie die LINKs-Funktion verwenden, um einen neuen Container aufzurufen, der mit dem vorhandenen kommuniziert, und einige zusätzliche Weiterleitungen an die -p-Flags dieses Containers. (Ich habe noch keine LINKs verwendet)

ODER

mit Docker 0.11? Sie können docker run --net host ..Ihren Container direkt an die Netzwerkschnittstellen des Hosts anschließen (dh das Netz hat keinen Namensabstand), sodass alle im Container geöffneten Ports verfügbar sind.

SvenDowideit
quelle
6
Dies scheint zumindest mit Docker 1.3.0 nicht zu funktionieren. Die DOCKER DNAT-Regel wird erstellt, wenn Docker mit -p ausgeführt wird, aber das manuelle Hinzufügen scheint keine Verbindungen zuzulassen. Das seltsame Löschen der Regel, während ein Container ausgeführt wird, scheint ihn auch nicht daran zu hindern, zu funktionieren ...
silasdavis
4
Vielen Dank. Ich wurde in das Gefühl der Sicherheit hineingelullt, dass nicht exponierte Häfen sicher waren.
Seanmcl
Es kann nützlich sein, Dinge zu automatisieren und jq + sed zu verwenden: 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 Anstatt zu rechnen jqund sedSie können die -fOption verwenden docker inspect:CONTAINER_IP=$(docker inspect -f '{{ .NetworkSettings.IPAddress }}' container_name)
pwes
für diejenigen, die kmkeen.com/jshon jshon -e 0 -e NetworkSettings -e Networks -e bridge -e IPAddress -u
slf
138

Folgendes würde ich tun:

  • Übernehmen Sie den Live-Container.
  • Führen Sie den Container mit dem neuen Image und geöffneten Ports erneut aus (ich würde empfehlen, ein freigegebenes Volume bereitzustellen und auch den ssh-Port zu öffnen).
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
quelle
30
Der wichtigste Teil meiner Frage ist, dass dies ohne Neustart des Containers geschehen muss ... Wenn Sie in den neuen Container wechseln, werden möglicherweise Dateien beibehalten, aber alle laufenden Prozesse werden effektiv abgebrochen, und ein Neustart auf einem physischen Computer ähnelt. Ich muss das tun, ohne dass das passiert. Trotzdem danke!
Reberhardt
in Ordung. Ich würde es jedoch mehr mit dem Starten einer parallelen Instanz vergleichen. Da beide jetzt ausgeführt werden (alt und neu), ist es möglicherweise einfacher, einen Proxy für den neuen Container zu erstellen, sobald die erforderlichen Migrationen abgeschlossen sind.
Bosky101
Ja, parallele Instanzen und Reverse Proxy sind einige der Hauptgründe, warum ich Docker liebe. In diesem Szenario muss ich jedoch alle laufenden Prozesse im Container beibehalten, die möglicherweise über SSH gestartet wurden. Während die ausführbaren Dateien beim Festschreiben des Images und beim Starten einer parallelen Instanz erhalten bleiben, werden die ausführbaren Dateien selbst nicht gestartet und alles im RAM geht verloren.
Reberhardt
6
Warum rennst du sudo dockerund nicht nur docker?
Thiago Figueiro
Dieser Tipp hat mir sehr geholfen, da ich Tonnen von Paketen in einem langsamen Netzwerk installiert habe. Durch das Ausführen war docker commitich bereit, die Anwendung erneut zu testen, anstatt Stunden damit zu verbringen, alles neu zu installieren.
Gustavohenke
48

Sie können zwar keinen neuen Port eines vorhandenen Containers verfügbar machen, aber Sie können einen neuen Container im selben Docker-Netzwerk starten und ihn dazu bringen, den Datenverkehr an den ursprünglichen Container weiterzuleiten.

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

Gearbeitetes Beispiel

Starten Sie einen Webdienst , der Port 80 überwacht , aber seinen internen Port 80 nicht verfügbar macht (oops!):

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

Suchen Sie die Docker-Netzwerk-IP:

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

Starten Sie verb/socatmit offengelegtem Port 8080 und leiten Sie den TCP-Verkehr an den Port 80 dieser IP weiter:

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

Sie können jetzt unter http: // localhost: 8080 / auf pastebin zugreifen , und Ihre Anforderungen werden an socat:1234die weitergeleitet, an die es weitergeleitet wird pastebin:80, und die Antwort verläuft in umgekehrter Reihenfolge auf demselben Weg.

RobM
quelle
7
Ziemlich clever! Wahrscheinlich besser zu verwenden verb/socat:alpine, da das Image 5% des Footprints ausmacht (es sei denn, Sie stoßen auf libc- oder DNS-Inkompatibilitäten ).
Jpaugh
3
Es gibt auchalpine/socat
Jamby
3
Hervorragende Antwort. Einfach, ein Liner, der es ohne schmutzige Hacks schafft.
Bruno Brant
2
Das hat bei mir perfekt funktioniert, danke! Ich musste --net myfoldername_defaultmeinen verb/socatStartbefehl erweitern, da ich den nicht exponierten Container in einer Docker-Komposition gestartet habe, die ein Netzwerk erstellt.
Emazzotta
35

IPtables-Hacks funktionieren zumindest unter Docker 1.4.1 nicht.

Der beste Weg wäre, einen anderen Container mit dem exponierten Port zu betreiben und mit socat weiterzuleiten. Folgendes habe ich getan, um mit SQLPlus (vorübergehend) eine Verbindung zur Datenbank herzustellen:

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
quelle
2
Die iptables-Hacks sollen vom Host-Computer ausgeführt werden, nicht vom Docker-Container. Im Wesentlichen werden Anforderungen an bestimmte Ports auf dem Host an die entsprechenden Docker-Container-Ports weitergeleitet. Es ist überhaupt nicht Docker-spezifisch, und Sie können es für völlig andere Hosts tun. Können Sie Ihrer Docker-Datei eine Code-Formatierung hinzufügen? Es scheint ziemlich nützlich zu sein.
AusIV
1
Februar 2016: Mit Docker 1.9.1 war dies die einzige Lösung, die für mich erfolgreich funktioniert hat. Keine der IPTables-Lösungen funktionierte. Es empfiehlt sich, dasselbe FROMBasis-Image wie Ihr DB-Container zu verwenden, um Ressourcen effizient zu nutzen.
Excalibur
4
Vielleicht möchten Sie apline / socat ausprobieren. Es wird mit vorinstalliertem socat geliefert und akzeptiert socat-Optionen als Befehl, sodass Sie überhaupt keine Docker-Datei schreiben müssen.
Trkoch
35

Hier ist eine andere Idee. Verwenden Sie SSH, um die Portweiterleitung durchzuführen. Dies hat den Vorteil, dass Sie auch unter OS X (und wahrscheinlich unter Windows) arbeiten können, wenn Ihr Docker-Host eine VM ist.

docker exec -it <containterid> ssh -R5432:localhost:5432 <user>@<hostip>
Scott Carlson
quelle
4
In meinem Fall hat das Docker-Image, das ausgeführt wurde, keine binäre SSH
Radu Toader
Muss ich auf meinem Computer einen SSH-Schlüssel erstellen und ihn dafür in den Docker-Container legen?
Oktavian
@octavian ssh-keygen -t rsa im Container. Hinzufügen eines Pub-Schlüssels zu autorisierten Schlüsseln auf dem Host
Luke W
8

Ich musste mich mit demselben Problem befassen und konnte es lösen, ohne einen meiner laufenden Container anzuhalten. Dies ist eine Lösung, die ab Februar 2016 mit Docker 1.9.1 auf dem neuesten Stand ist. Wie auch immer, diese Antwort ist eine detaillierte Version der Antwort von @ ricardo-branco, aber für neue Benutzer ausführlicher.

In meinem Szenario wollte ich vorübergehend eine Verbindung zu MySQL herstellen, das in einem Container ausgeführt wird. Da andere Anwendungscontainer damit verknüpft sind, war das Stoppen, Neukonfigurieren und erneutes Ausführen des Datenbankcontainers kein Starter.

Da ich extern auf die MySQL-Datenbank zugreifen möchte (von Sequel Pro über SSH-Tunneling), werde ich den Port 33306auf dem Host-Computer verwenden. (Nicht 3306, nur für den Fall, dass eine äußere MySQL-Instanz ausgeführt wird.)

Ungefähr eine Stunde, in der Iptables optimiert wurden, erwies sich als erfolglos, obwohl:

Schritt für Schritt habe ich Folgendes getan:

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

Bearbeiten dockerfile, platzieren Sie dies in:

# 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

Dann erstellen Sie das Bild:

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

Führen Sie es dann aus und verknüpfen Sie es mit Ihrem laufenden Container. (Verwenden Sie -dstatt -rm, um es im Hintergrund zu belassen, bis es explizit gestoppt und entfernt wird. Ich möchte, dass es in diesem Fall nur vorübergehend ausgeführt wird.)

docker run -it --rm --name=db-33306 --link the_live_db_container:db -p 33306:33306  your-namespace/db-expose-33306
Excalibur
quelle
Sie können das Verb / socat oder das alpine / socat-Bild direkt verwenden. Siehe verb / socat
pjotr_dolphin
7

Um die akzeptierte Antwortlösung zu erweitern iptables , musste ich zwei weitere Befehle auf dem Host ausführen, um ihn für die Außenwelt zu öffnen.

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

Hinweis: Ich habe Port https (443) geöffnet, meine Docker-interne IP war 172.17.0.2

Hinweis 2: Diese Regeln und temporären Regeln gelten nur bis zum Neustart des Containers

dstj
quelle
Das hat bei mir funktioniert ... aber später gab es einige Cavats. Gemäß Anmerkung 2 wird dies beendet, wenn die Container neu gestartet werden. Möglicherweise befindet sie sich auf einer anderen IP als dieser. Noch wichtiger ist jedoch, dass Docker keine Ahnung von diesen iptable-Einträgen hat. Sie werden daher nicht entfernt, wenn Sie den Dienst später vollständig neu starten und Docker dazu bringen, dies ordnungsgemäß zu tun. Das Ergebnis waren mehrere iptable-Einträge, die genau gleich waren, was dazu führte, dass es mit wenig bis gar keinen Fehlern oder Hinweisen auf die Ursache fehlschlug. Sobald zusätzliche Regeln festgelegt wurden, verschwand das Problem. Mit anderen Worten, überprüfen Sie Ihre IP-Tabellen nach jeder Änderung SEHR sorgfältig.
Anthony
5

Sie können SSH verwenden, um einen Tunnel zu erstellen und Ihren Container in Ihrem Host verfügbar zu machen.

Sie können dies auf beide Arten tun, von Container zu Host und von Host zu Container. Sie benötigen jedoch ein SSH-Tool wie OpenSSH in beiden (Client in einem und Server in einem anderen).

Im Container können Sie dies beispielsweise tun

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

Die IP-Adresse des Containers finden Sie in dieser Zeile (im Container):

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

Dann können Sie im Host einfach Folgendes tun:

sudo ssh -NfL 80:0.0.0.0:80 [email protected]
Tonne
quelle
Dies funktionierte für mich mit einer kleinen Korrektur ... dieser Befehl funktionierte: sudo ssh -NfL 25252: 172.17.0.50: 22 $ USER @ localhost
Alexander Guo
3

Falls für jemanden keine Antwort funktioniert - überprüfen Sie, ob Ihr Zielcontainer bereits im Docker-Netzwerk ausgeführt wird:

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

Speichern Sie es für später in der Variablen $NET_NAME:

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

Wenn ja, sollten Sie den Proxy-Container im selben Netzwerk ausführen.

Suchen Sie als Nächstes den Alias ​​für den Container:

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

Speichern Sie es für später in der Variablen $ALIAS:

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

Führen Sie nun socateinen Container im Netzwerk aus, $NET_NAMEum eine Brücke zum $ALIASexponierten (aber nicht veröffentlichten) Port des ED-Containers zu schlagen:

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

Sie können ein Overlay-Netzwerk wie Weave Net verwenden , das jedem Container eine eindeutige IP-Adresse zuweist und implizit alle Ports für jeden Containerteil des Netzwerks verfügbar macht.

Weave bietet auch die Integration des Host-Netzwerks . Es ist standardmäßig deaktiviert. Wenn Sie jedoch auch vom Host aus auf die Container-IP-Adressen (und alle ihre Ports) zugreifen möchten, können Sie sie einfach ausführen weave expose.

Vollständige Offenlegung: Ich arbeite bei Weaveworks.

fons
quelle
2

Es gibt eine praktische HAProxy-Hülle.

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

Dadurch wird ein HAProxy zum Zielcontainer erstellt. kinderleicht.

kwerle
quelle
1

Lesen Sie zuerst Ricardos Antwort. Das hat bei mir funktioniert.

Es gibt jedoch ein Szenario, in dem dies nicht funktioniert, wenn der laufende Container mit Docker-Compose gestartet wurde. Dies liegt daran, dass Docker-Compose (ich verwende Docker 1.17) ein neues Netzwerk erstellt. Der Weg, um dieses Szenario anzugehen, wäre

docker network ls

Fügen Sie dann Folgendes hinzu docker run -d --name sqlplus --link db:db -p 1521:1521 sqlplus --net network_name

ice.nicer
quelle
0

Es ist nicht möglich, eine Live-Portzuordnung durchzuführen, aber es gibt mehrere Möglichkeiten, einem Docker-Container zu geben, was einer realen Schnittstelle wie einer virtuellen Maschine entspricht.

Macvlan-Schnittstellen

Docker enthält jetzt einen Macvlan-Netzwerktreiber . Dadurch wird ein Docker-Netzwerk an eine "reale" Schnittstelle angeschlossen, und Sie können diese Netzwerkadressen direkt dem Container zuweisen (wie im Bridged-Modus für virtuelle Maschinen).

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

pipeworkSie können auch eine reale Schnittstelle einem Container zuordnen oder eine Unterschnittstelle in älteren Docker-Versionen einrichten.

IPs weiterleiten

Wenn Sie die Kontrolle über das Netzwerk haben, können Sie zusätzliche Netzwerke zur Verwendung in den Containern an Ihren Docker-Host weiterleiten.

Anschließend weisen Sie dieses Netzwerk den Containern zu und richten Ihren Docker-Host so ein, dass die Pakete über das Docker-Netzwerk weitergeleitet werden.

Shared Host-Schnittstelle

Mit dieser --net hostOption kann die Hostschnittstelle für einen Container freigegeben werden. Aufgrund der gemeinsamen Nutzung ist dies jedoch wahrscheinlich keine gute Einrichtung, um mehrere Container auf einem Host auszuführen.

Matt
quelle