Docker связать контейнеры

Обновлено: 18.04.2024

Warning

The --link flag is a legacy feature of Docker. It may eventually be removed. Unless you absolutely need to continue using it, we recommend that you use user-defined networks to facilitate communication between two containers instead of using --link . One feature that user-defined networks do not support that you can do with --link is sharing environment variables between containers. However, you can use other mechanisms such as volumes to share environment variables between containers in a more controlled way.

See Differences between user-defined bridges and the default bridge for some alternatives to using --link .

The information in this section explains legacy container links within the Docker default bridge network which is created automatically when you install Docker.

Before the Docker networks feature, you could use the Docker link feature to allow containers to discover each other and securely transfer information about one container to another container. With the introduction of the Docker networks feature, you can still create links but they behave differently between default bridge network and user defined networks.

This section briefly discusses connecting via a network port and then goes into detail on container linking in default bridge network.

Connect using network port mapping

Let’s say you used this command to run a simple Python Flask application:

Note: Containers have an internal network and an IP address. Docker can have a variety of network configurations. You can see more information on Docker networking here.

When that container was created, the -P flag was used to automatically map any network port inside it to a random high port within an ephemeral port range on your Docker host. Next, when docker ps was run, you saw that port 5000 in the container was bound to port 49155 on the host.

You also saw how you can bind a container’s ports to a specific port using the -p flag. Here port 80 of the host is mapped to port 5000 of the container:

And you saw why this isn’t such a great idea because it constrains you to only one container on that specific port.

Instead, you may specify a range of host ports to bind a container port to that is different than the default ephemeral port range:

This would bind port 5000 in the container to a randomly available port between 8000 and 9000 on the host.

There are also a few other ways you can configure the -p flag. By default the -p flag binds the specified port to all interfaces on the host machine. But you can also specify a binding to a specific interface, for example only to the localhost .

This would bind port 5000 inside the container to port 80 on the localhost or 127.0.0.1 interface on the host machine.

Or, to bind port 5000 of the container to a dynamic port but only on the localhost , you could use:

You can also bind UDP and SCTP (typically used by telecom protocols such as SIGTRAN, Diameter, and S1AP/X2AP) ports by adding a trailing /udp or /sctp . For example:

You also learned about the useful docker port shortcut which showed us the current port bindings. This is also useful for showing you specific port configurations. For example, if you’ve bound the container port to the localhost on the host machine, then the docker port output reflects that.

Note: The -p flag can be used multiple times to configure multiple ports.

Connect with the linking system

Note: This section covers the legacy link feature in the default bridge network. Refer to differences between user-defined bridges and the default bridge for more information on links in user-defined networks.

Network port mappings are not the only way Docker containers can connect to one another. Docker also has a linking system that allows you to link multiple containers together and send connection information from one to another. When containers are linked, information about a source container can be sent to a recipient container. This allows the recipient to see selected data describing aspects of the source container.

The importance of naming

To establish links, Docker relies on the names of your containers. You’ve already seen that each container you create has an automatically created name; indeed you’ve become familiar with our old friend nostalgic_morse during this guide. You can also name containers yourself. This naming provides two useful functions:

It can be useful to name containers that do specific functions in a way that makes it easier for you to remember them, for example naming a container containing a web application web .

It provides Docker with a reference point that allows it to refer to other containers, for example, you can specify to link the container web to container db .

You can name your container by using the --name flag, for example:

This launches a new container and uses the --name flag to name the container web . You can see the container’s name using the docker ps command.

You can also use docker inspect to return the container’s name.

Note: Container names must be unique. That means you can only call one container web . If you want to re-use a container name you must delete the old container (with docker container rm ) before you can create a new container with the same name. As an alternative you can use the --rm flag with the docker run command. This deletes the container immediately after it is stopped.

Communication across links

Links allow containers to discover each other and securely transfer information about one container to another container. When you set up a link, you create a conduit between a source container and a recipient container. The recipient can then access select data about the source. To create a link, you use the --link flag. First, create a new container, this time one containing a database.

This creates a new container called db from the training/postgres image, which contains a PostgreSQL database.

Now, you need to delete the web container you created previously so you can replace it with a linked one:

Now, create a new web container and link it with your db container.

This links the new web container with the db container you created earlier. The --link flag takes the form:

Where name is the name of the container we’re linking to and alias is an alias for the link name. That alias is used shortly. The --link flag also takes the form:

In this case the alias matches the name. You could write the previous example as:

Next, inspect your linked containers with docker inspect :

You can see that the web container is now linked to the db container web/db . Which allows it to access information about the db container.

So what does linking the containers actually do? You’ve learned that a link allows a source container to provide information about itself to a recipient container. In our example, the recipient, web , can access information about the source db . To do this, Docker creates a secure tunnel between the containers that doesn’t need to expose any ports externally on the container; when we started the db container we did not use either the -P or -p flags. That’s a big benefit of linking: we don’t need to expose the source container, here the PostgreSQL database, to the network.

Docker exposes connectivity information for the source container to the recipient container in two ways:

  • Environment variables,
  • Updating the /etc/hosts file.

Environment variables

Docker creates several environment variables when you link containers. Docker automatically creates environment variables in the target container based on the --link parameters. It also exposes all environment variables originating from Docker from the source container. These include variables from:

  • the ENV commands in the source container’s Dockerfile
  • the -e , --env , and --env-file options on the docker run command when the source container is started

These environment variables enable programmatic discovery from within the target container of information related to the source container.

Warning: It is important to understand that all environment variables originating from Docker within a container are made available to any container that links to it. This could have serious security implications if sensitive data is stored in them.

Docker sets an _NAME environment variable for each target container listed in the --link parameter. For example, if a new container called web is linked to a database container called db via --link db:webdb , then Docker creates a WEBDB_NAME=/web/webdb variable in the web container.

Docker also defines a set of environment variables for each port exposed by the source container. Each variable has a unique prefix in the form:

  • the alias specified in the --link parameter (for example, webdb )
  • the number exposed
  • a which is either TCP or UDP

Docker uses this prefix format to define three distinct environment variables:

  • The prefix_ADDR variable contains the IP Address from the URL, for example WEBDB_PORT_5432_TCP_ADDR=172.17.0.82 .
  • The prefix_PORT variable contains just the port number from the URL for example WEBDB_PORT_5432_TCP_PORT=5432 .
  • The prefix_PROTO variable contains just the protocol from the URL for example WEBDB_PORT_5432_TCP_PROTO=tcp .

If the container exposes multiple ports, an environment variable set is defined for each one. This means, for example, if a container exposes 4 ports that Docker creates 12 environment variables, 3 for each port.

Additionally, Docker creates an environment variable called _PORT . This variable contains the URL of the source container’s first exposed port. The ‘first’ port is defined as the exposed port with the lowest number. For example, consider the WEBDB_PORT=tcp://172.17.0.82:5432 variable. If that port is used for both tcp and udp, then the tcp one is specified.

Finally, Docker also exposes each Docker originated environment variable from the source container as an environment variable in the target. For each variable Docker creates an _ENV_ variable in the target container. The variable’s value is set to the value Docker used when it started the source container.

Returning back to our database example, you can run the env command to list the specified container’s environment variables.

You can see that Docker has created a series of environment variables with useful information about the source db container. Each variable is prefixed with DB_ , which is populated from the alias you specified above. If the alias were db1 , the variables would be prefixed with DB1_ . You can use these environment variables to configure your applications to connect to the database on the db container. The connection is secure and private; only the linked web container can communicate with the db container.

Important notes on Docker environment variables

Unlike host entries in the /etc/hosts file, IP addresses stored in the environment variables are not automatically updated if the source container is restarted. We recommend using the host entries in /etc/hosts to resolve the IP address of linked containers.

These environment variables are only set for the first process in the container. Some daemons, such as sshd , scrub them when spawning shells for connection.

Updating the /etc/hosts file

In addition to the environment variables, Docker adds a host entry for the source container to the /etc/hosts file. Here’s an entry for the web container:

You can see two relevant host entries. The first is an entry for the web container that uses the Container ID as a host name. The second entry uses the link alias to reference the IP address of the db container. In addition to the alias you provide, the linked container’s name, if unique from the alias provided to the --link parameter, and the linked container’s hostname are also added to /etc/hosts for the linked container’s IP address. You can ping that host via any of these entries:

Note: In the example, you had to install ping because it was not included in the container initially.

Here, you used the ping command to ping the db container using its host entry, which resolves to 172.17.0.5 . You can use this host entry to configure an application to make use of your db container.

Note: You can link multiple recipient containers to a single source. For example, you could have multiple (differently named) web containers attached to your db container.

If you restart the source container, the /etc/hosts files on the linked containers are automatically updated with the source container’s new IP address, allowing linked communication to continue.

Docker, если кто умудрился об этом ещё не слышать — фреймворк с открытым исходным кодом для управления контейнерной виртуализацией. Он быстрый, удобный, продуманный и модный. По сути он меняет правила игры в благородном деле управления конфигурацией серверов, сборки приложений, выполнения серверного кода, управления зависимостями и много ещё где.

Архитектура, которую поощряет Docker — это изолированные контейнеры, каждый из которых выполняет одну команду. Эти контейнеры должны знать только как друг друга найти — другими словами, о контейнере нужно знать его fqdn и порт, или ip и порт, то есть, не более, чем о любой внешней службе.

Рекомендованный способ сообщить такие координаты внутрь процесса, выполняемого в Docker — переменные окружения. Типичный пример этого подхода, не применительно к докеру — DATABASE_URL , принятый во фреймворке Rails или NODE_ENV принятый в фрейворке Nodejs.

И вот переменные окружения позволяют приложению внутри контейнера удобно и непринуждённо найти базу данных. Но для этого, человек, который пишет приложение, должен об этом знать. И хотя конфигурация приложения с помощью переменных окружения — это хорошо и правильно, иногда приложения написаны плохо, а запускать их как-то надо.

Docker, переменные окружения и ссылки

Docker приходит на помощь нам, когда мы хотим связать два контейнера и даёт нам механизм ссылкок (Docker links). Подробнее о них можно прочитать в руководстве на сайте самого Docker'а, но если вкратце, выглядит это так:

  1. Даём имя контейнеру при запуске: docker run -d --name db training/postgres . Теперь мы можем ссылаться на этот контейнер по имени db .
  2. Запускаем второй контейнер, связывая его с первым: docker run -d -P --name web --link db:db training/webapp python app.py . Самое интересное в этой строчке: --link name:alias . name — имя контейнера, alias — имя, под которым этот контейнер будет известен запускаемому.
  3. Это приведёт к двум последствиям: во-первых, в контейнере web появится набор переменных окружения, указывающих на контейнер db , во-вторых в /etc/hosts контейнера web появится алиас db указывающий на ip, на котором мы запустили контейнер с базой данных. Набор переменных окружения, которые будут доступны в контейнере web вот такой:

И если приложение отчаянно не готово читать такие вот переменные, то на помощь нам придёт консольная утилита socat .

socat

socat — это утилита Unix для перенаправления портов. Идея состоит в том, что с её помощью мы создадим у приложения внутри контейнера впечатление, что, например, база данных, запущена в том же контейнере на том же хосте и на своём стандартном порту, как это происходит на компьютере у разработчика. socat , как всё низкоуровневое юниксовое очень легковесный и ничем не отяготит основной процесс контейнера.

Давайте внимательнее посмотрим на переменные окружения, которые пробрасывает внутрь контейнера механизм ссылок. Нас особенно интересует одна из них: DB_PORT_5432_TCP=tcp://172.17.0.5:5432 . В этой переменной есть все данные, которые нам нужны: порт, который надо бы слушать на localhost (5432 в DB_5432_TCP ) и координаты самой базы данных (172.17.0.5:5432).

Такая переменная будет проброшена в контейнер для каждой переданной ссылки: базы данных, Redis а, вспомогательного сервиса.

Мы напишем скрипт, который будет оборачивать любую команду следующим образом: просканировать список переменных окружения в поисках интересующих нас, для каждой запустить socat, потом запустить переданную команду и отдать управление. Когда скрипт закончится, он должен завершить все socat процессы.

Скрипт

Стандартный заголовок. set -e инструктирует shell при первой же ошибке завершать скрипт, то есть, требует привычного программисту поведения.


Поскольку мы будем порождать дополнительные процессы socat, нам нужно будет за ними следить, чтобы можно было потом их завершить и подождать их завершения.


Теперь мы можем написать функцию, которая будет порождать нам дочерние процессы, которые мы можем запоминать.


Основа идеи в том, чтобы из набора переменных окружения, заканчивающихся на _TCP вытянуть кортежи (целевой_порт,адрес_источника,порт_источника) и превратить их в набор команд запуска socat .


env выведет список переменных окружения, grep оставит только нужные, to_link_tuple вытянет нужные нам тройки, sort | uniq предотвратит запуск двух socat ов для одной службы, to_socat_call уже создаст нужную нам команду.

Мы ещё хотели завершать дочерние процессы socat , когда завершится основной процесс. Мы сделаем это посылкой сигнала SIGTERM .


Запускаем основной процесс командой exec . Тогда управление будет передано ему, мы будем видеть его STDOUT и он станет получать сигналы STDIN а.


Весь скрипт можно посмотреть одним куском.

И что?

Подкладываем этот скрипт в контейнер, например, в /run/links.sh и запускаем контейнер теперь вот так:


Вуаля! В контейнере на 127.0.0.1 на порту 5432 будет доступен наш постгрес.

Entrypoint

Чтобы не нужно было помнить про наш скрипт, образу можно задать точку входа директивой ENTRYPOINT в Dockerfile'е. Это приведёт к тому, что любая команда, запущенная в таком образе, будет сначала дополнена префиксом в виде этой точки входа.

Добавьте в ваш Dockerfile :


и снова контейнер можно будет запускать просто передавая ему команды и быть уверенным в том, что службы из связанных контейнеров будут видны приложению, как будто они запущены на локалхосте.

А если доступа в образ нет?

В связи с вышеизложенным есть интересная задачка: как сделать такое же удобное проксирование служб, если нет доступа внутрь образа? Ну то есть, нам дают образ и клянутся, что внутри есть socat , но нашего скрипта там нет и подложить его мы не можем. Зато запускающую команду можем сделать сколь угодно сложной. Как нам пробросить внутрь свой wrapper?

На помощь приходит возможность пробросить частичку файловой системы хоста внутрь контейнера. Другими словами, мы можем на файловой системе хоста сделать, например, папку /usr/local/docker_bin , положить туда links.sh и запускать контейнер вот так:


В результате любые скрипты, которые мы положим в /usr/local/docker_bin будут доступны внутри контейнера для запуска.

Обратите внимание, что мы использовали флаг ro, не дающий контейнеру возможность писать в папку /run .

Альтернативным вариантом было бы отнаследоваться от образа и просто добавить туда файлы.

Итого

С помощью socat а и доброго слова можно добиться намного более удобного способа связи между контейнерами, чем с помощью одного только доброго слова.

Вместо послесловия

Внимательный и искушенный читатель наверняка подметит, что библиотека ambassadord делает в принципе то же самое, и она это уже делает. И этот читатель будет абсолютно прав. Пользователь, которому просто необходимо заставить заработать свою систему, наверняка предпочтет использовать уже готовое и проверенное решение, но ведь хабр не отличается массовостью таких пользователей. Именно поэтому и возник этот опус, который, как хороший анекдот, не только расказывает очевидные вещи, но еще и обучает.

В прошлой статье мы рассказали, что такое Docker и как с его помощью можно обойти Vendor–lock. В этой статье мы поговорим о Dockerfile как о правильном способе подготовки образов для Docker. Также мы рассмотрим ситуацию, когда контейнерам нужно взаимодействовать друг с другом.



