В новой статье от команды Amplicode, которая создаёт инструменты разработки web-приложений, я расскажу, как можно создать несколько Docker Compose файлов для разных нужд. Например, для продакшена и разработки, и при этом не утонуть в копипасте.
Статья также доступна в видеоформате для вашего удобства!
Итак, сегодня мы хотим получить два Docker Compose файла. Один будет полезен для продакшена и будет содержать три сервиса: наше Spring Boot приложение, PostgreSQL и Kafka, а второй будет использоваться во время разработки и точно так же будет включать в себя PostgreSQL и Kafka, а также инструменты для работы с ними, а именно pgAdmin и Kafka UI.
Docker Compose для продакшена!?
Отметим, что касательно использования Docker Compose в продакшене однозначного мнения нет. Одни считают, что Docker Compose не подходит для продакшена, и лучше использовать продвинутые системы оркестрации, такие как Kubernetes. Другие считают, что это вполне допустимо при определенных условиях. В общем, как часто бывает в программировании, ответ зависит от контекста.
Способы решения поставленной задачи
Возвращаясь к поставленной задаче: как вы можете заметить, Kafka и PostgreSQL должны присутствовать в обоих Docker Compose файлах, и есть несколько способов решить эту задачу.
Использование одного Docker Compose файла с профилями
Первый способ, которым мы можем решить поставленную задачу — это вообще отказаться от двух Docker Compose файлов, а создать только один, в котором описать все сервисы и использовать профили, чтобы указать, какие сервисы нужны во время разработки, а какие пригодятся только в продакшене.
#многие свойства сервисов не указаны для простоты восприятия
services:
spring-petclinic:
image: spring-petclinic:latest
profiles:
- prod
postgres:
image: postgres:16.3
profiles:
- prod
- dev
kafka:
image: confluentinc/cp-kafka:7.6.1
profiles:
- prod
- dev
kafkaui:
image: provectuslabs/kafka-ui:v0.7.2
profiles:
- dev
pgadmin:
image: dpage/pgadmin4:8.12.0
profiles:
- dev
Однако, этот вариант немного ограничен с точки зрения использования. Например, я не смогу указать для разных профилей, что для них должны использоваться разные порты или скрипты инициализации.
Создание отдельных Docker Compose файлов для каждого окружения
Второй способ, который я могу выбрать для решения этой задачи — это просто скопировать нужные мне сервисы в разные Docker Compose файлы. Однако, если придерживаться этого варианта, можно наткнуться на все типичные проблемы, которые связаны с копированием и вставкой содержимого, например, изменить версию сервиса в одном Docker Compose файле, но забыть сделать это в другом. И если первоначально у меня было всего лишь два Docker Compose файла, в дальнейшем их может неожиданно стать четыре, потом восемь, и так далее. Поддерживать все это в рабочем состоянии будет все сложнее и сложнее.
Использование include и extends для переиспользования сервисов
Наконец, есть третий способ, про который, возможно, не все знают. Этот способ подразумевает использование конструкций include и extends в Docker Compose для переиспользования сервисов.
Конструкция
include
позволяет включить один Docker Compose файл в другой, по сути предоставляя аналог запуска сразу нескольких Docker Compose файлов из терминала.
Файл
services.yaml
:
#многие свойства сервисов не указаны для простоты восприятия
services:
postgres:
image: postgres:16.3
kafka:
image: confluentinc/cp-kafka:7.6.1
Файл
app-compose.yaml
:
#многие свойства сервисов не указаны для простоты восприятия
include:
- services.yaml
services:
spring-petclinic:
image: spring-petclinic:latest
Этот подход действительно довольно удобен, если вам нужно просто переиспользовать одни и те же сервисы для разных Docker Compose файлов без какой‑либо дополнительной настройки.
И этот подход, в том числе, поддерживается Amplicode с точки зрения визуального отображения:
Но если вам нужно произвести тонкую настройку сервиса под свои нужды, то здесь больше подойдет ключевое слово
extends
.
С его помощью, точно так же, как и с помощью
include
, можно включить сервисы из другого Docker Compose файла в текущий. При этом у нас появляется возможность конфигурировать его свойства.
Файл
services.yaml
:
#многие свойства сервисов не указаны для простоты восприятия
services:
postgres:
image: postgres:16.3
kafka:
image: confluentinc/cp-kafka:7.6.1
Файл
app-compose.yaml
:
services:
spring-petclinic:
image: spring-petclinic:latest
postgres:
extends:
service: postgres
file: services.yaml
kafka:
extends:
service: kafka
file: services.yaml
#так как это расширение указанного сервиса,
#мы можем доконфигурировать его свойства
ports:
- "9092:9092"
В данной ситуации вариант с extends подойдет лучше всего. Используя этот вариант, можно избежать дублирования кода и при этом сохранить гибкость конфигурирования. Кстати, схожий подход использует и JHipster, генератор Spring Boot приложений. Отличный доклад про этот генератор сделал Илья Кучмин на последнем JPoint. Рекомендую его посмотреть, доклад получился очень интересный:
Решим задачу с extends и include
По легенде мы занимаемся доработкой существующего приложения, в котором уже был реализован следующий Docker Compose файл:
services:
spring-petclinic:
image: spring-petclinic:latest
build:
context: .
args:
DOCKER_BUILDKIT: 1
restart: always
ports:
- "8080:8080"
environment:
POSTGRES_HOST: postgres
POSTGRES_DB: spring-petclinic
POSTGRES_USER: root
POSTGRES_PASSWORD: root
KAFKA_BOOTSTRAP_SERVERS: kafka:29092
healthcheck:
test: wget --no-verbose --tries=1 --spider http://localhost:8080/actuator/health || exit 1
interval: 30s
timeout: 5s
start_period: 30s
retries: 5
depends_on:
- postgres
postgres:
image: postgres:16.3
restart: always
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
environment:
POSTGRES_USER: root
POSTGRES_PASSWORD: root
POSTGRES_DB: spring-petclinic
healthcheck:
test: pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB
interval: 10s
timeout: 5s
start_period: 10s
retries: 5
kafka:
image: confluentinc/cp-kafka:7.6.1
restart: always
ports:
- "29092:29092"
- "9092:9092"
volumes:
- kafka_data:/var/lib/kafka/data
environment:
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:29092,PLAINTEXT_HOST://localhost:9092
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT,CONTROLLER:PLAINTEXT
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT
KAFKA_NODE_ID: 1
CLUSTER_ID: 8GyRIS62T8aMSkDJs-AH5Q
KAFKA_PROCESS_ROLES: controller,broker
KAFKA_CONTROLLER_QUORUM_VOTERS: 1@kafka:9093
KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
KAFKA_LISTENERS: PLAINTEXT://kafka:29092,PLAINTEXT_HOST://0.0.0.0:9092,CONTROLLER://kafka:9093
healthcheck:
test: kafka-topics --bootstrap-server localhost:9092 --list
interval: 10s
timeout: 5s
start_period: 30s
retries: 5
volumes:
postgres_data:
kafka_data:
Docker Compose файл для переиспользуемых сервисов
По сути, этот файл мы можем использовать для продакшена, с небольшими модификациями. Давайте их выполним. Сначала переименуем его в
compose.prod.yaml
:
Далее, скопируем сервисы Kafka и PostgreSQL в новый Docker Compose файл с сервисами. Именно этот файл мы и будем в дальнейшем переиспользовать в других docker compose файлах, предназначенных для разных целей (продакшена, разработки, тестового окружения и т. д.). Для этого в панели Amplicode Explorer веберем Docker → New → Docker Compose File и зададим ему название
services.yaml
.
Так как все настройки, которые я укажу в этом файле, будут использоваться и в других местах, в которых я буду расширять сервисы из этого Docker Compose файла, те значения, которые, вероятнее всего, будут отличаться, отсюда лучше удалить. Поэтому для PostgreSQL удалим переменные окружения:
Также удалим открытый для внешнего подключения порт, так как если оставить его в этом сервисе и затем переопределять его в других Docker Compose файлах, то он все равно не будет перезатерт. Значения номеров портов будут объединяться из двух Docker Compose файлов, и в результате PostgreSQL будет доступен на двух портах.
А в случае с продакшеном я бы вообще не хотел, чтобы какой‑либо сервис, помимо Spring Boot приложения, открывал для внешнего подключения свои порты.
Для Kafka удалю только порты, переменные окружения оставлю, так как они будут одинаковые для двух окружений.
Файл
services.yaml
готов:
services:
postgres:
image: postgres:16.3
restart: always
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB
interval: 10s
timeout: 5s
start_period: 10s
retries: 5
kafka:
image: confluentinc/cp-kafka:7.6.1
restart: always
volumes:
- kafka_data:/var/lib/kafka/data
environment:
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:29092,PLAINTEXT_HOST://localhost:9092
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT,CONTROLLER:PLAINTEXT
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT
KAFKA_NODE_ID: 1
CLUSTER_ID: zNOJ9oWQQWCJqtCat68MLQ
KAFKA_PROCESS_ROLES: controller,broker
KAFKA_CONTROLLER_QUORUM_VOTERS: 1@kafka:9093
KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
KAFKA_LISTENERS: PLAINTEXT://kafka:29092,PLAINTEXT_HOST://0.0.0.0:9092,CONTROLLER://kafka:9093
healthcheck:
test: kafka-topics --bootstrap-server localhost:9092 --list
interval: 10s
timeout: 5s
start_period: 30s
retries: 5
volumes:
kafka_data:
postgres_data:
Docker Compose файл для продакшена
Теперь мне нужно вернуть файл для продакшена в то состояние, в котором он был. А именно, добавить сервисы для PostgreSQL и Kafka. Для этого вызову меню Generate и в нем найду действие Extend Existing Service от Amplicode:
Выберу сервис, имя оставлю без изменений.
В результате сервис в файле
compose.prod.yaml
будет выглядеть следующим образом:
Для корректной работы приложения сразу после запуска мне нужны некоторые таблицы в базе данных и записи в них. Я могу проинициализировать базу, указав путь к директории, в которой лежат скрипты инициализации. Чтобы сделать это не покидая IDE и не ошибиться с написанием пути можно воспользоваться панелью Amplicode Designer и секцией Init scripts. Используя ее, укажу путь до директории со скриптами в строке Source:
Так как указывать чувствительную информацию в открытом виде в Docker Compose файлах – это довольно грубое нарушение общепринятых политик безопасности, давайте воспользуемся
.env
файлом. Начнём набирать
env_file
и Amplicode предложит нам code completion не только для атрибута сервиса:
Но и для названия файла, расположенного в проекте, с расширением
.env
:
Что самое удобное, так это то, что Amplicode также отобразит все данные из него в соответствующих секциях в панели Amplicode Designer:
Также не забудем удалить переменные окружения из сервиса с нашим Spring Boot приложением:
И укажем
.env
файл:
Остается добавить Kafka, для которой никаких дополнительных телодвижений тут делать не придется. Аналогичным образом вызовем меню Generate и расширим сервис Kafka:
В итоге файл
compose.prod.yaml
теперь выглядит следующим образом:
services:
spring-petclinic:
image: spring-petclinic:latest
build:
context: .
args:
DOCKER_BUILDKIT: 1
restart: always
ports:
- "8080:8080"
env_file:
- prod.env
healthcheck:
test: wget --no-verbose --tries=1 --spider http://localhost:8080/actuator/health || exit 1
interval: 30s
timeout: 5s
start_period: 30s
retries: 5
depends_on:
- postgres
postgres:
extends:
service: postgres
file: services.yaml
env_file:
- prod.env
volumes:
- ./src/main/resources/db/postgres:/docker-entrypoint-initdb.d:ro
kafka:
extends:
service: kafka
file: services.yaml
volumes:
postgres_data:
kafka_data:
Docker Compose файл для разработки
Теперь провернём аналогичные действия и для Docker Compose файла, который понадобиться нам для разработки.
Сначала расширим сервисы PostgreSQL и Kafka описанные в файле
services.yaml
. В итоге у нас получится файл
compose.dev.yaml
со следующим содержимым:
services:
postgres:
extends:
service: postgres
file: services.yaml
ports:
- "5432:5432"
volumes:
- ./src/main/resources/db/postgres:/docker-entrypoint-initdb.d:ro
environment:
POSTGRES_DB: postgres
POSTGRES_USER: root
POSTGRES_PASSWORD: root
kafka:
extends:
service: kafka
file: services.yaml
volumes:
postgres_data:
kafka_data:
Отмечу, что для PostgreSQL я открыл порт
5432
для внешнего подключения, а для Kafka порт
9092
. Если бы я этого не сделал, то я бы не смог достучаться до базы данных и брокера сообщений извне Docker сети. А, следовательно, если бы я запустил приложение в режиме отладки, подключиться к PostgreSQL или Kafka оно бы не смогло.
Теперь мне остается только добавить удобные инструменты для взаимодействия с нашими сервисами во время разработки. Amplicode подозревает, что я именно этого и хочу, и предлагает их сгенерировать.
Что интересно, для PostgreSQL он даже автоматически настроит подключение в pgAdmin, так что мне ничего не нужно будет настраивать после того, как я его запущу и подключусь. Можно будет просто сразу начать пользоваться.
Жмём ОК и pgAdmin сервис готов:
Повторю то же самое и для Kafka UI.
Вот и все:
В итоге, после всех наших манипуляций файл
compose.dev.yaml
выглядит следующим образом:
services:
postgres:
extends:
service: postgres
file: services.yaml
ports:
- "5432:5432"
volumes:
- ./src/main/resources/db/postgres:/docker-entrypoint-initdb.d:ro
environment:
POSTGRES_DB: postgres
POSTGRES_USER: root
POSTGRES_PASSWORD: root
kafka:
extends:
service: kafka
file: services.yaml
pgadmin:
image: dpage/pgadmin4:8.12.0
restart: "no"
ports:
- "5050:80"
volumes:
- pgadmin_data:/var/lib/pgadmin
- ./docker/pgadmin/servers.json:/pgadmin4/servers.json
- ./docker/pgadmin/pgpass:/pgadmin4/pgpass
environment:
PGADMIN_DEFAULT_EMAIL: admin@admin.com
PGADMIN_DEFAULT_PASSWORD: root
PGADMIN_CONFIG_SERVER_MODE: "False"
PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED: "False"
healthcheck:
test: wget --no-verbose --tries=1 --spider http://localhost:80/misc/ping || exit -1
interval: 10s
timeout: 5s
start_period: 10s
retries: 5
entrypoint: /bin/sh -c "chmod 600 /pgadmin4/pgpass; /entrypoint.sh;"
kafkaui:
image: provectuslabs/kafka-ui:v0.7.2
restart: "no"
ports:
- "8989:8080"
environment:
DYNAMIC_CONFIG_ENABLED: "true"
KAFKA_CLUSTERS_0_NAME: 8GyRIS62T8aMSkDJs-AH5Q
KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS: kafka:29092
healthcheck:
test: wget --no-verbose --tries=1 --spider http://localhost:8080/actuator/health || exit -1
interval: 10s
timeout: 5s
start_period: 60s
retries: 5
volumes:
postgres_data:
kafka_data:
pgadmin_data:
Заключение
Теперь любой разработчик без каких-либо проблем сможет в пару кликов запустить все необходимые для разработки сервисы. И у него не будет никаких проблем, связанных с локальным окружением.
Сегодня мы узнали про два очень полезных ключевых слова Docker Compose —
include
и
extends
— а также научились ими пользоваться для решения конкретных задач.
Подписывайтесь на наши Telegram и YouTube, чтобы не пропустить новые материалы про Amplicode, Spring и связанные с ним технологии!