Эта публикация - текстовый вариант и сценарий для видео на YouTube (оно удобно разбито на эпизоды).
Привет, сегодня я расскажу о том что такое Docker compose файл, из чего он состоит и как его написать.
Docker compose - команда Docker, которая позволяет запустить несколько контейнеров в Docker. Благодаря Compose-файлам можно описать взаимодействие контейнеров, правила их запуска и работы, сделать отдельный файл, который позволит запускать мультиконтейнерные приложения в помощью одной команды.
У compose есть официальная спецификация, которая детально рассказывает о том, как ее реализация должна работать. Ссылку я оставлю в описании. Ее не то чтобы стоит читать, так как там очень много текста, который вам не поможет так хорошо как эта статья или документация с сайта Docker, но знайте, что спецификация есть и к ней можно обратиться в случае каких-то проблем.
Чтобы воспользоваться Compose, вам необходимо выполнить лишь три шага:
Создайте Dockerfile для вашего приложения, либо же воспользуйтесь docker image с какого-нибудь репозитория, например, DockerHub
Создайте Docker compose файл, в котором опишите сервисы, которые должны работать вместе в изолированной среде.
Запустите docker compose up и Docker запустит все ваше приложение
Compose application model
Спецификация Compose позволяет определить платформонезависимое контейнерное приложение. Такое приложение является набором контейнеров, которые должны работать совместно с адекватным разделением ресурсов и каналом коммуникации.
В Docker используется термин mount, который используется для обозначения привязки какого-то ресурса, например папки с файлами, к контейнеру. Я не нашел красивого русского обозначения, поэтому буду употреблять слово “связан”. То есть это не абстрактная связь, а конкретная связь контейнера и каких-то данных вне контейнера. Учитывайте это по ходу видео.
Компоненты приложения называются сервисами.
service
- абстракция и по сути просто конфигурация того, какой конкретный image и с какими настройками должен быть запущен. То есть запуская сервис много раз вы должны получить один и тот же результат.
Сервисы взаимодействуют между собой через сеть (network
). В Compose network - это еще одна абстракция, которая позволяет устанавливать IP соединения между контейнерами, внутри которого сервисы могут коммуницировать друг с другом.
Сервисы хранят и разделяют между собой данные в volumes
. Мы говорили немного о volumes в прошлой статье о Dockerfile. Если не смотрели его, то посмотрите, там много интересного и подробного. Спецификация compose описывает то какие данные и где должны храниться.
Некоторые сервисы требуют конфигурационные данные для работы, поэтому есть отдельный концепт - config
. С точки зрения контейнера configs похожи на volumes - так как это тоже файлы, которые могут быть связаны с контейнером и читаться им.
secret
- тип конфигурационных данных для чувствительных данных. Чувствительные данные - это пароли, ключи и все то, что не должно попасть в чужие руки. Секреты так же могут быть связаны с контейнером.
project
- это индивидуальное размещение (деплой) приложения на конкретной платформе. То есть тот docker compose файл, который вы напишите - это отдельный проект. Имя проекта используется для того, чтобы группировать ресурсы вместе и изолировать их от других приложений или других таких же запущенных проектов.
Давайте рассмотрим пример приложения. Сначала он может показаться сложным, однако к концу статьи вы с легкостью сможете его прочитать и разобраться.
Это пример приложения с фронтендом и бэкендом.
frontend
сконфигурирован в рантайме при помощи HTTP Configuration файла, а также HTTPS сертификатом, который встроен в secret store приложения - то место, где хранятся секреты.
backend
хранит данные в постоянном хранилище. Оба сервиса взаимодействуют между собой при помощи изолированной backend сети, фронтенд также состоит во фронтенд сети, открывая https порт 443 наружу.
Это приложение состоит из следующих частей:
2 сервиса -
frontend
с imagewebapp
иbackend
с imagedatabase
1 секрета - HTTPS-сертификата, включенных во
frontend
1 конфигурации, включенной во
frontend
1 постоянного хранилища - volume, связанного с
backend
2 сетей -
frontend
иbackend
сети
Так описан 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:
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
services:
foo:
image: busybox
command: echo "I'm running ${COMPOSE_PROJECT_NAME}"
Service
Сервис - это абстрактное определение какого-то ресурса, который используется для работы всего приложения. Ваше приложение состоит из нескольких ресурсов - например, бэкэнд, фронтенд и база данных. Вот каждый этот ресурс это отдельный сервис.
Сервисы представляют собой контейнеры, то есть запущенные image, и набор аргументов, поясняющих то, как этот контейнер должен работать.
В Compose файле должен быть верхнеуровневый элемент services
, который состоит из пар ключ: значение
, где ключ - это название сервиса и значение - его атрибуты.
Вы можете использовать как существующий image, который будет пулиться из какого-нибудь registry, так и собирать image при помощи Dockerfile, указав к нему путь.
Оригинальная документация состоит из описания вообще всех аргументов. В этой статье я расскажу лишь о части из них, которые используются повсеместно.
Атрибут build
- описывает путь к Dockerfile из которого будет собран image, а также параметры для этого Dockerfile, например, контекст.
services:
frontend:
image: example/webapp
build: ./webapp
backend:
image: example/database
build:
context: backend
dockerfile: ../backend.Dockerfile
В этом примере сервисы frontend
и backend
собираются из Dockerfile, который находится соответственно в ./webapp
и ../backend.Dockerfile
command
- переопределяет инструкцию CMD
, которая указана в Dockerfile этого сервиса, таким образом именно эта команда будет использоваться при запуске контейнера, а не та, что была указана в Dockerfile. Этот атрибут принимает как shell, так и exec форму команды, как и инструкция CMD
.
configs
- позволяет сервису использовать конфигурации, про которые мы говорили раньше в этом видео.
services:
redis:
image: redis:latest
configs:
- my_config
- my_other_config
configs:
my_config:
file: ./my_config.txt
my_other_config:
external: true
container_name
определяет имя для контейнера, который представлен этим сервисом. Этот атрибут принимает строку - название контейнера.
depends_on
говорит о том, в каком порядке должны быть запущены контейнеры сервисов.
services:
web:
build: .
depends_on:
- db
- redis
redis:
image: redis
db:
image: postgres
Здесь сервис web
зависит от базы даных и от Redis, контейнер web
будет запущен только после того, как будут запущены контейнеры Redis и базы данных. Это нужно для того, чтобы ваше приложение не упало, попытавшись подключиться к еще не поднятому контейнеру, от которого оно зависит. Как и в локальной разработке, если вы пользуетесь базой, то она должна быть запущена до того, как вы запустите приложение.
entrypoint
переопределяет ENTRYPOINT
, который будет вызван при запуске контейнера. В прошлой статье мы узнали, что в Dockerfile выполняется только последняя инструкция ENTRYPOINT
, но здесь мы ее переопределяем и можем задать новое поведение при запуске контейнера.
entrypoint:
- php
- -d
- zend_extension=/usr/local/lib/php/extensions/no-debug-non-zts-20100525/xdebug.so
- -d
- memory_limit=-1
- vendor/bin/phpunit
env_file
указывает путь к файлу с переменными, которые будут использоваться для работы контейнера. Чтобы не указывать все переменные, вы можете поместить их в один файл и сослаться на него. Вы можете передать как путь к отдельному файлу, так и список таких путей. По умолчанию это файл .env
.
env_file:
- ./a.env
- ./b.env
environment
определяет пары ключ: значение
для переменных, которые нужны при работе контейнера. Вы можете использовать два разных синтаксиса - map
и list
.
environment:
RACK_ENV: development
SHOW: "true"
USER_INPUT:
environment:
- RACK_ENV=development
- SHOW=true
- USER_INPUT
Если вы указали и env_file
и env
переменные, тогда переменные из env
имеют приоритет и будут перезаписывать переменные с таким же названием из env
файла.
image
определяет image, который будет запущен в контейнере. Вы можете указать просто название, но можете и дополнить его тегом или дайджестом. В сервисе должен быть указан либо image
либо build
атрибут, для того, чтобы Docker смог понять что должно быть запущено.
image: redis
image: redis:5
image: redis@sha256:0ed5d5928d4737458944eb604cc8509e245c3e19d02ad83935398bc4b991aac7
image: library/redis
image: docker.io/library/redis
image: my_private.registry:5000/redis
networks
- указывает список network
, к которым принадлежит сервис.
services:
some-service:
networks:
- some-network
- other-network
В этом примере сервис some-service
принадлежит к двум сетям, которые были описаны в Compose файле.
ports
определяет порты, которые отображаются наружу из контейнера. Мы говорили в прошлой статье, что EXPOSE
в Dockerfile не открывает порты, а лишь говорит разработчикам о том, что этот порт нужен для работы приложения. А вот в Compose файле ports
как раз и открывает порты наружу.
ports:
- "3000"
- "3000-3005"
- "8000:8000"
- "9090-9091:8080-8081"
- "49100:22"
- "8000-9000:80"
- "6060:6060/udp"
Таким образом обратившись к localhost:3000 вы попадете внутрь контейнера на порт 3000. Вы можете поменять порт и обращаться к другому локальному порту, который в свою очередь будет отображен в контейнер. Порты на локальной машине должны быть доступны, иначе Compose файл не будет запущен.
pull_policy
говорит о том, должен ли image пулиться из registry при каждом запуске контейнера, чтобы был запущен именно актуальный image.
Возможные значения:
always
- image всегда пулятсяnever
- не пулятся никогда и должны быть на локальной машине, чтобы Compose файл мог ими воспользоватьсяmissing
- Docker пулит только недостающие imagebuild
- Docker будет создавать image из Dockerfile каждый раз
restart
- определяет правила, по которым контейнер будет перезапускаться, когда он упал.
Есть несколько опций:
no
- по умолчанию. Docker не перезапускает контейнер если он упалalways
- Docker всегда перезапускает контейнер, пока он не будет удаленon-failure
- Docker перезапускает контейнер, если он упал из-за ошибки, а не был остановлен правильноunless-stopped
- Docker перезапускает контейнер, пока сервис не будет остановлен или удален
restart: "no"
restart: always
restart: on-failure
restart: unless-stopped
secrets
- позволяет сервису обратиться к секретам, которые были указаны в текущем Compose файле.
services:
frontend:
image: example/webapp
secrets:
- server-certificate
secrets:
server-certificate:
file: ./server.cert
volumes
определяет путь хранения volume из контейнера на локальном хосте. Вы можете создать volume в какой-нибудь директории локально или же сослаться на описанный volume из текущего Compose файла.
services:
backend:
image: example/backend
volumes:
- type: volume
source: db-data
target: /data
volume:
nocopy: true
- type: bind
source: /var/run/postgres/postgres.sock
target: /var/run.postgres/postgres.sock
volumes:
db-data:
Network
Networks или сети позволяют сервисам общаться между собой. Поместив несколько сервисов в один network, они могут свободно обращаться друг к другу по названию сервиса. Это удобно и быстро конфигурируется, что делает деплой приложения в Docker простым.
В сервисе мы рассмотрели атрибут networks
, в котором указывается список сетей, к которым принадлежит сервис.
Сейчас на примерах мы рассмотрим то, как настроить сеть и какие атрибуты у нее есть.
services:
frontend:
image: example/webapp
networks:
- front-tier
- back-tier
networks:
front-tier:
back-tier:
В этом простом примере создается сервис и две сети, к обеим из которых он принадлежит. Когда сервис один, то ему сети и не нужны, так как ему не с кем общаться.
services:
proxy:
build: ./proxy
networks:
- frontend
app:
build: ./app
networks:
- frontend
- backend
db:
image: postgres
networks:
- backend
networks:
frontend:
driver: custom-driver-1
backend:
driver: custom-driver-2
driver_opts:
foo: "1"
bar: "2"
В этом более сложном примере есть три сервиса - прокси, приложение и база данных. Этот пример напоминает тот, что мы рассмотрели в начале видео.
Прокси сервис изолирован от базы, так как они находятся в разных сетях. Но прокси может обратиться к сервису приложения.
Приложение может обратиться как к прокси, так и к базе данных, потому что принадлежит к обеим сетям, в которых находятся эти сервисы.
Также у этих сетей есть свои настройки - драйверы и их опции. Эти атрибуты необязательны, однако помогают настроить сеть.
Атрибут driver
указывает на то, какой драйвер для работы сети следует использовать. Будет выброшена ошибка, если драйвер недоступен для использования при запуске Compose файла.
Есть два типа драйверов, которые поддерживаются Compose: host
и none
. host
использует возможности хоста, то есть того компьютера, на котором запущен Compose файл, для коммуникации между сервисами, none
отключает коммуникацию.
networks:
db-data:
driver: overlay
driver_opts
определяет список опций для драйвера. Про них можно узнать из документации конкретного драйвера.
networks:
db-data:
driver_opts:
foo: "bar"
baz: 1
Если атрибут attachable
установлен в true
, тогда контейнеры вне этого Compose файла могут подключиться к сети и общаться с сервисами внутри этой сети.
networks:
mynet1:
driver: overlay
attachable: true
Если атрибут external
установлен в true
, тогда Docker ожидает, что эта сеть уже создана и будет обращаться к ней, иначе будет выброшена ошибка при запуске Compose файла. Все остальные атрибуты не будут учитываться, кроме атрибута имени, а если они отличаются от настоящих, то будет выброшена ошибка.
services:
proxy:
image: example/proxy
networks:
- outside
- default
app:
image: example/app
networks:
- default
networks:
outside:
external: true
В данном примере Compose будет использовать уже существующую сеть outside
, а не пытаться создать новую.
Если атрибут internal
установлен в true
, тогда вы запретите внешнее подключение к этой сети снаружи Compose файла, так как по умолчанию Compose предоставляет возможность внешнего подключения.
Атрибут name
устанавливает имя для сети, как и с volume, вы можете использовать переменные для подстановки значений при запуске Compose файла.
networks:
network1:
external: true
name: "${NETWORK_ID}"
Если вы используете внешний network, тогда вы можете указать настоящее имя сети, а внутри текущего Compose файла обращаться к сети с другим именем.
Volume
Volumes - это постоянные хранилища данных вашего контейнера.
Когда Docker контейнер перезапускается, все его данные, которые не хранятся в volume очищаются. Так как контейнер изолирован от окружающей среды, логично, что он будет очищен при перезапуске. Для того, чтобы сохранить важные данные, например, состояние базы данных из контейнера, необходимо создать volume, который будет хранить эти данные на хосте, на котором работает контейнер.
Верхнеуровневая конфигурация volumes
позволяет описать volumes, которые будут использованы сервисами. Как мы заметили раньше, в сервисе нужно явно указывать то, какие данные будут сохраняться и куда.
Давайте рассмотрим несколько примеров конфигурации volumes.
services:
backend:
image: example/database
volumes:
- db-data:/etc/data
backup:
image: backup-service
volumes:
- db-data:/var/lib/backup/data
volumes:
db-data:
На этом примере мы видим два сервиса, один из которых база данных, а второй - бэкап сервис, делающий бэкапы базы данных. Оба этих сервиса имеют доступ к данным из базы данных благодаря volume.
Сервис базы данных сохраняет данные в volume db-data
, а сервис бэкапа сохраняет эти данные в своем контейнере по пути /etc/data
. Таким образом данные из одного контейнера попадают во второй.
У конфигурации volume есть несколько атрибутов, но они не являются обязательными.
driver
указывает какой драйвер для volume должен быть использован. Некоторые могут быть платформозависимыми. Если драйвер недоступен, то Docker выбросит ошибку при попытке запуске Compose файла.
volumes:
db-data:
driver: foobar
driver_opts
определяет опции volume, пар ключ: значение
, они тоже платформозависимые, то есть на Windows и на Linux эти опции могут отличаться.
volumes:
example:
driver_opts:
type: "nfs"
o: "addr=10.40.0.199,nolock,soft,rw"
device: ":/docker/example"
Если атрибут external
установлен в true
, тогда Docker ожидает, что этот volume уже существует и управляется из другого Compose файла. Если такого volume не будет, то выбросится ошибка при запуске Compose файла. Также в этом случае все атрибуты кроме имени не будут учитываться, раз volume был создан и управляется из другого места. Но если эти атрибуты отличаются от оригинальных, то будет выброшена ошибка. Таким образом определяя external как true
, вы можете указать только имя volume для этого Compose файла.
services:
backend:
image: example/database
volumes:
- db-data:/etc/data
volumes:
db-data:
external: true
На этом примере volume не создается, а ищется уже существующий.
name
устанавливает имя для volume. Вы можете воспользоваться подстановкой переменных при запуске Compose файла. В данном случае имя volume будет получено из переменной DATABASE_VOLUME
из .env
файла.
volumes:
db-data:
name: ${DATABASE_VOLUME}
volumes:
db-data:
external:
name: actual-name-of-volume
В этом примере внешний volume имеет имя actual-name-of-volume
, а внутри текущего Compose файла вы можете обратиться по имени db-data
.
Config
Config позволяет сервисам изменять их поведение без необходимости изменения используемых image.
Сервисы могут обратиться к конфигам только если имеют атрибут configs
.
Как и volume, конфиги связаны с файловой системой контейнера. По умолчанию они находятся в корне файловой системы контейнера.
В конфиге указывается информация, которую могут использовать сервисы в Compose приложении. Источником конфига может быть файл или внешний источник.
file
- создается конфиг со всем содержимым файлаenvironment
- создается конфиг со значением переменнойcontent
- создается конфиг со значением, которое было передано в этом атрибуте
external
- Если указано как true
, Docker ожидает, что конфиг был создан, он не пытается его создать заново и выбрасывает ошибку, если такого конфига не существует.
name
- имя конфига, которое Docker ищет.
configs:
http_config:
file: ./httpd.conf
В этом примере создается config, значением которого будет содержимое файла. Имя для него будет <project_name>_http_config
.
configs:
app_config:
content: |
debug=${DEBUG}
spring.application.admin.enabled=${DEBUG}
spring.application.name=${COMPOSE_PROJECT_NAME}
В этом случае создается конфиг, содержимым которого является указанный текст.
Secret
Секреты используются для использования чувствительных данных. Сервисы могут обратиться к секретам только если имеют атрибут secrets
.
На верхнем уровне элемент secrets
определяет или ссылается на те самые чувствительные данные, источником которых служит либо file
либо environment
.
Если источник
file
, тогда создается секрет с содержимым этого файла.Если источник
environment
, тогда создается секрет со значением этой переменной.
external
обозначает то, что секрет уже был создан и Docker не пытается его создать. Если он не был создан, тогда вы получите ошибку при выполнении Compose файла.
name
- название секрета в Docker
Давайте рассмотрим несколько примеров.
secrets:
server-certificate:
file: ./server.cert
В этом примере создается секрет с названием <project_name>_server-certificate
в момент запуска Compose файла, а значение секрета берется из файла server.cert
.
secrets:
token:
environment: "OAUTH_TOKEN"
В этом случае секрет называется как <project_name>_token
и создается во время запуска Compose файла, значением является значение переменной.
secrets:
server-certificate:
external: true
В таком случае Docker пытается найти уже созданный секрет и использовать его.
Fragments
В Compose файле можно использовать встроенные возможности языка YAML, которые сделают Compose файл более аккуратным и эффективным.
Вы можете создать фрагменты для того, чтобы переиспользовать некоторые части файла. Например, если у вас есть несколько сервисов с одинаковой конфигурацией, вы можете объединить эту конфигурацию в одну и не дублировать ее повторно.
Фрагменты создаются при помощи символы амперсанд &
и имени фрагмента. Они устанавливаются на ключи YAML файла, раз этот формат - это ключ-значение, то подстановки происходят после двоеточия. С помощью *
и имени можно обратиться к этому фрагменту и получить его значение.
Фрагменты подставляются до подстановки переменных, поэтому здесь их нельзя использовать для создания названий.
volumes:
db-data: &default-volume
driver: default
metrics: *default-volume
В данном примере создается фрагмент default-volume
, который подставит все свойства volume db-data
в volume metrics
, то есть metrics
также будет иметь свойство driver: default
.
services:
first:
image: my-image:latest
environment: &env
- CONFIG_KEY
- EXAMPLE_KEY
- DEMO_VAR
second:
image: another-image:latest
environment: *env
В этом примере переменные одного сервиса помечаются якорем, чтобы быть переиспользоваными во втором сервисе.
Иногда вы можете хотеть переопределить значения фрагмента только частично, при помощи YAML merge вы можете это сделать как в данном примере.
services:
backend:
image: example/database
volumes:
- db-data
- metrics
volumes:
db-data: &default-volume
driver: default
name: "data"
metrics:
<<: *default-volume
name: "metrics"
В данном примере default-volume
подставит только driver
, а name
подставлен не будет, так как мы его явно указали.
Вы также можете дополнить значения фрагмента дополнительными элементами как в этом примере.
services:
first:
image: my-image:latest
environment: &env
FOO: BAR
ZOT: QUIX
second:
image: another-image:latest
environment:
<<: *env
YET_ANOTHER: VARIABLE
Но в таком случае вы должны указывать переменные в формате Ключ: Значение
, а не - Ключ=Значение
.
Если вы хотите использовать фрагменты в нескольких файлах, тогда воспользуйтесь расширениями Extension.
Extensions
Как и фрагменты, Extensions могут быть использованы для того, чтобы сделать Compose файл более эффективным и поддерживаемым.
Для этого на верхнем уровне нужно определить элемент, который начинается с x-
. Так Docker поймет, что это расширение и будет знать о нем.
Давайте рассмотрим несколько примеров:
x-env: &env
environment:
- CONFIG_KEY
- EXAMPLE_KEY
services:
first:
<<: *env
image: my-image:latest
second:
<<: *env
image: another-image:latest
В первом примере определен extension x-custom
и x-foo
, мы можем добавить x-foo
в сервис и Docker его обработает, но ничего не сделает, однако значение x-foo
можно получить в самом приложении webapp
.
Во втором примере env не принадлежит ни одному из сервисов. Такой extension определяет новую ноду, в которой содержится поле environment
. А якорь используется для того, чтобы обратиться к этому полю в двух сервисах.
x-environment: &default-environment
FOO: BAR
ZOT: QUIX
x-keys: &keys
KEY: VALUE
services:
frontend:
image: example/webapp
environment:
<< : [*default-environment, *keys]
YET_ANOTHER: VARIABLE
В этом примере можно использовать объединение из нескольких расширений, тогда все ключи из обоих расширений будут подставлены. Как и в случае с фрагментами, вы должны указывать переменные в формате Ключ: Значение
, а не - Ключ=Значение
.
Interpolation
Как и в Dockerfile, мы можем воспользоваться wildcard для подстановки значений переменных из конфигурационного файла.
Значение переменной можно получить написав ${VARIABLE}
или $VARIABLE
. Можно обратиться к вложенным переменным, например как здесь.
${VARIABLE:-${FOO}}
${VARIABLE?$FOO}
${VARIABLE:-${FOO:-default}}
Есть несколько видов подстановки.
${VARIABLE:-default} - подставит default, если переменная VARIABLE не задана или пустая
${VARIABLE-default} - подставит default только если переменная VARIABLE не задана
${VARIABLE:?err} - завершит программу с сообщением об ошибке err если переменная VARIABLE не задана или пустая
${VARIABLE?err} - завершит программу с сообщением об ошибке err только если переменная VARIABLE не задана
Если Docker не может найти переменную и нет значения по умолчанию, тогда выводится предупреждение и используется пустая строка. Стоит помнить об этом.
Если используется слияние нескольких Compose файлов, тогда подстановка происходит до слияния, чтобы не допустить ошибок.
Подстановки работают только для значений, а не ключей, поэтому такой пример не будет работать.
services:
foo:
labels:
"$VAR_NOT_INTERPOLATED_BY_COMPOSE": "BAR"
Merge
Вы можете использовать несколько compose файлов и объединить их в один, чтобы собрать свое приложение.
Давайте рассмотрим правила, которые соблюдает merge.
Если мержутся мапы, то есть пары ключ-значение, то недостающие ключи добавляются, а существующие перезаписываются.
#первый файл
services:
foo:
key1: value1
key2: value2
#второй файл
services:
foo:
key2: VALUE
key3: value3
#результат
services:
foo:
key1: value1
key2: VALUE
key3: value3
Если мержутся последовательности, тогда они дополняются.
#первый файл
services:
foo:
DNS:
- 1.1.1.1
#второй файл
services:
foo:
DNS:
- 8.8.8.8
#результат
services:
foo:
DNS:
- 1.1.1.1
- 8.8.8.8
COMMAND
, ENTRYPOINT
, HEALTHCHECK
перезаписываются, так как мы помним, что они не могут дублироваться в Dockerfile, то и здесь они не могут использоваться совместно, а только последняя инструкция будет применена.
#первый файл
services:
foo:
command: ["echo", "foo"]
#второй файл
services:
foo:
command: ["echo", "bar"]
#результат
services:
foo:
command: ["echo", "bar"]
В случае с уникальными ресурсами, такими как порты, volume, секреты и конфигурации если уникальные значения конфликтуют, тогда они будут перезаписаны.
#первый файл
services:
foo:
volumes:
- foo:/work
#второй файл
services:
foo:
volumes:
- bar:/work
#результат
services:
foo:
volumes:
- bar:/work
В качестве уникальных ресурсов выступают:
Include
Вы можете сослаться на другой Compose файл если вы хотите:
Переиспользовать Compose файлы,
Вы хотите вынести общие части в отдельные compose файлы, которые должны управляться независимо друг от друга
Вам нужно, чтобы Compose файл был не большого размера или включал в себя ресурсы определенной части большого приложения
Для того, чтобы подключить другой файл, вы должны указать на верхнем уровне секцию include
. Как только compose файлы из этой секции загрузились, они копируются в данный проект. Вы увидите предупреждение, если файлы конфликтуют между собой и Docker не будет сам мержить их.
Вы можете ссылаться на ресурсы из других Compose файлов, как будто эти ресурсы есть в вашем файле. Как на этом примере.
include:
- my-compose-include.yaml #здесь описан serviceB
services:
serviceA:
build: .
depends_on:
- serviceB #используем этот сервис как будто он описан прямо в этом файле
Вы также можете использовать переменные в include
для указания относительного пути папок, например для сборки приложения для разработки и production.
include:
- ${INCLUDE_PATH:?FOO}/compose.yaml
Можно использовать длинный синтаксис и более детально сконфигурировать include
.
include:
- path: ../commons/compose.yaml
project_directory: ..
env_file: ../another/.env
В данном случае path
- обязательный элемент и он определяет расположение compose файла относительно текущего. Это может быть как одна строка, так и список строк, если нужно подключить несколько Compose файлов.
project_directory
определяет путь, относительно которого относительные пути из path будут применены. По умолчанию это папка с текущим Compose файлом.
env_file
указывает путь к файлу с переменными, которые будут использоваться в указанных Compose файлах. По умолчанию это .env
файл в директории project_directory
. Тут так же можно указать как строку так и список строк. Переменные в локальной директории, то есть в той, в которой находится текущий Compose файл имеют преимущество перед импортируемыми и могут их переписать.
yarkov
За код картинками отдельное вам спасибо
ilyalisov Автор
В ближайшее время перепишу на текст!)
vektory79
Не просто текст, а оформить как код. Чтобы глаза при чтении не ломать.
Но за статьи спасибо. Как раз думал как подступиться, чтобы сыновьям докер объяснить :)