Деплоим Spring Boot приложение через Docker Compose в Timeweb Cloud за 10 минут

от автора

Когда вы впервые сталкиваетесь с задачей деплоя, процесс может показаться сложным и пугающим. Докер-образы, безопасность, container registry, а тем более Kubernetes — для новичка это настоящая головная боль. Именно поэтому наши партнеры из Amplicode решили написать статью, которая поможет вам максимально просто и быстро задеплоить ваше первое Spring-приложение в облако.

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

VK Видео и YT

❯ Описание приложения

Наше приложение — это стандартный пример Spring Petclinic, используемый для демонстрации возможностей Spring Boot. Помимо самого приложения нам также понадобятся два внешних сервиса: база данных PostgreSQL и брокер сообщений Kafka. Конечно, мы могли бы воспользоваться готовыми облачными сервисами, получив простоту настройки и быстрый старт. Однако сегодня мы пойдем другим путем: развернем все сервисы самостоятельно через Docker Compose.

❯ Контейнеризация Spring Boot приложения

Первый шаг — это создание файла docker-compose.yml:

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

Сгенерированные файлы
services:   spring-petclinic:     image: org.springframework.samples.spring-petclinic:latest     build:       context: ../..       dockerfile: existing-spring-boot-app-modification/spring-petclinic-main/Dockerfile       args:         DOCKER_BUILDKIT: 1     restart: "no"     ports:       - "8080:8080"     healthcheck:       test: wget --no-verbose --tries=1 --spider http://localhost:8080/actuator/health || exit 1       interval: 30s       timeout: 5s       start_period: 30s       retries: 5     labels:       amplicode.image: springboot   
FROM gradle:9.0.0 AS builder WORKDIR /application COPY . . RUN --mount=type=cache,target=/root/.gradle gradle clean :org.springframework.samples.spring-petclinic:build -x test  FROM bellsoft/liberica-openjre-alpine:23 AS layers WORKDIR /application COPY --from=builder /application/existing-spring-boot-app-modification/spring-petclinic-main/build/libs/*.jar app.jar RUN java -Djarmode=layertools -jar app.jar extract  FROM bellsoft/liberica-openjre-alpine:23 VOLUME /tmp RUN adduser -S spring-user USER spring-user  WORKDIR /application  COPY --from=layers /application/dependencies/ ./ COPY --from=layers /application/spring-boot-loader/ ./ COPY --from=layers /application/snapshot-dependencies/ ./ COPY --from=layers /application/application/ ./  ENV JAVA_RESERVED_CODE_CACHE_SIZE="240M" ENV JAVA_MAX_DIRECT_MEMORY_SIZE="10M" ENV JAVA_MAX_METASPACE_SIZE="179M" ENV JAVA_XSS="1M" ENV JAVA_XMX="345M"  ENV JAVA_ERROR_FILE_OPTS="-XX:ErrorFile=/tmp/java_error.log" ENV JAVA_HEAP_DUMP_OPTS="-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp" ENV JAVA_ON_OUT_OF_MEMORY_OPTS="-XX:+CrashOnOutOfMemoryError" ENV JAVA_NATIVE_MEMORY_TRACKING_OPTS="-XX:NativeMemoryTracking=summary -XX:+UnlockDiagnosticVMOptions -XX:+PrintNMTStatistics" ENV JAVA_GC_LOG_OPTS="-Xlog:gc*,safepoint:/tmp/gc.log::filecount=10,filesize=100M" ENV JAVA_FLIGHT_RECORDING_OPTS="-XX:StartFlightRecording=disk=true,dumponexit=true,filename=/tmp/,maxsize=10g,maxage=24h" ENV JAVA_JMX_REMOTE_OPTS="-Djava.rmi.server.hostname=127.0.0.1 \    -Dcom.sun.management.jmxremote.authenticate=false \    -Dcom.sun.management.jmxremote.ssl=false \    -Dcom.sun.management.jmxremote.port=5005 \    -Dcom.sun.management.jmxremote.rmi.port=5005"  ENTRYPOINT java \    -XX:ReservedCodeCacheSize=$JAVA_RESERVED_CODE_CACHE_SIZE \    -XX:MaxDirectMemorySize=$JAVA_MAX_DIRECT_MEMORY_SIZE \    -XX:MaxMetaspaceSize=$JAVA_MAX_METASPACE_SIZE \    -Xss$JAVA_XSS \    -Xmx$JAVA_XMX \    $JAVA_HEAP_DUMP_OPTS \    $JAVA_ON_OUT_OF_MEMORY_OPTS \    $JAVA_ERROR_FILE_OPTS \    $JAVA_NATIVE_MEMORY_TRACKING_OPTS \    $JAVA_GC_LOG_OPTS \    $JAVA_FLIGHT_RECORDING_OPTS \    $JAVA_JMX_REMOTE_OPTS \    org.springframework.boot.loader.JarLauncher 

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

Или даже уже собранный образ приложения:

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

Также заранее настраиваем инструменты для анализа и отладки: heap dump, GC-логи, Java Flight Recorder (JFR) и JMX. Эти инструменты помогут нам эффективно диагностировать проблемы в будущем (если вдруг когда они появятся).

Важный нюанс: по умолчанию я использую временные директории (например, /tmp), которые очищаются после перезапуска контейнера или виртуальной машины. Для продакшн-среды рекомендую подключить постоянный volume или сетевой диск.

❯ Создание PostgreSQL и Kafka сервисов для Docker Compose

Помимо приложения, в docker-compose.yml объявляем сервисы PostgreSQL и Kafka. В продакшн-среде не рекомендуется экспоузить порты этих сервисов напрямую наружу — достаточно, чтобы доступ к ним имело только ваше приложение. 

Сгенерированные сервисы
services:  postgres:    image: postgres:17.5    restart: "no"    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:8.0.0    restart: "no"    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: koa_uLJGTQe-qKkjpbagQg      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    labels:      amplicode.image: confluent/kafka volumes:  postgres_data:  kafka_data:

❯ Дополнительная конфигурация Spring Boot приложения

Чтобы наше приложение корректно работало в Docker-контейнере и могло гибко принимать параметры извне, необходимо внести небольшие изменения в настройки самого приложения. В частности, значения, такие как URL подключения к базе данных PostgreSQL, логин и пароль, а также адрес брокера Kafka, мы вынесем в переменные окружения. Это легко реализовать с помощью функции «Wrap properties into» от Amplicode, которая автоматически заменит статичные значения в файле свойств (application.properties) на переменные окружения.

Затем нам останется только передать эти переменные в Docker Compose сервис нашего приложения.

❯ Разворачиваем приложение в облаке

После того, как Dockerfile и docker-compose файл готовы, коммитим весь код в удаленный репозиторий. Например, в GitFlic. Затем переходим к облачной инфраструктуре. Я использую виртуальную машину в Timeweb Cloud, выбирая готовый образ с установленными Docker и Git, чтобы не тратить лишнее время на настройку окружения. 

Все остальные настройки оставляю по умолчанию. После создания виртуальной машины подключаюсь к ней по SSH:

Остается только клонировать репозиторий и запустить приложение командой docker-compose up -d:

Проверяем приложение на публичном ip. 

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

Всё работает великолепно!

❯ Итог

Конечно, такой подход далек от идеального продакшена, однако он отлично подходит для быстрого старта. С его помощью вы сможете на практике разобраться в основах деплоя и понять, как устроены докер-контейнеры и запуск приложений в облаке. В следующей статье мы рассмотрим более «взрослый» способ деплоя — с использованием Kubernetes.

Новости, обзоры продуктов и конкурсы от команды Timeweb.Cloud — в нашем Telegram-канале 


ссылка на оригинал статьи https://habr.com/ru/articles/936148/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *