Jak skonfigurować połączenie między kontenerami platformy Docker, aby ponowne uruchomienie go nie przerywało?

80

Mam kilka kontenerów Docker działających jak:

  • Nginx
  • Aplikacja internetowa 1
  • Aplikacja internetowa 2
  • PostgreSQL

Ponieważ Nginx musi łączyć się z serwerami aplikacji internetowych w aplikacji internetowej 1 i 2, a aplikacje internetowe muszą komunikować się z PostgreSQL, mam takie linki:

  • Nginx --- link ---> Aplikacja internetowa 1
  • Nginx --- link ---> Aplikacja internetowa 2
  • Aplikacja internetowa 1 --- link ---> PostgreSQL
  • Aplikacja internetowa 2 --- link ---> PostgreSQL

Na początku działa to całkiem dobrze. Jednak kiedy tworzę nową wersję aplikacji internetowej 1 i aplikacji internetowej 2, muszę je wymienić. Ja tylko usuwam kontenery aplikacji internetowej, konfiguruję nowe kontenery i uruchamiam je.

W przypadku kontenerów aplikacji internetowych ich adresy IP na początku wyglądałyby następująco:

  • 172.17.0.2
  • 172.17.0.3

A po ich wymianie będą miały nowe adresy IP:

  • 172.17.0.5
  • 172.17.0.6

Teraz te ujawnione zmienne środowiskowe w kontenerze Nginx nadal wskazują na stare adresy IP. Tu pojawia się problem. Jak wymienić kontener bez zrywania połączenia między kontenerami? Ten sam problem będzie dotyczył również PostgreSQL. Jeśli chcę zaktualizować wersję obrazu PostgreSQL, z pewnością muszę ją usunąć i uruchomić nową, ale potem muszę przebudować cały wykres kontenera, więc nie jest to idealne rozwiązanie do rzeczywistej pracy serwera.

Fang-Pen Lin
źródło

Odpowiedzi:

53

Efekt --linkjest statyczny, więc nie będzie działał w Twoim scenariuszu (obecnie nie ma ponownego łączenia, chociaż możesz usunąć linki ).

W dockerize.it używaliśmy dwóch różnych podejść, aby rozwiązać ten problem, bez linków i ambasadorów (chociaż możesz też dodać ambasadorów).

1) Użyj dynamicznego DNS

Ogólną ideą jest określenie jednej nazwy dla bazy danych (lub dowolnej innej usługi) i zaktualizowanie krótkotrwałego serwera DNS o rzeczywisty adres IP podczas uruchamiania i zatrzymywania kontenerów.

Zaczęliśmy od SkyDock . Działa z dwoma kontenerami docker, serwerem DNS i monitorem, który aktualizuje go automatycznie. Później przeszliśmy do czegoś bardziej niestandardowego przy użyciu Consul (również używającej wersji dockerized: docker -consul ).

Ewolucją tego (którego nie próbowaliśmy) byłaby konfiguracja etcd lub podobnego i użycie jego niestandardowego API do nauki adresów IP i portów. Oprogramowanie powinno również obsługiwać dynamiczną rekonfigurację.

2) Użyj adresu IP docker bridge

Podczas ujawniania portów kontenerów możesz po prostu powiązać je z docker0mostem, który ma (lub może mieć) dobrze znany adres.

Podczas zastępowania kontenera nową wersją wystarczy, że nowy kontener będzie publikował ten sam port pod tym samym adresem IP.

Jest to prostsze, ale też bardziej ograniczone. Możesz mieć konflikty portów, jeśli uruchomisz podobne oprogramowanie (na przykład dwa kontenery nie mogą nasłuchiwać na porcie 3306 na docker0moście), itp., Więc obecnie naszą ulubioną jest opcja 1.

Abel Muiño
źródło
20

Linki dotyczą określonego kontenera, a nie są oparte na nazwie kontenera. Czyli w momencie usunięcia kontenera łącze jest rozłączane i nowy kontener (nawet o tej samej nazwie) nie zajmie automatycznie jego miejsca.

Nowa funkcja sieciowa umożliwia łączenie się z kontenerami według ich nazw, więc jeśli utworzysz nową sieć, każdy kontener podłączony do tej sieci może łączyć się z innymi kontenerami po ich nazwie. Przykład:

1) Utwórz nową sieć

$ docker network create <network-name>       

2) Podłącz kontenery do sieci

$ docker run --net=<network-name> ...

lub

$ docker network connect <network-name> <container-name>

3) Ping kontenera według nazwy

docker exec -ti <container-name-A> ping <container-name-B> 

64 bytes from c1 (172.18.0.4): icmp_seq=1 ttl=64 time=0.137 ms
64 bytes from c1 (172.18.0.4): icmp_seq=2 ttl=64 time=0.073 ms
64 bytes from c1 (172.18.0.4): icmp_seq=3 ttl=64 time=0.074 ms
64 bytes from c1 (172.18.0.4): icmp_seq=4 ttl=64 time=0.074 ms

Zobacz sekcję dokumentacji;

Uwaga: W przeciwieństwie do starszej linkswersji, nowa sieć nie będzie tworzyć zmiennych środowiskowych ani współdzielić zmiennych środowiskowych z innymi kontenerami.

Ta funkcja obecnie nie obsługuje aliasów

Hemerson Varela
źródło
Chciałbym zaznaczyć, że działa to tylko w wersji 1.9 lub nowszej. Niektóre dystrybucje nie zostały jeszcze wydane z najnowszą wersją.
John Giotta
Inną opcją jest użycie aliasu o zasięgu sieci zamiast nazwy kontenera (która musi być globalnie unikalna, nie zawsze jest fajna). Niemniej jednak odpowiedź jest absolutnie poprawna.
Ivan Anishchuk,
10

Możesz użyć kontenera ambasadora . Ale nie łącz kontenera ambasadora ze swoim klientem, ponieważ stwarza to ten sam problem, co powyżej. Zamiast tego użyj udostępnionego portu kontenera ambasadora na hoście platformy docker (zwykle 172.17.42.1). Przykład:

objętość postgresów:

$ docker run --name PGDATA -v /data/pgdata/data:/data -v /data/pgdata/log:/var/log/postgresql phusion/baseimage:0.9.10 true

postgres-kontener:

$ docker run -d --name postgres --volumes-from PGDATA -e USER=postgres -e PASS='postgres' paintedfox/postgresql

ambasador-kontener na postgres:

$ docker run -d --name pg_ambassador --link postgres:postgres -p 5432:5432 ctlc/ambassador

Teraz możesz uruchomić kontener klienta postgresql bez łączenia kontenera ambasadora i uzyskać dostęp do postgresql na hoście bramy (zazwyczaj 172.17.42.1):

$ docker run --rm -t -i paintedfox/postgresql /bin/bash
root@b94251eac8be:/# PGHOST=$(netstat -nr | grep '^0\.0\.0\.0 ' | awk '{print $2}')
root@b94251eac8be:/# echo $PGHOST
172.17.42.1
root@b94251eac8be:/#
root@b94251eac8be:/# psql -h $PGHOST --user postgres
Password for user postgres: 
psql (9.3.4)
SSL connection (cipher: DHE-RSA-AES256-SHA, bits: 256)
Type "help" for help.

postgres=#
postgres=# select 6*7 as answer;
 answer 
--------
     42
(1 row)

bpostgres=# 

Teraz możesz zrestartować kontener ambasadora bez konieczności restartowania klienta.

Swen Thümmler
źródło
czy "-p 5432: 5432" nie wystawi PostgreSQL na świat zewnętrzny?
Fang-Pen Lin
2
Tak, to będzie. Jeśli tego nie chcesz, możesz użyć opcji „-p 172.17.42.1:5432:5432”.
Swen Thümmler
1
tak przy okazji, dlaczego musisz utworzyć kontener „PGDATA” i połączyć go z kontenerem postgresql? Nie rozumiem, czemu po prostu nie utworzyć kontenera postgresql i nie zamapować jego wolumenu bezpośrednio na katalog hosta?
Fang-Pen Lin
Pojemnik PGDATA nie jest potrzebny, używam go tylko do oddzielenia problemów. Uruchamiając kontener posgres nie muszę pamiętać, jak mapowane są woluminy w kontenerze PGDATA. Dodałem to, ponieważ tak właśnie robię. W zasadzie to kwestia gustu - sam nie jestem jeszcze pewien, czy to dobry pomysł, czy nie ...
Swen Thümmler
Rzeczywiście najlepszą praktyką jest używanie kontenera danych, tak jak robi to Swen.
derFunk
2

Jeśli ktoś nadal jest ciekawy, musisz użyć wpisów hosta w pliku / etc / hosts dla każdego kontenera dockera i nie powinien polegać na zmiennych ENV, ponieważ nie są one aktualizowane automatycznie.

Dla każdego połączonego kontenera będzie wpis pliku hosta w formacie LINKEDCONTAINERNAME_PORT_PORTNUMBER_TCP itd.

Poniższy fragment pochodzi z dokumentów docker

Ważne uwagi dotyczące zmiennych środowiskowych platformy Docker

W przeciwieństwie do wpisów hostów w pliku / etc / hosts, adresy IP przechowywane w zmiennych środowiskowych nie są automatycznie aktualizowane po ponownym uruchomieniu kontenera źródłowego. Zalecamy używanie wpisów hosta w / etc / hosts do rozwiązywania adresów IP połączonych kontenerów.

Te zmienne środowiskowe są ustawiane tylko dla pierwszego procesu w kontenerze. Niektóre demony, takie jak sshd, będą je szorować podczas tworzenia powłok w celu połączenia.

frameworksnow
źródło
2

Jest to zawarte w eksperymentalnej kompilacji dockera 3 tygodnie temu wraz z wprowadzeniem usług: https://github.com/docker/docker/blob/master/experimental/networking.md

Powinieneś być w stanie uzyskać dynamiczne łącze w miejscu, uruchamiając kontener Dockera z --publish-service <name>argumentami. Ta nazwa będzie dostępna przez DNS. Jest to trwałe przy ponownym uruchomieniu kontenera (o ile oczywiście ponownie uruchomisz kontener z tą samą nazwą usługi)

Laurens Rietveld
źródło
Jak zainstalować tę wersję? github.com/docker/docker/releases/tag/v1.8.0-rc1
m59
1
Zobacz tę stronę, aby uzyskać więcej informacji: github.com/docker/docker/tree/master/experimental . Wersja krótka: uruchom, wget -qO- https://experimental.docker.com/ | shaby zainstalować wersję eksperymentalną
Laurens Rietveld
1
Ta odpowiedź była prawidłowa, ale jest teraz nieaktualna, ponieważ docker usunął publish-serviceopcję eksperymentalną . Teraz zamiast tego mają aliasy o zasięgu sieci. Zasadniczo to samo.
Ivan Anishchuk,
1

Aby rozwiązać ten problem, możesz użyć linków dokowanych z nazwami.

Najbardziej podstawowa konfiguracja polega na utworzeniu najpierw nazwanego kontenera bazy danych:

$ sudo docker run -d --name db training/postgres

następnie utwórz kontener WWW łączący się z bazą danych:

$ sudo docker run -d -P --name web --link db:db training/webapp python app.py

Dzięki temu nie musisz ręcznie łączyć kontenerów z ich adresami IP.

Pak
źródło
2
Hm ... wygląda na to, że docker w jakiś sposób wygeneruje dla ciebie linkowaną nazwę hosta, ale tak jak to robi, generuje nazwę w / etc / hosts, jest statyczna, kiedy ponownie uruchomię połączony kontener, IP się zmieni, ale / etc / hosty pozostają takie same, więc nie będzie działać.
Fang-Pen Lin
2
Od wersji 1.0 Docker jest bardziej agresywne przypisywanie adresów IP. Po ponownym uruchomieniu kontener ( dbw tym przypadku) otrzyma nowy adres IP. Twój drugi kontener (zrestartowany lub nie) zachowa wartości ENV od momentu jego uruchomienia i jest bezużyteczny.
Niemiecki DZ
1
fyi wygląda na to, że nadchodzi poprawka, / etc / hosts zostanie zaktualizowany po ponownym uruchomieniu połączonego kontenera: github.com/docker/docker/issues/6350
jamshid
1
Wydaje się, że ten problem został rozwiązany, a proponowana metoda działa dla mnie.
Jens Piegsa
1
To jest najbardziej poprawna odpowiedź. Jedynym problemem jest to, że link jest jednokierunkowy i dodaje zależność między kontenerami: nie możesz połączyć dwóch kontenerów i nie możesz zatrzymać połączonego kontenera, a następnie uruchomić go ponownie (z nowymi opcjami lub czymś w tym rodzaju). W każdym z tych przypadków użyj sieci ( net-aliaslub nazwy kontenera).
Ivan Anishchuk
1

dzięki podejściu OpenSVC można obejść ten problem:

  • użyj usługi z własnym adresem IP / nazwą dns ​​(tą, z którą będą się łączyć Twoi użytkownicy końcowi)
  • powiedz Dockerowi, aby ujawnił porty pod ten konkretny adres IP (opcja dockera "--ip")
  • skonfiguruj swoje aplikacje, aby łączyły się z adresem IP usługi

za każdym razem, gdy wymieniasz kontener, masz pewność, że połączy się on z właściwym adresem IP.

Samouczek tutaj => Docker Multi Containers z OpenSVC

nie przegap części „złożonej orkiestracji” na końcu tuto, która może pomóc w uruchamianiu / zatrzymywaniu kontenerów we właściwej kolejności (1 podzbiór postgresql + 1 podzbiór webapp + 1 podzbiór nginx)

główną wadą jest to, że wystawiasz aplikacje webowe i porty PostgreSQL na adres publiczny, aw rzeczywistości tylko port tcp nginx musi być ujawniony publicznie.

user3757286
źródło
1

Możesz także wypróbować metodę ambasadora polegającą na posiadaniu kontenera pośredniczącego tylko po to, aby zachować łącze w stanie nienaruszonym ... (zobacz https://docs.docker.com/articles/ambassador_pattern_linking/ ), aby uzyskać więcej informacji

Gekkie
źródło
Ambasador to fajny wzór, ale cierpi na ten sam problem: adres IP niekoniecznie będzie aktualizowany przy ponownym uruchomieniu. Są jednak świetne do połączeń między hostami. Cóż, może z nową wersją dockera, która też nie będzie potrzebna.
Ivan Anishchuk,
@IvanAnishchuk prawda, ale w tamtym czasie był to właściwy sposób postępowania ... (+2 lata temu;))
Gekkie
0

Możesz powiązać porty połączeń obrazów ze stałymi portami na hoście i skonfigurować usługi, aby ich używać.

Ma to również swoje wady, ale może zadziałać w twoim przypadku.

ivant
źródło
Wiązanie portów localhost ma rzeczywiście swoje wady. Nowa sieć Dockera sprawia, że ​​jest przestarzała.
Ivan Anishchuk
0

Inną alternatywą jest skorzystanie z --net container:$CONTAINER_IDopcji.

Krok 1: utwórz kontenery „sieciowe”

docker run --name db_net ubuntu:14.04 sleep infinity
docker run --name app1_net --link db_net:db ubuntu:14.04 sleep infinity
docker run --name app2_net --link db_net:db ubuntu:14.04 sleep infinity
docker run -p 80 -p 443 --name nginx_net --link app1_net:app1 --link app2_net:app2 ubuntu:14.04 sleep infinity

Krok 2: Wstrzyknij usługi do kontenerów „sieciowych”

docker run --name db --net container:db_net pgsql
docker run --name app1 --net container:app1_net app1
docker run --name app2 --net container:app1_net app2
docker run --name nginx --net container:app1_net nginx

Dopóki nie dotkniesz kontenerów „sieciowych”, adresy IP Twoich linków nie powinny ulec zmianie.

user1202136
źródło
Utworzona przez użytkownika sieć mostkowa o znaczącej nazwie jest prawdopodobnie lepszą opcją. Nie wymaga tworzenia kontenerów tylko po to, aby korzystać z ich sieci.
Ivan Anishchuk,
0

Alias ​​o zasięgu sieci jest tym, czego potrzebujesz w tym przypadku. Jest to dość nowa funkcja, za pomocą której można „opublikować” kontener udostępniający usługę dla całej sieci, w przeciwieństwie do aliasów odsyłaczy dostępnych tylko z jednego kontenera.

Nie dodaje żadnego rodzaju zależności między kontenerami - mogą się komunikować, dopóki oba są uruchomione, niezależnie od ponownych uruchomień, zamiany i kolejności uruchamiania. Uważam, że używa wewnętrznie DNS zamiast / etc / hosts

Użyj go w ten sposób: docker run --net=some_user_definied_nw --net-alias postgres ...i możesz połączyć się z nim za pomocą tego aliasu z dowolnego kontenera w tej samej sieci.

Nie działa w domyślnej sieci, niestety musisz ją utworzyć, docker network create <network>a następnie używać z --net=<network>każdym kontenerem ( kompozycja też ją obsługuje ).

Oprócz tego, że kontener jest wyłączony, a tym samym nieosiągalny przez alias, wiele kontenerów może również współdzielić alias, w którym to przypadku nie ma gwarancji, że zostanie on rozwiązany na właściwy. Ale w niektórych przypadkach może to pomóc w bezproblemowej aktualizacji.

Na razie nie jest to dobrze udokumentowane, trudno to rozgryźć, czytając tylko stronę podręcznika.

Ivan Anishchuk
źródło