Odsłanianie portu w aktywnym kontenerze Docker

408

Próbuję utworzyć kontener Docker, który działa jak pełna maszyna wirtualna. Wiem, że mogę użyć instrukcji EXPOSE w pliku Docker, aby odsłonić port, i mogę użyć -pflagi z, docker runaby przypisać porty, ale czy po uruchomieniu kontenera istnieje polecenie otwarcia / mapowania dodatkowych portów na żywo?

Załóżmy na przykład, że mam kontener Docker z uruchomionym sshd. Ktoś inny używający kontenera ssh i instaluje httpd. Czy istnieje sposób na ujawnienie portu 80 w kontenerze i zamapowanie go na port 8080 na hoście, aby ludzie mogli odwiedzić serwer WWW działający w kontenerze bez jego ponownego uruchamiania?

reberhardt
źródło
1
Możesz nadać kontenerowi routowalny adres IP , więc mapowanie portów nie jest wymagane.
Matt

Odpowiedzi:

331

Nie można tego zrobić za pomocą Dockera, ale można uzyskać dostęp do nieosłoniętego portu kontenera z komputera hosta.

jeśli masz kontener, który z czymś działającym na jego porcie 8000, możesz uruchomić

wget http://container_ip:8000

Aby uzyskać adres IP kontenera, uruchom 2 polecenia:

docker ps

docker inspect container_name | grep IPAddress

Wewnętrznie Docker uruchamia się, aby wywołać iptables po uruchomieniu obrazu, więc być może niektóre warianty będą działać.

aby odsłonić port kontenera 8000 na porcie hostów lokalnych 8001:

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

Jednym ze sposobów rozwiązania tego problemu jest skonfigurowanie innego kontenera z żądanym mapowaniem portów i porównanie wyników iptables-save (musiałem jednak usunąć niektóre inne opcje, które zmuszają ruch do przejścia przez doker pełnomocnik).

UWAGA: jest to dokowanie niwelujące, więc należy to zrobić ze świadomością, że może on tworzyć niebieski dym

LUB

Inną alternatywą jest poszukiwanie opcji (nowa? Post 0.6.6?) -P - która wykorzysta losowe porty hosta, a następnie połączy je.

LUB

w wersji 0.6.5 możesz użyć funkcji LINK, aby wywołać nowy kontener, który będzie komunikował się z istniejącym, z dodatkowym przekazywaniem flag -p tego kontenera? (Nie korzystałem jeszcze z LINK-ów)

LUB

z dokerem 0.11? możesz użyć, docker run --net host ..aby podłączyć kontener bezpośrednio do interfejsów sieciowych hosta (tzn. sieć nie ma odstępów między nazwami), a zatem wszystkie porty otwierane w kontenerze są widoczne.

SvenDowideit
źródło
6
Nie działa to przynajmniej z dokerem 1.3.0. Reguła DOCKER DNAT jest tworzona podczas uruchamiania dokera z opcją -p, ale dodanie jej ręcznie nie wydaje się zezwalać na połączenia. Dziwne usunięcie reguły podczas działania kontenera nie wydaje się też powstrzymywać go od działania ...
silasdavis
4
Dzięki. Uświadomiłem sobie, że porty nienaświetlone są bezpieczne.
seanmcl
Automatyzacja rzeczy i używanie jq + sed może być przydatne: 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 Zamiast fakturowania jqi sedmożna użyć -fopcji docker inspect:CONTAINER_IP=$(docker inspect -f '{{ .NetworkSettings.IPAddress }}' container_name)
pwes
dla tych, którzy wolą kmkeen.com/jshon jshon -e 0 -e NetworkSettings -e Networks -e bridge -e IPAddress -u
slf 13.06.16
138

Oto co bym zrobił:

  • Zatwierdź aktywny kontener.
  • Uruchom kontener ponownie z nowym obrazem, z otwartymi portami (zaleciłbym zamontowanie wspólnego woluminu i otwarcie portu 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
źródło
31
Kluczową częścią mojego pytania jest to, że musi się to zdarzyć bez ponownego uruchamiania kontenera ... Przejście do nowego kontenera może przechowywać pliki, ale skutecznie zabije wszystkie uruchomione procesy i będzie podobne do ponownego uruchomienia komputera fizycznego. Muszę to zrobić bez tego. Ale dziękuję!
reberhardt
w porządku. porównałbym to bardziej do uruchomienia równoległego wystąpienia. ponieważ oba są teraz uruchomione (stary i nowy), łatwiejsze może być proxy do nowego kontenera po zakończeniu niezbędnych migracji.
bosky101
Tak, równoległe instancje i odwrotne proxy są jednymi z głównych powodów, dla których kocham Docker. Jednak w tym scenariuszu muszę zachować wszystkie uruchomione procesy w kontenerze, które mogły zostać uruchomione przez SSH. Podczas gdy pliki wykonywalne zostaną zachowane podczas zatwierdzania obrazu i uruchamiania równoległej instancji, same pliki wykonywalne nie zostaną uruchomione, a pamięć RAM zostanie utracona.
reberhardt
6
Dlaczego biegasz, sudo dockera nie tylko docker?
Thiago Figueiro,
Ta wskazówka bardzo mi pomogła, ponieważ zainstalowałem mnóstwo pakietów w wolnej sieci. Uruchamiając, docker commitbyłem gotowy ponownie przetestować aplikację zamiast spędzać godziny na ponownej instalacji wszystkiego.
gustavohenke
49

Chociaż nie możesz ujawnić nowego portu istniejącego kontenera, możesz uruchomić nowy kontener w tej samej sieci Docker i zmusić go do przekazywania ruchu do oryginalnego kontenera.

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

Przykład działania

Uruchom usługę internetową, która nasłuchuje na porcie 80, ale nie ujawniaj swojego portu wewnętrznego 80 (ups!):

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

Znajdź adres IP swojej sieci Docker:

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

Uruchom verb/socatz widocznym portem 8080 i poproś go o przekazywanie ruchu TCP do portu 80 tego adresu IP:

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

Możesz teraz uzyskać dostęp do pastebin na http: // localhost: 8080 / , a twoje żądania idą do socat:1234tego, do którego je przesyła dalej pastebin:80, a odpowiedź przechodzi tą samą ścieżką w odwrotną stronę.

RobM
źródło
7
Całkiem sprytne! Jednak prawdopodobnie lepiej go użyć verb/socat:alpine, ponieważ jego obraz ma 5% powierzchni (chyba że napotkasz niezgodności libc lub DNS ).
jpaugh
3
Jest teżalpine/socat
Jamby
3
Doskonała odpowiedź. Prosta, jedna wkładka, która załatwi sprawę bez żadnych brudnych hacków.
Bruno Brant,
2
To zadziałało dla mnie idealnie, dzięki! Musiałem dodać --net myfoldername_defaultdo mojej verb/socatkomendy uruchamiania, ponieważ uruchomiłem nienaświetlony kontener w kompozycji dokera, która tworzy sieć.
emazzotta
35

Hacki IPtables nie działają, przynajmniej w Docker 1.4.1.

Najlepszym sposobem byłoby uruchomienie innego kontenera z odsłoniętym portem i przekaźnik z socat. Oto, co zrobiłem, aby (tymczasowo) połączyć się z bazą danych za pomocą SQLPlus:

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

Plik Docker:

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
źródło
2
Hacki iptables powinny działać z komputera hosta, a nie z kontenera dokera. Zasadniczo przekazuje żądania do niektórych portów hosta do odpowiednich portów kontenerów dokujących. W ogóle nie jest to specyficzne dla dokerów i możesz to zrobić na zupełnie różnych hostach. Czy możesz dodać formatowanie kodu do pliku Docker? To wydaje się całkiem przydatne.
AusIV,
1
Luty 2016: Działając Docker 1.9.1, było to jedyne rozwiązanie, które z powodzeniem działało dla mnie. Żadne z rozwiązań IPTables nie działało. Warto skorzystać z tego samego FROMobrazu podstawowego co kontener DB, aby efektywnie wykorzystać zasoby.
Excalibur
4
Możesz spróbować apline / socat . Jest fabrycznie zainstalowany socat i akceptuje opcje socat jako polecenie, więc nie musisz w ogóle pisać pliku Docker.
trkoch
35

Oto inny pomysł. Użyj SSH do przekierowania portów; ma to tę zaletę, że działa również w systemie OS X (i prawdopodobnie w systemie Windows), gdy host Dockera jest maszyną wirtualną.

docker exec -it <containterid> ssh -R5432:localhost:5432 <user>@<hostip>
Scott Carlson
źródło
4
w moim przypadku działający obraz dokera nie ma binarnego ssh
Radu Toader
Czy muszę w tym celu utworzyć klucz SSH i umieścić go w kontenerze dokera?
octavian
@octavian ssh-keygen -t rsa wewnątrz kontenera. dodaj klucz pubu do uprawnionego klucze na hoście
Łukasz W
8

Musiałem poradzić sobie z tym samym problemem i byłem w stanie go rozwiązać bez zatrzymywania któregokolwiek z moich uruchomionych kontenerów. To rozwiązanie jest aktualne od lutego 2016 r. Przy użyciu Docker 1.9.1. W każdym razie ta odpowiedź jest szczegółową wersją odpowiedzi @ ricardo-branco, ale bardziej szczegółową dla nowych użytkowników.

W moim scenariuszu chciałem tymczasowo połączyć się z MySQL działającym w kontenerze, a ponieważ inne kontenery aplikacji są z nim połączone, zatrzymywanie, rekonfigurowanie i ponowne uruchamianie kontenera bazy danych nie było uruchomieniem.

Ponieważ chciałbym uzyskać dostęp do bazy danych MySQL zewnętrznie (z Sequel Pro poprzez tunelowanie SSH), użyję portu 33306na hoście. (Nie 3306, na wypadek, gdyby działała zewnętrzna instancja MySQL.)

Około godziny poprawiania iptables okazało się bezowocne, mimo że:

Krok po kroku, oto co zrobiłem:

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

Edytuj dockerfile, umieszczając to w środku:

# 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

Następnie zbuduj obraz:

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

Następnie uruchom go, łącząc się z działającym kontenerem. (Użyj -dzamiast -rmtrzymać go w tle, dopóki nie zostanie wyraźnie zatrzymany i usunięty. W tym przypadku chcę, aby działał tylko tymczasowo).

docker run -it --rm --name=db-33306 --link the_live_db_container:db -p 33306:33306  your-namespace/db-expose-33306
Excalibur
źródło
Możesz użyć czasownika / socat lub obrazu alpejskiego / socat bezpośrednio. Zobacz czasownik / socat
pjotr_dolphin
7

Aby dodać do akceptowanego iptables rozwiązania odpowiedzi , musiałem uruchomić jeszcze dwa polecenia na hoście, aby otworzyć je na świat zewnętrzny.

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

Uwaga: otwierałem port https (443), mój wewnętrzny adres IP dokera to 172.17.0.2

Uwaga 2: Te reguły są tymczasowe i będą obowiązywać tylko do momentu ponownego uruchomienia kontenera

dstj
źródło
To zadziałało dla mnie ... jednak niektóre Cavat były później. Zgodnie z uwagą 2 przestanie to działać, jeśli pojemniki zostaną ponownie uruchomione, wówczas może znajdować się w innym adresie IP. Ale co ważniejsze, doker nie ma pojęcia o tych iptable wpisach, więc nie zostaną one usunięte, gdy później w pełni zrestartujesz usługę i poprosisz dokera, aby zrobił to poprawnie. Rezultatem było wiele iptable wpisów, które były dokładnie takie same, co spowodowało niepowodzenie z niewielkimi lub żadnymi błędami lub wskazaniami co do przyczyny. Po orzeczeniu dodatkowych zasad problem zniknął. Innymi słowy, po każdej zmianie dokładnie przejrzyj swoje tabele IP BARDZO.
Anthony
5

Możesz użyć SSH, aby utworzyć tunel i odsłonić swój kontener na hoście.

Możesz to zrobić na dwa sposoby, od kontenera do hosta i od hosta do kontenera. Ale potrzebujesz narzędzia SSH, takiego jak OpenSSH w obu (klient w jednym i serwer w innym).

Na przykład w kontenerze możesz to zrobić

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

Adres IP kontenera można znaleźć w tym wierszu (w kontenerze):

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

Następnie na hoście możesz po prostu zrobić:

sudo ssh -NfL 80:0.0.0.0:80 [email protected]
tona
źródło
To zadziałało dla mnie, z jedną małą korektą ... to polecenie działało: sudo ssh -NfL 25252: 172.17.0.50: 22 $ USER @ localhost
Alexander Guo
3

Jeśli żadna odpowiedź nie zadziała dla kogoś - sprawdź, czy docelowy kontener jest już uruchomiony w sieci dokerów:

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

Zapisz na później w zmiennej $NET_NAME:

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

Jeśli tak, należy uruchomić kontener proxy w tej samej sieci.

Następnie wyszukaj alias kontenera:

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

Zapisz na później w zmiennej $ALIAS:

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

Teraz uruchom socatw kontenerze w sieci, $NET_NAMEaby połączyć się z $ALIASodsłoniętym (ale nie opublikowanym) portem kontenera 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
źródło
2

Możesz użyć sieci nakładek, takiej jak Weave Net , która przypisze unikalny adres IP do każdego kontenera i pośrednio udostępni wszystkie porty każdej kontenerowej części sieci.

Weave zapewnia również integrację sieci hosta . Jest domyślnie wyłączony, ale jeśli chcesz również uzyskać dostęp do adresów IP kontenera (i wszystkich jego portów) z hosta, możesz uruchomić, po prostu uruchomweave expose .

Pełne ujawnienie: pracuję w Weaveworks.

fons
źródło
2

Jest poręczne opakowanie HAProxy.

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

To tworzy HAProxy do docelowego kontenera. bułka z masłem.

kwerle
źródło
1

Przeczytaj najpierw odpowiedź Ricardo . To zadziałało dla mnie.

Istnieje jednak scenariusz, w którym nie zadziała to, jeśli uruchomiony kontener został wyrzucony przy użyciu narzędzia dokującego-komponuj. Wynika to z faktu, że program dokujący-komponuj (korzystam z programu dokującego 1.17) tworzy nową sieć. Można rozwiązać ten scenariusz

docker network ls

Następnie dołącz następujące elementy docker run -d --name sqlplus --link db:db -p 1521:1521 sqlplus --net network_name

ice.nicer
źródło
0

Nie jest możliwe mapowanie portów na żywo, ale istnieje wiele sposobów na przekazanie kontenerowi Docker poziomu odpowiadającego rzeczywistemu interfejsowi, jaki miałaby maszyna wirtualna.

Interfejsy Macvlan

Docker zawiera teraz sterownik sieci Macvlan . Umożliwia to podłączenie sieci Docker do interfejsu „świata rzeczywistego” i umożliwia przypisanie tych adresów sieciowych bezpośrednio do kontenera (podobnie jak tryb mostkowania maszyn wirtualnych).

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

pipeworkmoże także odwzorować prawdziwy interfejs na kontener lub skonfigurować interfejs podrzędny w starszych wersjach Dockera.

Routing IP

Jeśli masz kontrolę nad siecią, możesz kierować dodatkowe sieci do hosta Docker w celu użycia w kontenerach.

Następnie przypisz tę sieć do kontenerów i skonfiguruj hosta Docker do kierowania pakietów przez sieć dokerów.

Wspólny interfejs hosta

Ta --net hostopcja umożliwia udostępnienie interfejsu hosta w kontenerze, ale prawdopodobnie nie jest to dobra konfiguracja do uruchamiania wielu kontenerów na jednym hoście ze względu na wspólną naturę.

Matt
źródło