Docker для новичков — #3 Что нужно знать о Docker compose

Эта публикация - текстовый вариант и сценарий для видео на YouTube (оно удобно разбито на эпизоды).

Привет, сегодня я расскажу о том что такое Docker compose файл, из чего он состоит и как его написать.

Docker compose - команда Docker, которая позволяет запустить несколько контейнеров в Docker. Благодаря Compose-файлам можно описать взаимодействие контейнеров, правила их запуска и работы, сделать отдельный файл, который позволит запускать мультиконтейнерные приложения в помощью одной команды.

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

Чтобы воспользоваться Compose, вам необходимо выполнить лишь три шага:

  1. Создайте Dockerfile для вашего приложения, либо же воспользуйтесь docker image с какого-нибудь репозитория, например, DockerHub

  2. Создайте Docker compose файл, в котором опишите сервисы, которые должны работать вместе в изолированной среде.

  3. Запустите docker compose up и Docker запустит все ваше приложение

Compose application model

Спецификация Compose позволяет определить платформонезависимое контейнерное приложение. Такое приложение является набором контейнеров, которые должны работать совместно с адекватным разделением ресурсов и каналом коммуникации.

В Docker используется термин mount, который используется для обозначения привязки какого-то ресурса, например папки с файлами, к контейнеру. Я не нашел красивого русского обозначения, поэтому буду употреблять слово “связан”. То есть это не абстрактная связь, а конкретная связь контейнера и каких-то данных вне контейнера. Учитывайте это по ходу видео.

Компоненты приложения называются сервисами.

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

Сервисы взаимодействуют между собой через сеть (network). В Compose network - это еще одна абстракция, которая позволяет устанавливать IP соединения между контейнерами, внутри которого сервисы могут коммуницировать друг с другом.

Сервисы хранят и разделяют между собой данные в volumes. Мы говорили немного о volumes в прошлой статье о Dockerfile. Если не смотрели его, то посмотрите, там много интересного и подробного. Спецификация compose описывает то какие данные и где должны храниться.

Некоторые сервисы требуют конфигурационные данные для работы, поэтому есть отдельный концепт - config. С точки зрения контейнера configs похожи на volumes - так как это тоже файлы, которые могут быть связаны с контейнером и читаться им.

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

Project - это индивидуальное размещение (деплой) приложения на конкретной платформе. То есть тот docker compose файл, который вы напишите - это отдельный проект. Имя проекта используется для того, чтобы группировать ресурсы вместе и изолировать их от других приложений или других таких же запущенных проектов.

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

Изображение взято из документации Docker.
Изображение взято из документации Docker.

Изображение взято из документации Docker.Это пример приложения с фронтендом и бэкендом.

Фронтенд сконфигурирован в рантайме при помощи HTTP Configuration файла, а также HTTPS сертификатом, который встроен в secret store приложения - то место, где хранятся секреты.

Бэкенд хранит данные в постоянном хранилище. Оба сервиса взаимодействуют между собой при помощи изолированной backend сети, фронтенд также состоит во фронтенд сети, открывая https порт 443 наружу.

Это приложение состоит из следующих частей:

  • 2 сервиса - фронтенд с image webapp и бэкенд с image database

  • 1 секрета - HTTPS-сертификата, включенных во фронтенд

  • 1 конфигурации, включенной во фронтенд

  • 1 постоянного хранилища - volume, связанного с бэкендом

  • 2 сетей - фронтенд и бэкенд сети

Так описан compose-файл

services:
frontend:
image: example/webapp
ports:
- "443:8043"
networks:
- front-tier
- back-tier
configs:
- httpd-config
secrets:
- server-certificate

backend:
image: example/database
volumes:
- db-data:/etc/data
networks:
- back-tier

volumes:
db-data:
driver: flocker
driver_opts:
size: "10GiB"

configs:
httpd-config:
external: true

secrets:
server-certificate:
external: true

networks:
# The presence of these objects is sufficient to define them
front-tier: {}
back-tier: {}

В этом примере показаны различия между volume, secret и config:

  • они все связаны с контейнером, однако только volume может изменять данные

  • secrets и configs readonly

Compose file

Compose файл - это .yaml файл, который определяет конфигурацию приложения, а именно версию файла, сервисы - контейнеры, которые должны быть запущены, сети, volumes, конфигурации и secrets.

По умолчанию используется имя compose.yaml или docker-compose.yaml, однако предпочтительно использовать первый вариант compose.yaml