В InfoboxCloud мы сделали готовый образ Ubuntu 14.04 с Docker. Не забудьте поставить галочку «Разрешить управление ядром ОС» при создании сервера, это требуется для работы Docker.

Dockerfile

Подход docker commit, описанный в предыдущей статье, не является рекомендованным для Docker. Его плюс состоит в том, что мы настраиваем контейнер практически так, как привыкли настраивать стандартный сервер.

Вместо этого подхода мы рекомендуем использовать подход Dockerfile и команду docker build. Dockerfile использует обычный DSL с инструкциями для построения образов Docker. После этого выполняется команда docker build для построения нового образа с инструкциями в Dockerfile.

Написание Dockerfile

Давайте создадим простой образ с веб-сервером с помощью Dockerfile. Для начала создадим директорию и сам Dockerfile.

Созданная директория — билд-окружение, в которой Docker вызывает контекст или строит контекст. Docker загрузит контекст в папке в процессе работы Docker–демона, когда будет запущена сборка образа. Таким образом будет возможно для Docker–демона получить доступ к любому коду, файлам или другим данным, которые вы захотите включить в образ.

Добавим в Dockerfile информацию по построению образа:

  • Запуск контейнера из образа
  • Исполнение инструкции и внесение изменений в контейнер
  • Запуск эквивалента docker commit для записи изменений в новый слой образа
  • Запуск нового контейнера из нового образа
  • Исполнение следующей инструкции в файле и повторение шагов процесса.

Первая инструкция в Dockerfile всегда должна быть FROM, указывающая, из какого образа нужно построить образ. В нашем примере мы строим образ из базового образа ubuntu версии 14:04.

Далее мы указываем инструкцию MAINTAINER, сообщающую Docker автора образа и его email. Это полезно, чтобы пользователи образа могли связаться с автором при необходимости.

Инструкция RUN исполняет команду в конкретном образе. В нашем примере с помощью ее мы обновляем APT репозитории и устанавливаем пакет с NGINX, затем создаем файл /usr/share/nginx/html/index.html.

По-умолчанию инструкция RUN исполняется внутри оболочки с использованием обертки команд /bin/sh -c. Если вы запускаете инструкцию на платформе без оболочки или просто хотите выполнить инструкцию без оболочки, вы можете указать формат исполнения:

Мы используем этот формат для указания массива, содержащего команду для исполнения и параметры команды.

Далее мы указываем инструкцию EXPOSE, которая говорит Docker, что приложение в контейнере должно использовать определенный порт в контейнере. Это не означает, что вы можете автоматически получать доступ к сервису, запущенному на порту контейнера (в нашем примере порт 80). По соображениям безопасности Docker не открывает порт автоматически, но ожидает, когда это сделает пользователь в команде docker run. Вы можете указать множество инструкций EXPOSE для указания, какие порты должны быть открыты. Также инструкция EXPOSE полезна для проброса портов между контейнерами.

Строим образ из нашего файла

, где trukhinyuri – название репозитория, где будет храниться образ, nginx – имя образа. Последний параметр — путь к папке с Dockerfile. Если вы не укажете название образа, он автоматически получит название latest. Также вы можете указать git репозиторий, где находится Dockerfile.

В данном примере мы строим образ из Dockerfile, расположенном в корневой директории Docker.

Если в корне билд контекста есть файл .dockerignore – он интерпретируется как список паттернов исключений.

Что произойдет, если инструкция не исполнится?

Давайте переименуем в Dockerfile nginx в ngin и посмотрим.


Мы можем создать контейнер из предпоследнего шага с ID образа 066b799ea548
docker run -i -t 066b799ea548 /bin/bash
и отладить исполнение.

По-умолчанию Docker кеширует каждый шаг и формируя кеш сборок. Чтобы отключить кеш, например для использования последнего apt-get update, используйте флаг --no-cache.

Использования кеша сборок для шаблонизации

Используя кеш сборок можно строить образы из Dockerfile в форме простых шаблонов. Например шаблон для обновления APT-кеша в Ubuntu:

Инструкция ENV устанавливает переменные окружения в образе. В данном случае мы указываем, когда шаблон был обновлен. Когда необходимо обновить построенный образ, просто нужно изменить дату в ENV. Docker сбросит кеш и версии пакетов в образе будут последними.

Инструкции Dockerfile

Давайте рассмотрим и другие инструкции Dockerfile. Полный список можно посмотреть тут.

CMD

Инструкция CMD указывает, какую команду необходимо запустить, когда контейнер запущен. В отличие от команды RUN указанная команда исполняется не во время построения образа, а во время запуска контейнера.

В данном случае мы запускаем bash и передаем ему параметр в виде массива. Если мы задаем команду не в виде массива — она будет исполняться в /bin/sh -c. Важно помнить, что вы можете перегрузить команду CMD, используя docker run.

ENTRYPOINT

Часто команду CMD путают с ENTRYPOINT. Разница в том, что вы не можете перегружать ENTRYPOINT при запуске контейнера.

При запуске контейнера параметры передаются команде, указанной в ENTRYPOINT.

Можно комбинировать ENTRYPOINT и CMD.

В этом случае команда в ENTRYPOINT выполнится в любом случае, а команда в CMD выполнится, если не передано другой команды при запуске контейнера. Если требуется, вы все-таки можете перегрузить команду ENTRYPOINT с помощью флага --entrypoint.

WORKDIR

С помощью WORKDIR можно установить рабочую директорию, откуда будут запускаться команды ENTRYPOINT и CMD.

Вы можете перегрузить рабочую директорию контейнера в рантайме с помощью флага -w.

USER

Специфицирует пользователя, под которым должен быть запущен образ. Мы можем указать имя пользователя или UID и группу или GID.

Вы можете перегрузить эту команду, используя глаг -u при запуске контейнера. Если пользователь не указан, используется root по-умолчанию.

VOLUME

Инструкция VOLUME добавляет тома в образ. Том — папка в одном или более контейнерах или папка хоста, проброшенная через Union File System (UFS).
Тома могут быть расшарены или повторно использованы между контейнерами. Это позволяет добавлять и изменять данные без коммита в образ.

В примере выше создается точка монтирования /opt/project для любого контейнера, созданного из образа. Таким образом вы можете указывать и несколько томов в массиве.

ADD

Инструкция ADD добавляет файлы или папки из нашего билд-окружения в образ, что полезно например при установке приложения.

Источником может быть URL, имя файла или директория.


В последнем примере архив tar.gz будет распакован в /var/www/wordpress. Если путь назначения не указан — будет использован полный путь включая директории.

COPY

Инструкция COPY отличается от ADD тем, что предназначена для копирования локальных файлов из билд-контекста и не поддерживает распаковки файлов:

ONBUILD

Инструкция ONBUILD добавляет триггеры в образы. Триггер исполняется, когда образ используется как базовый для другого образа, например, когда исходный код, нужный для образа еще не доступен, но требует для работы конкретного окружения.

Коммуникация между контейнерами

В предыдущей статье было показано, как запускать изолированные контейнеры Docker и как пробрасывать файловую систему в них. Но что, если приложениям нужно связываться друг с другом. Есть 2 способа: связь через проброс портов и линковку контейнеров.

Проброс портов

Такой способ связи уже был показан ранее. Посмотрим на варианты проброса портов чуть шире.
Когда мы используем EXPOSE в Dockerfile или параметр -p номер_порта – порт контейнера привязывается к произвольному порту хоста. Посмотреть этот порт можно командой docker ps или docker port имя_контейнера номер_порта_в_контейнере. В момент создания образа мы можем не знать, какой порт будет свободен на машине в момент запуска контейнера.

Указать, на какой конкретный порт хоста мы привяжем порт контейнера можно параметром docker run -p порт_хоста: порт_контейнера
По-умолчанию порт используется на всех интерфейсах машины. Можно, например, привязать к localhost явно:

Можно привязать UDP порты, указав /udp:

Линковка контейнеров

