Запустить приложение в контейнере это, конечно, интересно, но еще интереснее запустить сразу несколько и разрешить им между собой взаимодействовать. Допустим, вдоволь наигравшись с микросервисами, я решил разделить своё настоящее вэб-приложение на:
- контейнер со статическим контентом и вэб-сервером и
- контейнер с данными и RESTful сервисом.
Первый контейнер открывает 80й порт, самостоятельно раздаёт html/css/js и обращается ко второму контейнеру, когда приходит запрос на данные.
Схема простая, кроме одного «но». Как первому контейнеру найти и достучаться до второго?
Сеть в Docker.
Кроме контейнеров и образов Docker оперирует еще и собственными сетями, которых по-умолчанию он устанавливает аж три ( docker network ls ):
- bridge
- host
- none
Контейнеры подключаются к bridge по-умолчанию, а тот уже раздаёт IP адреса из своего диапазона, и через сетевые мосты перенаправляет траффик в большой интернет. Самое приятное: если пытливый программистский разум не вмешивался в настройки Докера, то все контейнеры внутри своей сети могут свободно видеть друг друга и общаться между собой, а снаружи к ним можно достучаться только привязав контейнерные порты к портам хоста (например, -p80:80 ).
Это настолько круто, что я повторю последнее предложение примером: можно запустить nginx контейнер и не открывать его 80й порт хосту — другие контейнеры из его сети всё еще смогут с ним общаться.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
docker run -d nginx #6e47c39f797a71429971e5c2df59305dd5bd2e47bd901e2f404de18a53aefa53 docker inspect --format '{{ .NetworkSettings.IPAddress }}' 6e4 #172.17.0.2 docker run -ti busybox #/ # wget -qO- 172.17.0.2 #<!DOCTYPE html> #<html> #<head> #<title>Welcome to nginx!</title> #<style> #... |
То есть, зная IP адрес контейнера, общаться с ним — не проблема. А вот знать адрес контейнера — проблема. То, что сегодня nginx висит на 172.17.0.2 совсем не значит, что здесь же он будет и завтра. Поменялся порядок запуска контейнера, перенесли его в другую сеть, поменяли настройки Docker-сервера — и айпишка поменялась. Нужно что-то более постоянное.
Как пережить динамическую айпишку.
Есть аж три способа:
- использовать --link
- использовать имя контейнера и свою сеть
- использовать сетевой псевдоним
—link
Link уже сегодня считается устаревшей фичей, но при этом всё еще работает, так что иногда — почему бы и нет.
У всех контейнеров есть имя — динамически сгенерированное, либо явно заданное. Я запускал nginx контейнер без --name параметра, поэтому Docker обозвал его вместо меня — nauseous_aryabhata. Если запустить второй контейнер с параметром --link=nauseous_aryabhata , то он получит целую кучу переменных среды и запись в /etc/hosts впридачу — всё для того, чтобы найти связанный контейнер:
1 2 3 4 5 6 7 |
docker run -ti --link=nauseous_aryabhata busybox env | grep tcp:// #NAUSEOUS_ARYABHATA_PORT=tcp://172.17.0.2:80 #NAUSEOUS_ARYABHATA_PORT_80_TCP=tcp://172.17.0.2:80 #NAUSEOUS_ARYABHATA_PORT_443_TCP=tcp://172.17.0.2:443 cat /etc/hosts | grep nauseous_aryabhata #172.17.0.2 nauseous_aryabhata 6e47c39f797a |
Теперь, если мне нужно отправить запрос ко второму контейнеру, можно просто воспользоваться его именем.
1 2 3 4 5 6 7 |
wget -qO- http://nauseous_aryabhata #<!DOCTYPE html> #<html> #<head> #<title>Welcome to nginx!</title> #<style> #... |
Использовать имя контейнера и создать свою сеть.
Что мне не нравится в приятных фичах, так это слово legacy, поэтому --link всё-таки хорош больше с эстетической точки зрения. Есть другой способ: если не использовать bridge-сеть по умолчанию, в создать свою (user defined network), то Docker добавит к ней маленький невидимый DNS сервис, через который можно общаться внутри своей сети со всеми контейнерами по имени. И тут же быстрый пример:
Шаг первый. Создаём свою сеть:
1 |
docker network create mynetwork |
Шаг второй. Запускаем в этой сети nginx. В этот раз с явно указанным именем web:
1 |
docker run -d --net=mynetwork --name=web nginx |
Шаг третий. Запустим в этой же сети еще один контейнер, и проверим, видит ли он web:
1 2 3 4 |
docker run -ti --net=mynetwork busybox wget -qO- web #<!DOCTYPE html> #... |
Та-дааам!
Использовать сетевой псевдоним.
Этот способ очень похож на второй, только вместо имени контейнеру можно задать сетевой псевдоним:
1 2 3 |
docker run -d \ --net=mynetwork \ --network-alias=webserver nginx |
Теперь все запросы к http://webserver встроенный DNS так же перенаправит к nginx-контейнеру. Можно также задавать одновременно и имя, и псевдоним, и использовать оба.
Мораль.
В общем, настроить взаимодействие контейнеров между собой по TCP/HTTP — не проблема. Причём, я взял только самые базовые инструменты. А ведь есть еще разные типы сетей, можно создавать собственные сетевые мосты и даже overlay networks, которые объединяют сети между несколькими машинами в одну так, что контейнерам кажется, будто они все находятся на одном хосте. Но это — повод для других статей.
Магия однако. 🙂
Ну дык.
А если я хочу по IP подключатся с одного контейнера на 2-й вместо имени. и чтобы IP был статический для nginx контейнера. Это возможно?
По IP должно тоже работать, потому что обращение по имени, в конечном итоге, это запрос на DNS сервер за реальным IP адресом, и потом уже идёт общение по нему.
Контейнеру можно привязать статическую айпишку через параметр
--ip
. Я так никогда не делал, но интернеты говорят, что сработает — https://stackoverflow.com/questions/27937185/assign-static-ip-to-docker-container