Несколько compose файлов могут быть объединены вместе. Такое объединение перезаписывает или дополняет другой compose файл, собирая несколько файлов в один.

Вы можете использовать фрагменты, расширения или include-команду для работы с несколькими Compose файлами.

YAML формат стал популярным относительно недавно и он считается самым удобным для восприятия человеком. Данные в этом файле представляются как ключ: значение, табуляция позволяет понять уровень вложенности.

В нем поддерживаются списки, ассоциативные массивы или мапы. Вы можете ознакомиться с синтаксисом на сайте yaml.org. Однако я верю, что на приведенных в этой статье примерах вы сможете понять как писать и читать файлы с таким форматом данных.

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

На предыдущем примере на верхнем уровне находятся элементы services, networks, secrets, configs, volumes.

Версия Docker compose файла

В начале docker compose файла на верхнем уровне можно указать версию compose - она опциональна. Она используется для обратной совместимости и только предоставляет информацию для разработчиков.

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

Имя Docker compose файла

Имя указывает на то, как должно называться приложение, которое создается при помощи Compose файла. Если вы не укажите его явно, то оно будет сгенерировано. Как только имя определено, к нему можно обратиться по ключу COMPOSE_PROJECT_NAME

Изображение взято из документации Docker.
Изображение взято из документации Docker.

Service

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

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

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

Вы можете использовать как существующий image, который будет пулиться из какого-нибудь registry, так и собирать image при помощи Dockerfile, указав к нему путь.

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

Атрибут build - описывает путь к Dockerfile из которого будет собран image, а также параметры для этого Dockerfile, например, контекст.

Эта публикация - текстовый вариант и сценарий для видео на YouTube (оно удобно разбито на эпизоды).-3
Изображение взято из документации Docker.

В этом примере сервисы frontend и backend собираются из Dockerfile, который находится соответственно в ./webapp и ../backend.Dockerfile

Command - переопределяет инструкцию CMD, которая указана в Dockerfile этого сервиса, таким образом именно эта команда будет использоваться при запуске контейнера, а не та, что была указана в Dockerfile. Этот атрибут принимает как shell, так и exec форму команды, как и инструкция CMD.

Configs - позволяет сервису использовать конфигурации, про которые мы говорили раньше в этом видео.

Эта публикация - текстовый вариант и сценарий для видео на YouTube (оно удобно разбито на эпизоды).-4
Изображение взято из документации Docker.

Container_name определяет имя для контейнера, который представлен этим сервисом. Этот атрибут принимает строку - название контейнера.

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

Эта публикация - текстовый вариант и сценарий для видео на YouTube (оно удобно разбито на эпизоды).-5
Изображение взято из документации Docker.

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

Entrypoint переопределяет ENTRYPOINT, который будет вызван при запуске контейнера. В прошлой статье мы узнали, что в Dockerfile выполняется только последняя инструкция ENTRYPOINT, но здесь мы ее переопределяем и можем задать новое поведение при запуске контейнера.

Эта публикация - текстовый вариант и сценарий для видео на YouTube (оно удобно разбито на эпизоды).-6
Изображение взято из документации Docker.

Env_file указывает путь к файлу с переменными, которые будут использоваться для работы контейнера. Чтобы не указывать все переменные, вы можете поместить их в один файл и сослаться на него. Вы можете передать как путь к отдельному файлу, так и список таких путей.

Эта публикация - текстовый вариант и сценарий для видео на YouTube (оно удобно разбито на эпизоды).-7
Изображение взято из документации Docker.

Environment определяет пары ключ: значение для переменных, которые нужны при работе контейнера. Вы можете использовать два разных синтаксиса - map и list.

Эта публикация - текстовый вариант и сценарий для видео на YouTube (оно удобно разбито на эпизоды).-8
Изображение взято из документации Docker.
Эта публикация - текстовый вариант и сценарий для видео на YouTube (оно удобно разбито на эпизоды).-9
Изображение взято из документации Docker.

Если вы указали и env_file и env переменные, тогда переменные из env имеют приоритет и будут перезаписывать переменные с таким же названием из env файла.

Image определяет image, который будет запущен в контейнере. Вы можете указать просто название, но можете и дополнить его тегом или дайджестом. В сервисе должен быть указан либо image либо build атрибут, для того, чтобы Docker смог понять что должно быть запущено.

Эта публикация - текстовый вариант и сценарий для видео на YouTube (оно удобно разбито на эпизоды).-10
Изображение взято из документации Docker.

Networks - указывает список network, к которым принадлежит сервис.

Эта публикация - текстовый вариант и сценарий для видео на YouTube (оно удобно разбито на эпизоды).-11
Изображение взято из документации Docker.

В этом примере сервис some-service принадлежит к двум сетям, которые были описаны в Compose файле.

Ports определяет порты, которые отображаются наружу из контейнера. Мы говорили в прошлой статье, что EXPOSE в Dockerfile не открывает порты, а лишь говорит разработчикам о том, что этот порт нужен для работы приложения. А вот в Compose файле ports как раз и открывает порты наружу.

Эта публикация - текстовый вариант и сценарий для видео на YouTube (оно удобно разбито на эпизоды).-12
Изображение взято из документации Docker.

Таким образом обратившись к localhost:3000 вы попадете внутрь контейнера на порт 3000. Вы можете поменять порт и обращаться к другому локальному порту, который в свою очередь будет отображен в контейнер. Порты на локальной машине должны быть доступны, иначе Compose файл не будет запущен.

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

Возможные значения:

  • Always - image всегда пулятся

  • Never - не пулятся никогда и должны быть на локальной машине, чтобы Compose файл мог ими воспользоваться

  • Missing - Docker пулит только недостающие image

  • Build - Docker будет создавать image из Dockerfile каждый раз

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

Есть несколько опций:

  • No - по умолчанию. Docker не перезапускает контейнер если он упал

  • Always - Docker всегда перезапускает контейнер, пока он не будет удален

  • On-failure - Docker перезапускает контейнер, если он упал из-за ошибки, а не был остановлен правильно

  • Unless-stopped - Docker перезапускает контейнер, пока сервис не будет остановлен или удален

Эта публикация - текстовый вариант и сценарий для видео на YouTube (оно удобно разбито на эпизоды).-13
Изображение взято из документации Docker.

Secrets - позволяет сервису обратиться к секретам, которые были указаны в текущем Compose-файле.

Эта публикация - текстовый вариант и сценарий для видео на YouTube (оно удобно разбито на эпизоды).-14
Изображение взято из документации Docker.

Volumes определяет путь хранения volume из контейнера на локальном хосте. Вы можете создать volume в какой-нибудь директории локально или же сослаться на описанный volume из текущего Compose-файла.

Эта публикация - текстовый вариант и сценарий для видео на YouTube (оно удобно разбито на эпизоды).-15
Изображение взято из документации Docker.

Изображение взято из документации Docker.Network

Networks или сети позволяют сервисам общаться между собой. Поместив несколько сервисов в один network, они могут свободно обращаться друг к другу по названию сервиса. Это удобно и быстро конфигурируется, что делает деплой приложения в Docker простым.

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

Сейчас на примерах мы рассмотрим то, как настроить сеть и какие атрибуты у нее есть.

Изображение взято из документации Docker.
Изображение взято из документации Docker.

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

Эта публикация - текстовый вариант и сценарий для видео на YouTube (оно удобно разбито на эпизоды).-17
Изображение взято из документации Docker.

В этом более сложном примере есть три сервиса - прокси, приложение и база данных. Этот пример напоминает тот, что мы рассмотрели в начале видео.

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

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

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

Атрибут driver указывает на то, какой драйвер для работы сети следует использовать. Будет выброшена ошибка, если драйвер недоступен для использования при запуске Compose файла.

Есть два типа драйверов, которые поддерживаются Compose - host и none. Host использует возможности хоста, то есть того компьютера, на котором запущен Compose файл, для коммуникации между сервисами, none отключает коммуникацию.

Эта публикация - текстовый вариант и сценарий для видео на YouTube (оно удобно разбито на эпизоды).-18
Изображение взято из документации Docker.

Driver_opts определяет список опций для драйвера. Про них можно узнать из документации конкретного драйвера

Эта публикация - текстовый вариант и сценарий для видео на YouTube (оно удобно разбито на эпизоды).-19
Изображение взято из документации Docker.

Если атрибут Attachable установлен в true, тогда контейнеры вне этого Compose файла могут подключиться к сети и общаться с сервисами внутри этой сети.

Эта публикация - текстовый вариант и сценарий для видео на YouTube (оно удобно разбито на эпизоды).-20
Изображение взято из документации Docker.

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

Эта публикация - текстовый вариант и сценарий для видео на YouTube (оно удобно разбито на эпизоды).-21
Изображение взято из документации Docker.

В данном примере Compose будет использовать уже существующую сеть outside, а не пытаться создать новую.

Если атрибут Internal установлен в true, тогда вы запретите внешнее подключение к этой сети снаружи Compose файла, так как по умолчанию Compose предоставляет возможность внешнего подключения.

Атрибут Name устанавливает имя для сети, как и с volume, вы можете использовать переменные для подстановки значений при запуске Compose файла.

Эта публикация - текстовый вариант и сценарий для видео на YouTube (оно удобно разбито на эпизоды).-22
Изображение взято из документации Docker.

Если вы используете внешний network, тогда вы можете указать настоящее имя сети, а внутри текущего Compose файла обращаться к сети с другим именем.

Volume

Volumes - это постоянные хранилища данных вашего контейнера.

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

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

Давайте рассмотрим несколько примеров конфигурации volumes.

Эта публикация - текстовый вариант и сценарий для видео на YouTube (оно удобно разбито на эпизоды).-23
Изображение взято из документации Docker.

На этом примере мы видим два сервиса, один из которых база данных, а второй - бэкап сервис, делающий бэкапы базы данных. Оба этих сервиса имеют доступ к данным из базы данных благодаря volume.

Сервис базы данных сохраняет данные в volume db-data, а сервис бэкапа сохраняет эти данные в своем контейнере по пути /etc/data
Таким образом данные из одного контейнера попадают во второй.

У конфигурации volume есть несколько атрибутов, но они не являются обязательными.

Driver указывает какой драйвер для volume должен быть использован. Некоторые могут быть платформозависимыми. Если драйвер недоступен, то Docker выбросит ошибку при попытке запуске Compose файла.

Эта публикация - текстовый вариант и сценарий для видео на YouTube (оно удобно разбито на эпизоды).-24
Изображение взято из документации Docker.

Driver_opts определяет опции volume, пар ключ: значение, они тоже платформозависимые, то есть на Windows и на Linux эти опции могут отличаться.

Эта публикация - текстовый вариант и сценарий для видео на YouTube (оно удобно разбито на эпизоды).-25
Изображение взято из документации Docker.

Если атрибут External установлен в true, тогда Docker ожидает, что этот volume уже существует и управляется из другого Compose файла. Если такого volume не будет, то выбросится ошибка при запуске Compose файла. Также в этом случае все атрибуты кроме имени не будут учитываться, раз volume был создан и управляется из другого места. Но если эти атрибуты отличаются от оригинальных, то будет выброшена ошибка. Таким образом определяя external как true, вы можете указать только имя volume для этого Compose файла.

Эта публикация - текстовый вариант и сценарий для видео на YouTube (оно удобно разбито на эпизоды).-26
Изображение взято из документации Docker.

На этом примере volume не создается, а ищется уже существующий.

Name устанавливает имя для volume. Вы можете воспользоваться подстановкой переменных при запуске Compose файла. В данном случае имя volume будет получено из переменной DATABASE_VOLUME из .env файла

Эта публикация - текстовый вариант и сценарий для видео на YouTube (оно удобно разбито на эпизоды).-27
Изображение взято из документации Docker.
Эта публикация - текстовый вариант и сценарий для видео на YouTube (оно удобно разбито на эпизоды).-28
Изображение взято из документации Docker.

В этом примере внешний volume имеет имя actual-name-of-volume, а внутри текущего Compose файла вы можете обратиться по имени db-data

Config

Config позволяет сервисам изменять их поведение без необходимости изменения используемых image.

Сервисы могут обратиться к конфигам только если имеют атрибут configs.

Как и volume, конфиги связаны с файловой системой контейнера. По умолчанию они находятся в корне файловой системы контейнера.

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

  • File - создается конфиг со всем содержимым файла

  • Environment - создается конфиг со значением переменной

  • Content - создается конфиг со значением, которое было передано в этом атрибуте

External - Если указано как true, Docker ожидает, что конфиг был создан, он не пытается его создать заново и выбрасывает ошибку, если такого конфига не существует.

Name - имя конфига, которое Docker ищет

Эта публикация - текстовый вариант и сценарий для видео на YouTube (оно удобно разбито на эпизоды).-29
Изображение взято из документации Docker.

В этом примере создается конфиг, значением которого будет содержимое файла. Имя для него будет <project_name>_http_config

Эта публикация - текстовый вариант и сценарий для видео на YouTube (оно удобно разбито на эпизоды).-30
Изображение взято из документации Docker.

В этом случае создается конфиг, содержимым которого является указанный текст.

Secret

Секреты используются для использования чувствительных данных. Сервисы могут обратиться к секретам только если имеют атрибут secrets.

На верхнем уровне элемент secrets определяет или ссылается на те самые чувствительные данные, источником которых служит либо файл либо environment.

  • Если источник file, тогда создается секрет с содержимым этого файла.

  • Если источник environment, тогда создается секрет со значением этой переменной.

External обозначает то, что секрет уже был создан и Docker не пытается его создать. Если он не был создан, тогда вы получите ошибку при выполнении Compose файла.

Name - название секрета в Docker

Давайте рассмотрим несколько примеров.

Эта публикация - текстовый вариант и сценарий для видео на YouTube (оно удобно разбито на эпизоды).-31
Изображение взято из документации Docker.

В этом примере создается секрет с названием <project_name>_server-certificate в момент запуска Compose файла, а значение секрета берется из файла server.cert

Эта публикация - текстовый вариант и сценарий для видео на YouTube (оно удобно разбито на эпизоды).-32
Изображение взято из документации Docker.

В этом случае секрет называется как <project_name>_token и создается во время запуска Compose файла, значением является значение переменной.

Эта публикация - текстовый вариант и сценарий для видео на YouTube (оно удобно разбито на эпизоды).-33
Изображение взято из документации Docker.

В таком случае Docker пытается найти уже созданный секрет и использовать его.

Fragments

В Compose файле можно использовать встроенные возможности языка YAML, которые сделают Compose файл более аккуратным и эффективным.

Вы можете создать фрагменты для того, чтобы переиспользовать некоторые части файла. Например, если у вас есть несколько сервисов с одинаковой конфигурацией, вы можете объединить эту конфигурацию в одну и не дублировать ее повторно.

Фрагменты создаются при помощи символы амперсанд & и имени фрагмента. Они устанавливаются на ключи YAML файла, раз этот формат - это ключ-значение, то подстановки происходят после двоеточия. С помощью * и имени можно обратиться к этому фрагменту и получить его значение.

Фрагменты подставляются до подстановки переменных, поэтому здесь их нельзя использовать для создания названий.

Эта публикация - текстовый вариант и сценарий для видео на YouTube (оно удобно разбито на эпизоды).-34
Изображение взято из документации Docker.

В данном примере создается фрагмент default-volume, который подставит все свойства volume db-data в volume metrics, то есть metrics будет также иметь свойство driver: default.

Эта публикация - текстовый вариант и сценарий для видео на YouTube (оно удобно разбито на эпизоды).-35
Изображение взято из документации Docker.

В этом примере переменные одного сервиса помечаются якорем, чтобы быть переиспользоваными во втором сервисе.

Иногда вы можете хотеть переопределить значения фрагмента только частично, при помощи YAML merge вы можете это сделать как в данном примере.

Эта публикация - текстовый вариант и сценарий для видео на YouTube (оно удобно разбито на эпизоды).-36
Изображение взято из документации Docker.

В данном примере default-volume подставит только driver, а name подставлен не будет, так как мы его явно указали.

Вы также можете дополнить значения фрагмента дополнительными элементами как в этом примере.

Эта публикация - текстовый вариант и сценарий для видео на YouTube (оно удобно разбито на эпизоды).-37
Изображение взято из документации Docker.

Но в таком случае вы должны указывать переменные в формате Ключ: Значение, а не - Ключ=Значение.

Если вы хотите использовать фрагменты в нескольких файлах, тогда воспользуйтесь расширениями Extension.

Extensions

Как и фрагменты, Extensions могут быть использованы для того, чтобы сделать Compose файл более эффективным и поддерживаемым.

Для этого на верхнем уровне нужно определить элемент, который начинается с x-. Так Docker поймет, что это расширение и будет знать о нем.

Давайте рассмотрим несколько примеров:

Эта публикация - текстовый вариант и сценарий для видео на YouTube (оно удобно разбито на эпизоды).-38
Изображение взято из документации Docker.

В первом примере определен extension x-custom и x-foo, мы можем добавить x-foo в сервис и Docker его обработает, но ничего не сделает, однако значение x-foo можно получить в самом приложении webapp.

Во втором примере env не принадлежит ни одному из сервисов. Такой extension определяет новую ноду, в которой содержится поле environment. А якорь используется для того, чтобы обратиться к этому полю в двух сервисах.

Эта публикация - текстовый вариант и сценарий для видео на YouTube (оно удобно разбито на эпизоды).-39
Изображение взято из документации Docker.

В этом примере можно использовать объединение из нескольких расширений, тогда все ключи из обоих расширений будут подставлены. Как и в случае с фрагментами, вы должны указывать переменные в формате Ключ: Значение, а не - Ключ=Значение.

Interpolation

Как и в Dockerfile, мы можем воспользоваться wildcard для подстановки значений переменных из конфигурационного файла.

Значение переменной можно получить написав ${VARIABLE} или $VARIABLE. Можно обратиться к вложенным переменным, например как здесь.

Эта публикация - текстовый вариант и сценарий для видео на YouTube (оно удобно разбито на эпизоды).-40
Изображение взято из документации Docker.

Есть несколько видов подстановки.

Эта публикация - текстовый вариант и сценарий для видео на YouTube (оно удобно разбито на эпизоды).-41
Изображение взято из документации Docker.
Эта публикация - текстовый вариант и сценарий для видео на YouTube (оно удобно разбито на эпизоды).-42
Изображение взято из документации Docker.

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

Если используется слияние нескольких Compose файлов, тогда подстановка происходит до слияния, чтобы не допустить ошибок.

Подстановки работают только для значений, а не ключей, поэтому такой пример не будет работать

Эта публикация - текстовый вариант и сценарий для видео на YouTube (оно удобно разбито на эпизоды).-43
Изображение взято из документации Docker.

Merge

Вы можете использовать несколько compose файлов и объединить их в один, чтобы собрать свое приложение.

Давайте рассмотрим правила, которые соблюдает merge.

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

Эта публикация - текстовый вариант и сценарий для видео на YouTube (оно удобно разбито на эпизоды).-44
Изображение взято из документации Docker.

Если мержутся последовательности, тогда они дополняются.

Изображение взято из документации Docker.
Изображение взято из документации Docker.

Command, entrypoint, healthcheck перезаписываются, так как мы помним, что они не могут дублироваться в Dockerfile, то и здесь они не могут использоваться совместно, а только последняя инструкция будет применена.

Эта публикация - текстовый вариант и сценарий для видео на YouTube (оно удобно разбито на эпизоды).-46
Изображение взято из документации Docker.

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

Эта публикация - текстовый вариант и сценарий для видео на YouTube (оно удобно разбито на эпизоды).-47
Изображение взято из документации Docker.

В качестве уникальных ресурсов выступают:

Эта публикация - текстовый вариант и сценарий для видео на YouTube (оно удобно разбито на эпизоды).-48
Изображение взято из документации Docker.

Include

Вы можете сослаться на другой Compose файл если вы хотите:

  1. Переиспользовать Compose файлы,

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

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

Для того, чтобы подключить другой файл, вы должны указать на верхнем уровне секцию include. Как только compose файлы из этой секции загрузились, они копируются в данный проект. Вы увидите предупреждение, если файлы конфликтуют между собой и Docker не будет сам мержить их.

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

Эта публикация - текстовый вариант и сценарий для видео на YouTube (оно удобно разбито на эпизоды).-49
Изображение взято из документации Docker.

Вы также можете использовать переменные в include для указания относительного пути папок, например для сборки приложения для разработки и production.

Эта публикация - текстовый вариант и сценарий для видео на YouTube (оно удобно разбито на эпизоды).-50
Изображение взято из документации Docker.

Можно использовать длинный синтаксис и более детально сконфигурировать include.

Эта публикация - текстовый вариант и сценарий для видео на YouTube (оно удобно разбито на эпизоды).-51
Изображение взято из документации Docker.

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

Project_directory определяет путь, относительно которого относительные пути из path будут применены. По умолчанию это папка с текущим Compose файлом.

Env_file указывает путь к файлу с переменными, которые будут использоваться в указанных Compose файлах. По умолчанию это .env файл в дирректории project_directory. Тут так же можно указать как строку так и список строк. Переменные в локальной директории, то есть в той, в которой находится текущий Compose файл имеют преимущество перед импортируемыми и могут их переписать.

Заключение

Надеюсь, что вы узнали что-то новое и поняли то как работает Compose. Если у вас остались вопросы, то можете их задать в комментариях, Discord или в сообщения в телеграмме.

В следующей статье мы рассмотрим оптимизацию Dockerfile.