Связь через сетевые порты — лишь один способ коммуникации. Docker предоставляет систему линковки, позволяющую связать множество контейнеров вместе и отправлять информацию о соединении от одного контейнера другому.

Для установки связи нужно использовать имена контейнеров. Как было показано ранее, вы можете дать имя контейнеру при создании с помощью флага --name.

Допустим у вас есть 2 контейнера: web и db. Чтобы создать связь, удалите контейнер web и пересоздайте с использованием команды --link name:alias.

Используя docker -ps можно увидеть связанные контейнеры.

  • Через переменные окружения
  • Через /etc/hosts

Префикс DB_ был взят из alias контейнера.

Можно просто использовать информацию из hosts, например команда ping db (где db – alias) будет работать.

Заключение

В этой статье мы научились использовать Dockerfile и организовывать связь между контейнерами. Это только вершина айсберга, очень многое осталось за кадром и будет рассмотрено в будущем. Для дополнительного чтения рекомендуем книгу The Docker Book.

Готовый образ с Docker доступен в облаке InfoboxCloud.

В случае, если вы не можете задавать вопросы на Хабре, можно задать в Сообществе InfoboxCloud.
Если вы обнаружили ошибку в статье, автор ее с удовольствием исправит. Пожалуйста напишите в ЛС или на почту о ней.

Изображение от Mike Wheatley

Изображение от Mike Wheatley

Цель этой статьи — показать как мы можем сконфигурировать два и более контейнера, чтобы они могли взаимодействовать друг с другом. В этой статье мы сделаем следующее:

Создадим образ Docker используя простой веб-сервис с использованием Python и Flask.

Запустим два отдельных контейнера

Создадим сеть в Docker

Объединим контейнеры используя созданную сеть

Подготовка

Чтобы пойти дальше вы должны обладать средними знаниями в программировании и API. Также понадобится докер инсталлированный локально на вашей машине.

Руководство об основах работы с контейнерами можно найти здесь:

Для этой статьи мы будем использовать два простых веб-сервиса, каждый со своим эндпойнтом. Давайте назовём первый сервис "ping", второй — "pong", а нашим замыслом будет отправка запроса сервисом "ping" к сервису "pong" так, чтобы они могли играть в пинг-понг.

Я использовал Flask и Docker чтобы создать простое приложение и вы можете прочитать больше о тех базовых командах Docker что я использовал, в этом руководстве.

Мы запустим каждый сервис в собственном контейнере и объединим эти контейнеры, используя сеть в Docker.

Сервис "ping"

Наши сервисы - очень простые flask-приложения. В app.py будут наши эндпойнты.

В нашем случае сервис "ping" будет иметь эндпойнт "/ping", который будет отправлять запросы к сервису "pong" в эндпойнт "/pong". Если сервис "pong" недоступен, то он просто вернёт "Ping …”. В противном случае сервис вернёт “Ping … Pong”.

В requirements.txt перечислены все модули, которые мы будем использовать, а в Dockerfile перечислены все шаги, которые помогут нам собрать образ.

Сервис "pong"

Так же, как и сервис "ping", наш сервис "pong" представляет собой flask-приложение и имеет эндпойнт "/pong", как показано ниже.

Сервис "ping" сервис мы запустим на порту 5000, а сервис "pong" на порту 5001.

Собираем образы Docker

Сейчас у нас есть два python-сервиса с их Dockerfile. Давайте соберём образы Docker для них.

После того как выполним команду docker images мы должны увидеть два образа:

Запуск контейнеров

Теперь у нас есть образы, давайте создадим из них контейнеры и запустим их.

И ожидаемый вывод в консоль будет подобен следующему:

Теперь давайте запустим контейнер для сервиса "pong":

А сейчас давайте выполним docker container ls , чтобы посмотреть на созданный контейнеры:

Мы видим, что у нас теперь есть два контейнера с именами pong-service-container и ping-service-container .

Настраиваем сеть в Docker

Мы можем сделать доступным взаимодействие через сеть в Docker посредством следующих шагов:

