Перейти к основному содержимому

Докеризация приложения Spring Boot

· 9 мин. чтения

1. Обзор

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

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

Давайте начнем с создания простого приложения Spring Boot, которое мы затем запустим в облегченном базовом образе под управлением Alpine Linux .

2. Dockerize отдельное приложение Spring Boot

В качестве примера приложения, которое мы можем докеризовать, мы создадим простое приложение Spring Boot, docker-message-server, которое предоставляет одну конечную точку и возвращает статическое сообщение:

@RestController
public class DockerMessageController {
@GetMapping("/messages")
public String getMessage() {
return "Hello from Docker!";
}
}

С правильно настроенным файлом Maven мы можем создать исполняемый файл jar:

$> mvn clean package

Далее мы запустим приложение Spring Boot:

$> java -jar target/docker-message-server-1.0.0.jar

Теперь у нас есть работающее приложение Spring Boot, к которому мы можем получить доступ по адресу localhost:8888/messages.

Чтобы докеризировать приложение, мы сначала создаем файл с именем Dockerfile со следующим содержимым:

FROM openjdk:8-jdk-alpine
MAINTAINER foreach.com
COPY target/docker-message-server-1.0.0.jar message-server-1.0.0.jar
ENTRYPOINT ["java","-jar","/message-server-1.0.0.jar"]

Этот файл содержит следующую информацию:

  • FROM : В качестве основы для нашего образа мы возьмем Alpine Linux с поддержкой Java , созданный в предыдущем разделе. ``
  • MAINTAINER : хранитель образа.
  • COPY : мы позволяем Docker скопировать наш jar-файл в образ.
  • ENTRYPOINT : это будет исполняемый файл, запускаемый при загрузке контейнера. Мы должны определить их как JSON-Array , потому что мы будем использовать ENTRYPOINT в сочетании с CMD для некоторых аргументов приложения.

Чтобы создать образ из нашего Dockerfile , нам нужно запустить «docker build», как и раньше:

$> docker build --tag=message-server:latest .

Наконец, мы можем запустить контейнер из нашего образа:

$> docker run -p8887:8888 message-server:latest

Это запустит наше приложение в Docker, и мы сможем получить к нему доступ с хост-компьютера по адресу localhost:8887/messages . Здесь важно определить сопоставление портов, которое сопоставляет порт на хосте (8887) с портом внутри Docker ( 8888 ). Это порт, который мы определили в свойствах приложения Spring Boot.

Примечание. Порт 8887 может быть недоступен на машине, на которой мы запускаем контейнер. В этом случае сопоставление может не работать, и нам нужно выбрать порт, который все еще доступен.

Если мы запустим контейнер в автономном режиме, мы можем проверить его детали, остановить его и удалить с помощью следующих команд:

$> docker inspect message-server
$> docker stop message-server
$> docker rm message-server

2.1. Изменение основного изображения

Мы можем легко изменить базовый образ, чтобы использовать другую версию Java. Например, если мы хотим использовать дистрибутив Corretto от Amazon, мы можем просто изменить Dockerfile :

FROM amazoncorretto:11-alpine-jdk
MAINTAINER foreach.com
COPY target/docker-message-server-1.0.0.jar message-server-1.0.0.jar
ENTRYPOINT ["java","-jar","/message-server-1.0.0.jar"]

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

3. Dockerize приложения в композите

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

Для решения этой проблемы Docker предоставляет инструмент Docker Compose . Этот инструмент поставляется с собственным файлом сборки в формате YAML и лучше подходит для управления несколькими контейнерами. Например, он может запускать или останавливать набор служб одной командой или объединять выходные данные журнала нескольких служб в один псевдотерминал .

3.1. Второе загрузочное приложение Spring

Давайте создадим пример двух приложений, работающих в разных контейнерах Docker. Они будут взаимодействовать друг с другом и представляться хост-системе как «единое целое». В качестве простого примера создадим второе приложение Spring Boot docker-product-server :

@RestController
public class DockerProductController {
@GetMapping("/products")
public String getMessage() {
return "A brand new product";
}
}

Мы можем собрать и запустить приложение так же, как и наш сервер сообщений .

3.2. Файл компоновки Docker

Мы можем объединить конфигурацию обоих сервисов в один файл с именем docker-compose.yml :

version: '2'
services:
message-server:
container_name: message-server
build:
context: docker-message-server
dockerfile: Dockerfile
image: message-server:latest
ports:
- 18888:8888
networks:
- spring-cloud-network
product-server:
container_name: product-server
build:
context: docker-product-server
dockerfile: Dockerfile
image: product-server:latest
ports:
- 19999:9999
networks:
- spring-cloud-network
networks:
spring-cloud-network:
driver: bridge
  • версия : указывает, какую версию формата следует использовать. Это обязательное поле. Здесь мы используем более новую версию, тогда как устаревший формат — «1».

  • services : каждый объект в этом ключе определяет службу , также известную как контейнер. Этот раздел является обязательным.

  • build : если указано, docker-compose может создать образ из Dockerfile.

  • context : если указано, указывает каталог сборки, в котором просматривается Dockerfile .

  • dockerfile : если указано, задает альтернативное имя для Dockerfile.

  • image : сообщает Docker , какое имя он должен дать образу, когда используются функции сборки. В противном случае он ищет этот образ в библиотеке или удаленном реестре.

  • network : это идентификатор именованных сетей для использования. Данное имя-значение должно быть указано в разделе сетей .

  • сети : в этом разделе мы указываем сети, доступные для наших сервисов . В этом примере мы позволяем docker-compose создать для нас именованную сеть типа «мост» . Если для параметра external установлено значение true , он будет использовать существующий с заданным именем.

Прежде чем мы продолжим, мы проверим наш файл сборки на наличие синтаксических ошибок:

$> docker-compose config

Затем мы можем собрать наши образы, создать определенные контейнеры и запустить их одной командой:

$> docker-compose up --build

Это запустит сервер сообщений и сервер продукта одновременно.

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

$> docker-compose down

Более подробное знакомство с docker-compose можно прочитать в нашей статье Introduction to Docker Compose .

3.3. Услуги масштабирования

Приятной особенностью docker-compose является возможность масштабирования сервисов . Например, мы можем указать Docker запустить три контейнера для сервера сообщений и два контейнера для продукта-сервера .

Однако, чтобы это работало правильно, мы должны удалить container_name из нашего docker-compose.yml , чтобы Docker мог выбирать имена и изменять конфигурацию открытого порта, чтобы избежать конфликтов.

Что касается портов, мы можем указать Docker сопоставить диапазон портов на хосте с одним конкретным портом внутри Docker:

ports:
- 18800-18888:8888

После этого мы можем масштабировать наши сервисы следующим образом (обратите внимание, что мы используем модифицированный yml-файл ):

$> docker-compose --file docker-compose-scale.yml up -d --build --scale message-server=1 product-server=1

Эта команда запустит один сервер сообщений и один сервер продукта .

Чтобы масштабировать наши сервисы, мы можем запустить следующую команду:

$> docker-compose --file docker-compose-scale.yml up -d --build --scale message-server=3 product-server=2

Эта команда запустит два дополнительных сервера сообщений и один дополнительный сервер продукта. Работающие контейнеры не будут остановлены.

4. Пользовательское базовое изображение

Базовый образ ( openjdk:8-jdk-alpine ), который мы использовали до сих пор, содержал дистрибутив операционной системы Alpine с уже установленным JDK 8. В качестве альтернативы мы можем создать собственный базовый образ (на основе Alpine или любой другой операционной системы).

Для этого мы можем использовать Dockerfile с Alpine в качестве базового образа и установить JDK по нашему выбору:

FROM alpine:edge
MAINTAINER foreach.com
RUN apk add --no-cache openjdk8
  • FROM : ключевое слово FROM указывает Docker использовать данное изображение с его тегом в качестве базы сборки. Если этого образа нет в локальной библиотеке, выполняется онлайн-поиск на DockerHub или в любом другом настроенном удаленном реестре.
  • MAINTAINER : MAINTAINER обычно представляет собой адрес электронной почты, идентифицирующий автора изображения.
  • RUN : С помощью команды RUN мы выполняем командную строку оболочки в целевой системе. Здесь мы используем менеджер пакетов Alpine Linux , apk, для установки Java 8 OpenJDK.

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

docker build --tag=alpine-java:base --rm=true .

ВНИМАНИЕ: Опция –tag задает имя образа, а –rm=true удаляет промежуточные образы после его успешной сборки. Последний символ в этой команде оболочки — точка, действующая как аргумент каталога сборки.

Теперь мы можем использовать созданный образ вместо openjdk:8-jdk-alpine .

5. Поддержка пакетов сборки в Spring Boot 2.3

В Spring Boot 2.3 добавлена поддержка пакетов сборки . Проще говоря, вместо того, чтобы создавать собственный Dockerfile и собирать его с помощью чего-то вроде docker build , все, что нам нужно сделать, это выполнить следующую команду:

$ mvn spring-boot:build-image

Точно так же в Gradle:

$ ./gradlew bootBuildImage

Чтобы это работало, нам нужно установить и запустить Docker.

Основная мотивация сборочных пакетов — создать такой же опыт развертывания, который уже некоторое время предоставляют некоторые известные облачные сервисы, такие как Heroku или Cloud Foundry. Мы просто запускаем цель build-image , а затем платформа сама позаботится о сборке и развертывании артефакта.

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

Помимо простоты использования и улучшения общего опыта разработчиков, он также может быть более эффективным. Например, подход buildpacks создаст многоуровневый образ Docker и использует развернутую версию файла Jar.

Давайте посмотрим, что произойдет после того, как мы запустим указанную выше команду.

Когда мы перечисляем доступные образы докеров:

docker image ls -a

Мы видим строку для изображения, которое мы только что создали:

docker-message-server 1.0.0 b535b0cc0079

Здесь имя и версия образа соответствуют имени и версии, которые мы определили в файле конфигурации Maven или Gradle. Хэш-код — это короткая версия хеша изображения.

Затем, чтобы запустить наш контейнер, мы можем просто запустить:

docker run -it -p9099:8888 docker-message-server:1.0.0

Как и в случае с нашим встроенным образом, нам нужно сопоставить порт, чтобы сделать наше приложение Spring Boot доступным из-за пределов Docker.

6. Заключение

В этой статье мы узнали, как создавать собственные образы Docker , запускать приложение Spring Boot в качестве контейнера Docker и создавать контейнеры с помощью docker-compose .

Для дальнейшего чтения о файлах сборки мы обращаемся к официальному справочнику Dockerfile и справочнику docker-compose.yml .

Как обычно, исходники этой статьи можно найти на Github .