Как настроить связь между контейнерами 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, мне, безусловно, нужно удалить ее и запустить новую, но затем мне нужно перестроить весь контейнерный граф, это не очень хорошая идея для реального работа сервера жизни.
11 ответов
Эффект --link
является статическим, поэтому он не будет работать для вашего сценария (в настоящее время нет повторной ссылки, хотя вы можете удалить ссылки).
Мы использовали два разных подхода в dockerize.it, чтобы решить эту проблему, без ссылок или послов (хотя вы также можете добавить послов).
1) Используйте динамический DNS
Общая идея заключается в том, что вы указываете одно имя для своей базы данных (или любой другой службы) и обновляете недолговечный DNS-сервер фактическим IP-адресом при запуске и остановке контейнеров.
Мы начали с SkyDock. Он работает с двумя док-контейнерами, DNS-сервером и монитором, который автоматически обновляет его. Позже мы перешли к чему-то более нестандартному, используя Consul (также используя докеризированную версию: docker-consul).
Эволюция этого (который мы не пробовали) состояла бы в настройке etcd или подобного и использовании его пользовательского API для изучения IP-адресов и портов. Программное обеспечение также должно поддерживать динамическую реконфигурацию.
2) Используйте док-мост ip
Открывая порты контейнера, вы можете просто привязать их к docker0
мост, который имеет (или может иметь) хорошо известный адрес.
При замене контейнера новой версией просто заставьте новый контейнер публиковать тот же порт на том же IP-адресе.
Это проще, но и более ограничено. У вас могут возникнуть конфликты портов, если вы запускаете аналогичное программное обеспечение (например, два контейнера не могут прослушивать порт 3306 на docker0
мост), и так далее... так что наш текущий любимый вариант 1.
Ссылки предназначены для конкретного контейнера, а не на основе имени контейнера. Поэтому, когда вы удаляете контейнер, ссылка отключается, и новый контейнер (даже с тем же именем) не будет автоматически занимать свое место.
Новая сетевая функция позволяет вам подключаться к контейнерам по их имени, поэтому, если вы создаете новую сеть, любой контейнер, подключенный к этой сети, может обращаться к другим контейнерам по их имени. Пример:
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
новая сеть не будет создавать переменные среды и не делить переменные среды с другими контейнерами.
Эта функция в настоящее время не поддерживает псевдонимы
Вы можете использовать посол контейнер. Но не связывайте контейнер посла с вашим клиентом, так как это создает ту же проблему, что и выше. Вместо этого используйте открытый порт контейнера-посла на хосте докера (обычно 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=#
Теперь вы можете перезапустить посол без необходимости перезапуска клиента.
Если кому-то все еще любопытно, вы должны использовать записи хоста в файле /etc/hosts каждого контейнера докера и не должны зависеть от переменных ENV, поскольку они не обновляются автоматически.
Для каждого связанного контейнера будет запись в файле хоста в формате LINKEDCONTAINERNAME_PORT_PORTNUMBER_TCP и т. Д.
Следующее от докеров документации
Важные замечания по переменным среды Docker
В отличие от записей хоста в файле /etc/hosts, IP-адреса, хранящиеся в переменных среды, не обновляются автоматически, если исходный контейнер перезапускается. Мы рекомендуем использовать записи хоста в /etc/hosts для разрешения IP-адреса связанных контейнеров.
Эти переменные среды устанавливаются только для первого процесса в контейнере. Некоторые демоны, такие как sshd, будут очищать их при создании оболочек для соединения.
Это включено в экспериментальную сборку докера 3 недели назад, с введением сервисов: https://github.com/docker/docker/blob/master/experimental/networking.md
Вы должны иметь возможность получить динамическую ссылку на месте, запустив Docker-контейнер с --publish-service <name>
аргументы. Это имя будет доступно через DNS. Это сохраняется при перезапуске контейнера (если вы перезапускаете контейнер с тем же именем службы, которое, конечно,)
Вы также можете попробовать метод после использования промежуточного контейнера только для того, чтобы сохранить ссылку нетронутой... (см. https://docs.docker.com/articles/ambassador_pattern_linking/) для получения дополнительной информации.
Вы можете использовать dockerlink с именами для решения этой проблемы.
Наиболее простой настройкой было бы сначала создать именованный контейнер базы данных:
$ sudo docker run -d --name db training/postgres
затем создайте веб-контейнер, соединяющийся с БД:
$ sudo docker run -d -P --name web --link db:db training/webapp python app.py
При этом вам не нужно вручную подключать контейнеры с их IP-адресами.
С подходом OpenSVC вы можете обойти:
- используйте сервис со своим собственным IP-адресом / DNS-именем (тот, к которому ваши конечные пользователи будут подключаться)
- сказать докеру выставить порты на этот конкретный IP-адрес (опция докера "--ip")
- настроить приложения для подключения к IP-адресу службы
каждый раз, когда вы заменяете контейнер, вы уверены, что он подключится к правильному IP-адресу.
Учебник здесь => Docker Multi Containers с OpenSVC
не пропустите часть "сложная оркестровка" в конце tuto, которая поможет вам запускать / останавливать контейнеры в правильном порядке (1 подмножество postgresql + 1 подмножество webapp + 1 подмножество nginx)
главный недостаток заключается в том, что вы выставляете порты webapp и PostgreSQL на публичный адрес, и на самом деле публично должен быть открыт только порт nginx tcp.
В этом случае вам нужен сетевой псевдоним. Это довольно новая функция, которую можно использовать для "публикации" контейнера, предоставляющего сервис для всей сети, в отличие от псевдонимов ссылок, доступных только из одного контейнера.
Он не добавляет никакой зависимости между контейнерами - они могут взаимодействовать, пока оба работают, независимо от перезапусков и порядка замены и запуска. Я полагаю, что он использует DNS для внутреннего использования вместо /etc/hosts
Используйте это так: docker run --net=some_user_definied_nw --net-alias postgres ...
и вы можете подключиться к нему, используя этот псевдоним из любого контейнера в той же сети.
Не работает в сети по умолчанию, к сожалению, вы должны создать docker network create <network>
а затем использовать его с --net=<network>
для каждого контейнера (также поддерживает compose).
В дополнение к тому, что контейнер не работает и, следовательно, недоступен псевдонимом, несколько контейнеров также могут иметь общий псевдоним, и в этом случае не гарантируется, что он будет преобразован в правильный. Но в некоторых случаях это может помочь с плавным обновлением, вероятно.
Это все еще не очень хорошо документировано, трудно понять, просто прочитав страницу руководства.
Вы можете привязать порты подключения ваших образов к фиксированным портам на хосте и настроить службы для их использования.
Это также имеет свои недостатки, но это может работать в вашем случае.
Другой альтернативой является использование --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-адреса ваших ссылок не должны меняться.