Добавляем контейнеры в сеть

И таким образом всем контейнеры в одной докер-сети могут взаимодействовать между собой через имя контейнера или IP-адрес.

Давайте выполним эти вышеуказанные шаги.

Создаём сеть в Docker

Давайте создадим сеть с именем ping-pong-network

и когда мы выполним команду docker network inspect ping-pong-network , мы получим:

И как говорилось выше, это сеть без контейнеров и мы можем пойти дальше и добавить запущенные контейнеры в эту сеть, используя следующие команды:

И если теперь запустим инспектирование сети( docker network inspect ping-pong-network) , то в секции Containers мы увидим наши контейнеры:

И как было сказано выше, контейнеры могут взаимодействовать друг с другом используя имя контейнера или IPv4 адрес.

Проверяем взаимодействие контейнеров

Когда оба сервиса — "ping" и "pong" будут объединены общей сетью, то запрос к эндпойнту "/ping" сервиса "ping":

Для тестирования остановим один из контейнеров и затем проведём инспекцию сети. Мы должны будем увидеть только один контейнер.

Видеоруководство

Кому лень читать, кто больше любит видео и сюда пролистал "по диагонали", может посмотреть это руководство в формате видео.

Заключение

Контейнеры в одной докер-сети могут взаимодействовать используя свой IP адрес или имя контейнера.

Это удобно при использовании Docker во время разработки или в производственном окружении, когда вы бы хотели использовать отдельные контейнеры для разных сервисов — например, базы данных, фронтенда, бэкенда, поиска и т.д.

If you are working your way through the user guide, you just built and ran a simple application. You’ve also built in your own images. This section teaches you how to network your containers.

Launch a container on the default network

Docker includes support for networking containers through the use of network drivers. By default, Docker provides two network drivers for you, the bridge and the overlay drivers. You can also write a network driver plugin so that you can create your own drivers but that is an advanced task.

Every installation of the Docker Engine automatically includes three default networks. You can list them:

The network named bridge is a special network. Unless you tell it otherwise, Docker always launches your containers in this network. Try this now:

bridge1

Inspecting the network is an easy way to find out the container’s IP address.

You can remove a container from a network by disconnecting the container. To do this, you supply both the network name and the container name. You can also use the container ID. In this example, though, the name is faster.

While you can disconnect a container from a network, you cannot remove the builtin bridge network named bridge . Networks are natural ways to isolate containers from other containers or other networks. So, as you get more experienced with Docker, create your own networks.

Create your own bridge network

Docker Engine natively supports both bridge networks and overlay networks. A bridge network is limited to a single host running Docker Engine. An overlay network can include multiple hosts and is a more advanced topic. For this example, create a bridge network:

The -d flag tells Docker to use the bridge driver for the new network. You could have left this flag off as bridge is the default value for this flag. Go ahead and list the networks on your machine:

If you inspect the network, it has nothing in it.

Add containers to a network

To build web applications that act in concert but do so securely, create a network. Networks, by definition, provide complete isolation for containers. You can add containers to a network when you first run a container.

Launch a container running a PostgreSQL database and pass it the --net=my_bridge flag to connect it to your new network:

If you inspect your my_bridge you can see it has a container attached. You can also inspect your container to see where it is connected:

Now, go ahead and start your by now familiar web application. This time don’t specify a network.

bridge2

Which network is your web application running under? Inspect the application to verify that it is running in the default bridge network.

Then, get the IP address of your web

Now, open a shell to your running db container:

After a bit, use CTRL-C to end the ping and notice that the ping failed. That is because the two containers are running on different networks. You can fix that. Then, use the exit command to close the container.

Docker networking allows you to attach a container to as many networks as you like. You can also attach an already running container. Go ahead and attach your running web app to the my_bridge .

bridge3

Open a shell into the db application again and try the ping command. This time just use the container name web rather than the IP address.

The ping shows it is contacting a different IP address, the address on the my_bridge which is different from its address on the bridge network.

Next steps

Now that you know how to network containers, see how to manage data in containers.

Читайте также: