1. Обзор
При интенсивном использовании Docker управление несколькими разными контейнерами быстро становится громоздким.
Docker Compose — это инструмент, который помогает нам преодолеть эту проблему и легко обрабатывать несколько контейнеров одновременно.
В этом уроке мы рассмотрим его основные функции и мощные механизмы.
2. Объяснение конфигурации YAML
Короче говоря, Docker Compose работает, применяя множество правил, объявленных в одном конфигурационном файле docker-compose.yml
.
Эти правила YAML , как человекочитаемые, так и машинно-оптимизированные, дают нам эффективный способ запечатлеть весь проект с десяти тысяч футов в несколько строк.
Почти каждое правило заменяет определенную команду Docker, так что в итоге нам просто нужно запустить:
docker-compose up
Мы можем получить десятки конфигураций, применяемых Compose под капотом. Это избавит нас от необходимости писать сценарии с помощью Bash или чего-то еще.
В этом файле нам нужно указать версию
формата файла Compose, как минимум один сервис
и опционально тома
и сети
:
version: "3.7"
services:
...
volumes:
...
networks:
...
Давайте посмотрим, что это за элементы на самом деле.
2.1. Услуги
В первую очередь сервисы
относятся к конфигурации контейнеров .
Например, возьмем докеризованное веб-приложение, состоящее из внешнего интерфейса, внутреннего интерфейса и базы данных: мы, вероятно, разделим эти компоненты на три образа и определим их как три разных сервиса в конфигурации:
services:
frontend:
image: my-vue-app
...
backend:
image: my-springboot-app
...
db:
image: postgres
...
Есть несколько настроек, которые мы можем применить к службам, и мы подробно рассмотрим их позже.
2.2. Объемы и сети
`С другой стороны,
тома — это физические области дискового пространства, разделяемые между хостом и контейнером или даже между контейнерами.` Другими словами, том — это общий каталог на хосте , видимый из некоторых или всех контейнеров.
Точно так же сети
определяют правила связи между контейнерами и между контейнером и хостом . Общие сетевые зоны сделают службы контейнеров доступными друг для друга, а частные зоны разделят их в виртуальных песочницах.
Опять же, мы узнаем о них больше в следующем разделе.
3. Анализ сервиса
Теперь приступим к осмотру основных настроек службы.
3.1. Вытягивание изображения
Иногда образ, который нам нужен для нашего сервиса, уже был опубликован (нами или кем-то еще) в Docker Hub или другом Docker Registry.
Если это так, то мы обращаемся к нему с помощью атрибута изображения
, указав имя изображения и тег:
services:
my-service:
image: ubuntu:latest
...
3.2. Создание образа
Вместо этого нам может понадобиться создать образ из исходного кода, прочитав его Dockerfile
.
На этот раз мы будем использовать ключевое слово build
, передав путь к Dockerfile в качестве значения:
services:
my-custom-app:
build: /path/to/dockerfile/
...
Мы также можем использовать URL вместо пути:
services:
my-custom-app:
build: https://github.com/my-company/my-project.git
...
Кроме того, мы можем указать имя образа
в сочетании с атрибутом сборки
, который будет называть изображение после создания, делая его доступным для использования другими службами :
services:
my-custom-app:
build: https://github.com/my-company/my-project.git
image: my-project-image
...
3.3. Настройка сети
Контейнеры Docker взаимодействуют между собой в сетях, созданных неявно или посредством конфигурации с помощью Docker Compose . Служба может взаимодействовать с другой службой в той же сети, просто ссылаясь на нее по имени контейнера и порту (например , network-example-service:80
), при условии, что мы сделали порт доступным через ключевое слово expose
:
services:
network-example-service:
image: karthequian/helloworld:latest
expose:
- "80"
В этом случае, кстати, тоже работало бы без экспонирования, потому что директива expose
уже есть в образе Dockerfile .
Чтобы получить доступ к контейнеру с хоста , порты должны быть представлены декларативно с помощью ключевого слова ports
, что также позволяет нам выбирать, следует ли предоставлять порт на хосте по-разному:
services:
network-example-service:
image: karthequian/helloworld:latest
ports:
- "80:80"
...
my-custom-app:
image: myapp:latest
ports:
- "8080:3000"
...
my-custom-app-replica:
image: myapp:latest
ports:
- "8081:3000"
...
Порт 80 теперь будет виден с хоста, а порт 3000 двух других контейнеров будет доступен через порты 8080 и 8081 на хосте. Этот мощный механизм позволяет запускать разные контейнеры, открывающие одни и те же порты, без конфликтов .
Наконец, мы можем определить дополнительные виртуальные сети для разделения наших контейнеров:
services:
network-example-service:
image: karthequian/helloworld:latest
networks:
- my-shared-network
...
another-service-in-the-same-network:
image: alpine:latest
networks:
- my-shared-network
...
another-service-in-its-own-network:
image: alpine:latest
networks:
- my-private-network
...
networks:
my-shared-network: {}
my-private-network: {}
В этом последнем примере мы видим, что другая служба в той же сети
сможет пропинговать и получить доступ к порту 80 network-example-service
, в то время как другая служба в своей собственной сети
выиграла . 'т.
3.4. Настройка томов
Существует три типа томов: анонимные
, именованные
и хостовые
.
Docker управляет как анонимными, так и именованными томами , автоматически монтируя их в самостоятельно созданные каталоги на хосте. В то время как анонимные тома были полезны в более старых версиях Docker (до 1.9), в настоящее время рекомендуется использовать именованные тома. Тома хоста также позволяют нам указать существующую папку на хосте.
Мы можем настроить хост-тома на уровне службы и именованные тома на внешнем уровне конфигурации, чтобы сделать последние видимыми для других контейнеров, а не только для того, которому они принадлежат:
services:
volumes-example-service:
image: alpine:latest
volumes:
- my-named-global-volume:/my-volumes/named-global-volume
- /tmp:/my-volumes/host-volume
- /home:/my-volumes/readonly-host-volume:ro
...
another-volumes-example-service:
image: alpine:latest
volumes:
- my-named-global-volume:/another-path/the-same-named-global-volume
...
volumes:
my-named-global-volume:
Здесь оба контейнера будут иметь доступ для чтения/записи к общей папке my-named-global-volume
, независимо от того, на какие пути они ее сопоставили. Вместо этого два хост-тома будут доступны только для volumes-example-service
.
Папка /tmp
файловой системы хоста сопоставляется с папкой /my-volumes/host-volume
контейнера.
Эта часть файловой системы доступна для записи, что означает, что контейнер может не только читать, но и записывать (и удалять) файлы на хост-компьютере.
Мы можем смонтировать том в режиме только для чтения, добавив `` к правилу :ro
, например, для папки /home
(мы не хотим, чтобы контейнер Docker по ошибке удалял наших пользователей).
3.5. Объявление зависимостей
Часто нам нужно создать цепочку зависимостей между нашими сервисами, чтобы одни сервисы загружались раньше (и выгружались после) других. Мы можем добиться этого результата с помощью ключевого слова depend_on
:
services:
kafka:
image: wurstmeister/kafka:2.11-0.11.0.3
depends_on:
- zookeeper
...
zookeeper:
image: wurstmeister/zookeeper
...
Однако мы должны знать, что Compose не будет ждать завершения загрузки службы zookeeper перед запуском службы
kafka
: он просто будет ждать ее запуска. Если нам нужно, чтобы служба была полностью загружена перед запуском другой службы, нам нужно получить более глубокий контроль над порядком запуска и завершения работы в Compose .
4. Управление переменными среды
В Compose легко работать с переменными среды. Мы можем определить статические переменные среды, а также определить динамические переменные с помощью нотации ${} :
services:
database:
image: "postgres:${POSTGRES_VERSION}"
environment:
DB: mydb
USER: "${USER}"
Существуют разные способы предоставления этих значений для Compose.
Например, можно установить их в файле .env
в том же каталоге, структурированном как файл .properties ,
ключ=значение
:
POSTGRES_VERSION=alpine
USER=foo
В противном случае мы можем установить их в ОС перед вызовом команды:
export POSTGRES_VERSION=alpine
export USER=foo
docker-compose up
Наконец, нам может пригодиться простой однострочный код в оболочке:
POSTGRES_VERSION=alpine USER=foo docker-compose up
Мы можем смешивать подходы, но давайте помнить, что Compose использует следующий порядок приоритетов, перезаписывая менее важные более высокими:
- Создать файл
- Переменные среды оболочки
- Файл среды
- Докерфайл
- Переменная не определена
5. Масштабирование и реплики
В более старых версиях Compose нам разрешалось масштабировать экземпляры контейнера с помощью команды масштабирования docker-compose
. Более новые версии устарели и заменили его параметром –
–
масштабирования
.
С другой стороны, мы можем использовать Docker Swarm — кластер Docker Engines — и декларативно автомасштабировать наши контейнеры с помощью атрибута replicas в разделе
deploy
:
services:
worker:
image: dockersamples/examplevotingapp_worker
networks:
- frontend
- backend
deploy:
mode: replicated
replicas: 6
resources:
limits:
cpus: '0.50'
memory: 50M
reservations:
cpus: '0.25'
memory: 20M
...
При развертывании
мы также можем указать многие другие параметры, например пороговые значения ресурсов. Однако Compose рассматривает весь раздел deploy
только при развертывании в Swarm и игнорирует его в противном случае.
6. Реальный пример: Spring Cloud Data Flow
В то время как небольшие эксперименты помогают нам понять отдельные механизмы, наблюдение за реальным кодом в действии определенно раскроет общую картину.
Spring Cloud Data Flow — сложный проект, но достаточно простой, чтобы его можно было понять. Давайте загрузим его файл YAML и запустим:
DATAFLOW_VERSION=2.1.0.RELEASE SKIPPER_VERSION=2.0.2.RELEASE docker-compose up
Compose скачает, настроит и запустит каждый компонент, а затем объединит журналы контейнера в единый поток в текущем терминале .
Он также применит уникальные цвета к каждому из них для отличного взаимодействия с пользователем:
Мы можем получить следующую ошибку при запуске новой установки Docker Compose:
lookup registry-1.docker.io: no such host
Хотя существуют разные решения этой распространенной ловушки, использование 8.8.8.8
в качестве DNS, вероятно, является самым простым.
7. Управление жизненным циклом
Наконец, давайте подробнее рассмотрим синтаксис Docker Compose:
docker-compose [-f <arg>...] [options] [COMMAND] [ARGS...]
Хотя доступно множество опций и команд , нам нужно хотя бы знать, какие из них правильно активировать и деактивировать всю систему.
7.1. Запускать
Мы видели, что мы можем создавать и запускать контейнеры, сети и тома, определенные в конфигурации, с помощью up
:
docker-compose up
Однако после первого раза мы можем просто использовать start
для запуска служб:
docker-compose start
В случае, если имя нашего файла отличается от имени по умолчанию ( docker-compose.yml
), мы можем использовать флаги -f
и --file для
указания альтернативного имени файла
:
``
docker-compose -f custom-compose-file.yml start
Compose также может работать в фоновом режиме как демон при запуске с параметром -d
:
docker-compose up -d
7.2. Неисправность
Чтобы безопасно остановить активные службы, мы можем использовать команду stop
, которая сохранит контейнеры, тома и сети вместе со всеми внесенными в них изменениями:
docker-compose stop
Вместо этого для сброса статуса нашего проекта мы просто запускаем вниз
, что уничтожит все, кроме внешних томов :
docker-compose down
8. Заключение
В этом руководстве мы узнали о Docker Compose и о том, как он работает.
Как обычно, мы можем найти исходный файл docker-compose.yml
на GitHub , а также ряд полезных тестов, доступных на следующем изображении: