Як налаштувати зв’язок між контейнерами Docker, щоб перезапуск не порушив?


80

У мене є кілька контейнерів Docker, які працюють, як:

  • Nginx
  • Веб-програма 1
  • Веб-програма 2
  • PostgreSQL

Оскільки Nginx повинен підключатися до серверів веб-додатків у веб-програмах 1 і 2, а веб-додаткам потрібно спілкуватися з PostgreSQL, у мене є такі зв’язки:

  • Nginx --- посилання ---> Веб-програма 1
  • Nginx --- посилання ---> Веб-програма 2
  • Веб-програма 1 --- посилання ---> PostgreSQL
  • Веб-програма 2 --- посилання ---> PostgreSQL

Спочатку це працює досить добре. Однак, коли я розробляю нову версію веб-програми 1 та веб-програми 2, мені потрібно їх замінити. Що я роблю, це видаляю контейнери веб-програми, встановлюю нові контейнери та запускаю їх.

Для контейнерів веб-програм спочатку їхні IP-адреси мали б приблизно такий вигляд:

  • 172.17.0.2
  • 172.17.0.3

І після їх заміни вони матимуть нові IP-адреси:

  • 172.17.0.5
  • 172.17.0.6

Тепер ці відкриті змінні середовища в контейнері Nginx все ще вказують на старі IP-адреси. Ось проблема. Як замінити контейнер, не порушуючи зв’язок між контейнерами? Те саме питання трапиться і з PostgreSQL. Якщо я хочу оновити версію образу PostgreSQL, мені, звичайно, потрібно видалити її та запустити нову, але тоді мені потрібно відновити весь графік контейнера, тому це не ідеально для реальної роботи сервера.

Відповіді:


53

Ефект --linkстатичний, тому він не буде працювати у вашому сценарії (на даний момент не існує повторного зв’язування, хоча ви можете видалити посилання ).

Ми використовували два різні підходи на dockerize.it для вирішення цієї проблеми без посилань та послів (хоча ви могли б додати послів теж).

1) Використовуйте динамічний DNS

Загальна ідея полягає в тому, що ви вказуєте одне ім’я для своєї бази даних (або будь-якої іншої служби) та оновлюєте короткочасний DNS-сервер із фактичним IP-адресою під час запуску та зупинки контейнерів.

Ми почали з SkyDock . Він працює з двома контейнерами докерів, DNS-сервером та монітором, який підтримує його автоматичне оновлення. Пізніше ми перейшли до чогось більш спеціального за допомогою Consul (також використовуючи докерізовану версію: docker -consul ).

Еволюцією цього (чого ми ще не пробували) було б встановлення etcd або подібного та використання його користувацького API для вивчення IP та портів. Програмне забезпечення також повинно підтримувати динамічну реконфігурацію.

2) Використовуйте докер-міст ip

Виставляючи контейнерні порти, ви можете просто прив'язати їх до docker0мосту, який має (або може мати) добре відому адресу.

Замінюючи контейнер новою версією, просто змусіть новий контейнер публікувати той самий порт на тому ж IP.

Це простіше, але також і обмежено. У вас можуть виникнути конфлікти портів, якщо ви запускаєте подібне програмне забезпечення (наприклад, два контейнери не можуть прослуховувати порт 3306 на docker0мосту) і т. Д., Тому наш поточний фаворит - варіант 1.


20

Посилання призначені для конкретного контейнера, а не на основі назви контейнера. Отож, як тільки ви видалите контейнер, посилання від’єднується, і новий контейнер (навіть з тим самим ім’ям) автоматично не займе своє місце.

Нова мережева функція дозволяє підключатися до контейнерів за їх іменем, тому, якщо ви створюєте нову мережу, будь-який контейнер, підключений до цієї мережі, може дістатися до інших контейнерів за їх іменем. Приклад:

1) Створити нову мережу

$ docker network create <network-name>       

2) Підключіть контейнери до мережі

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

або

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

3) Пінг-контейнер за назвою

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

Дивіться цей розділ документації;

Примітка: На відміну від застарілої версії, linksнова мережа не буде створювати змінні середовища, а також не ділити змінні середовища з іншими контейнерами.

На даний момент ця функція не підтримує псевдоніми


Я хотів би зазначити, що це працює лише у версії 1.9 або пізнішої. Деякі дистрибутиви ще не випущені з останніми версіями.
Джон Джотта,

Інший варіант - використовувати псевдонім, що охоплює мережу, замість імені контейнера (яке має бути глобально унікальним, не завжди приємно). Але відповідь, проте, абсолютно правильна.
Іван Аніщук

10

Ви можете використовувати контейнер для послів . Але не пов'язуйте контейнер посольства з вашим клієнтом, оскільки це створює ту ж проблему, що і вище. Натомість використовуйте відкритий порт контейнера для послів на хості докера (як правило, 172.17.42.1). Приклад:

обсяг postgres:

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

postgres-контейнер:

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

посол-контейнер для postgres:

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

Тепер ви можете запустити контейнер клієнта postgresql, не пов’язуючи контейнер посла і отримати доступ до postgresql на хості шлюзу (зазвичай 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=# 

Тепер ви можете перезапустити контейнер посла без необхідності перезапускати клієнта.


чи "-p 5432: 5432" не піддасть PostgreSQL зовнішньому світу?
Fang-Pen Lin

2
Так це буде. Якщо ви цього не хочете, ви можете використовувати "-p 172.17.42.1:5432:5432".
Swen Thümmler

1
до речі, навіщо вам потрібно створювати цей контейнер "PGDATA" і пов'язувати його з контейнером postgresql? Я не розумію, чому б просто не створити контейнер postgresql і не зіставити його обсяг безпосередньо в хост-каталог?
Fang-Pen Lin

Контейнер PGDATA не потрібен, я використовую його лише для відокремлення проблем. Під час запуску контейнера posgres мені не потрібно згадувати, як відображаються томи в контейнері PGDATA. Я додав це, тому що саме так я зараз роблю. Це в основному справа смаку - я сам ще не впевнений, хороша це ідея чи ні ...
Свен Тюммлер

Це справді найкраща практика - використовувати контейнер обсягу даних, як це робить Свен.
derFunk

2

Якщо комусь все ще цікаво, вам доведеться використовувати записи хоста у файлі / etc / hosts кожного контейнера докера, і вони не повинні залежати від змінних ENV, оскільки вони не оновлюються автоматично.

Буде записаний файл хосту для кожного зв'язаного контейнера у форматі LINKEDCONTAINERNAME_PORT_PORTNUMBER_TCP тощо.

Далі йдеться з docker docs

Важливі примітки щодо змінних середовища Docker

На відміну від записів хоста у файлі / etc / hosts, IP-адреси, що зберігаються у змінних середовища, не оновлюються автоматично, якщо перезавантажити вихідний контейнер. Ми рекомендуємо використовувати записи хосту в / etc / hosts для вирішення IP-адреси пов’язаних контейнерів.

Ці змінні середовища встановлюються лише для першого процесу в контейнері. Деякі демони, такі як sshd, будуть чистити їх, коли нерестяться оболонки для підключення.


2

Це включено в експериментальну збірку docker 3 тижні тому, із введенням сервісів: https://github.com/docker/docker/blob/master/experimental/networking.md

Ви зможете отримати динамічне посилання на місці, запустивши контейнер докера з --publish-service <name>аргументами. Ця назва буде доступна через DNS. Це зберігається при перезапуску контейнера (якщо ви перезапускаєте контейнер з тим самим іменем служби, що, звичайно)


Як встановити цю версію? github.com/docker/docker/releases/tag/v1.8.0-rc1
m59,

1
Для отримання додаткової інформації див. Цю сторінку: github.com/docker/docker/tree/master/experimental . Коротка версія: запустіть, wget -qO- https://experimental.docker.com/ | shщоб встановити експериментальну версію
Лоренс Рітвельд

1
Ця відповідь була дійсною, але зараз застаріла, оскільки експериментальна publish-serviceопція видалена докером . Тепер у них замість цього є псевдоніми, охоплені мережею. По суті те саме, щоправда.
Іван Аніщук

1

Ви можете використовувати docker-посилання з іменами, щоб вирішити цю проблему.

Найбільш базовим налаштуванням було б спочатку створити іменований контейнер бази даних:

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

потім створіть веб-контейнер, що підключається до db:

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

Завдяки цьому вам не потрібно вручну підключати контейнери з їх IP-адресами.


2
Хм ... схоже, докер якось згенерує для вас зв’язане ім’я хосту, але так, як це робить, воно генерує ім’я в / etc / hosts, воно статичне, коли я перезапускаю пов’язаний контейнер, IP змінюється, але / etc / хости залишаються незмінними, тому це не буде працювати.
Fang-Pen Lin

2
Оскільки Docker версії 1.0 є більш агресивним призначенням IP-адрес. Коли ви перезапустите контейнер ( dbу цьому випадку), він отримає нову IP-адресу. Інший контейнер (перезапущений чи ні) зберігатиме значення ENV з моменту запуску, і він марний.
GermanDZ

1
fyi схоже на виправлення, / etc / hosts буде оновлено при перезапуску пов'язаного контейнера: github.com/docker/docker/issues/6350
jamshid

1
Здається, ця проблема виправлена, і запропонований метод працює для мене.
Jens Piegsa

1
Це найбільш правильна відповідь тут. Єдина проблема полягає в односпрямованості і додає залежність між контейнерами: ви не можете зшити два контейнери і не можете зупинити зв’язаний контейнер, а потім запустити його заново (з новими опціями або щось інше). У будь-якому з цих випадків використовуйте мережі (і будь-яку, net-aliasабо назву контейнера).
Іван Аніщук

1

з підходом OpenSVC ви можете обійти:

  • скористатися послугою із власною ip-адресою / іменем dns (до якої підключатимуться ваші кінцеві користувачі)
  • сказати докеру виставляти порти за цією конкретною ip-адресою (опція докера "--ip")
  • налаштуйте свої програми для підключення до ip-адреси служби

кожного разу, коли ви замінюєте контейнер, ви впевнені, що він підключиться до правильної ip-адреси.

Підручник тут => Docker Multi Containers with OpenSVC

не пропустіть частину "складної оркестрації" в кінці програми, яка допоможе вам запускати / зупиняти контейнери у правильному порядку (1 підмножина postgresql + 1 підмножина webapp + 1 підмножина nginx)

головним недоліком є ​​те, що ви виставляєте порти webapp та PostgreSQL на загальнодоступні адреси, і насправді лише публічний доступ має бути виставлений лише на порт tgin nginx.


1

Ви також можете спробувати метод посла, щоб мати посередницький контейнер лише для збереження цілісності посилання ... (див. Https://docs.docker.com/articles/ambassy_pattern_linking/ ) для отримання додаткової інформації


Посол - приємний зразок, але він страждає від тієї ж проблеми: ip-адреса не обов'язково оновлюватиметься при перезапуску. Однак вони чудово підходять для підключення між хостами. Ну, можливо, з новим випуском докера, який теж не знадобиться.
Іван Аніщук

@IvanAnishchuk правда, але на той час, коли був зроблений коментар, це був шлях ... (+2 роки тому;))
Геккі,

0

Ви можете прив’язати порти підключення ваших зображень до фіксованих портів на хості та налаштувати служби, щоб вони використовували їх замість цього.

Це також має свої недоліки, але це може спрацювати у вашому випадку.


Прив’язка портів localhost справді має свої недоліки. Нова мережа докерів робить її застарілою.
Іван Аніщук

0

Інша альтернатива - використання --net container:$CONTAINER_IDопції.

Крок 1: Створіть "мережеві" контейнери

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

Крок 2: Введіть служби в "мережеві" контейнери

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

Поки ви не торкаєтесь «мережевих» контейнерів, IP-адреси ваших посилань не повинні змінюватися.


Створена користувачем мостова мережа зі значущим ім'ям, мабуть, кращий варіант. Не вимагає створення контейнерів лише для використання їхніх мереж.
Іван Аніщук

0

У цьому випадку вам потрібен мережевий псевдонім . Це досить нова функція, яку можна використовувати для "публікації" контейнера, що надає послугу для всієї мережі, на відміну від псевдонімів посилань, доступних лише з одного контейнера.

Це не додає ніякої залежності між контейнерами - вони можуть спілкуватися, поки працюють обидва, незалежно від перезапусків та порядку заміни та запуску. Я вважаю, що він використовує DNS внутрішньо замість / etc / hosts

Використовуйте його так: docker run --net=some_user_definied_nw --net-alias postgres ...і ви можете підключитися до нього, використовуючи цей псевдонім із будь-якого контейнера в тій же мережі.

Не працює в мережі за замовчуванням, на жаль, вам доведеться створити docker network create <network>та використати його --net=<network>для кожного контейнера ( compose це також підтримує ).

Окрім того, що контейнер не працює і, отже, недоступний за допомогою псевдоніма, кілька контейнерів можуть також використовувати спільний псевдонім, і в цьому випадку не гарантується, що він буде вирішено правильним. Але в деяких випадках це може допомогти з безшовним оновленням, напевно.

На сьогодні це все не дуже добре задокументовано, важко зрозуміти, просто прочитавши сторінку довідки.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.