Статьи

Использование Docker для помещения существующего приложения в несколько контейнеров

Я наконец-то дошел до того, чтобы научиться использовать Docker после того, как узнал, что это такое и что делает, даже не используя его. Это первая публикация, в которой я попытался использовать Docker, и, вероятно, именно к ней я обращаюсь всякий раз, когда начинаю новый проект (в любом случае для Java или Kotlin).

Это будет короткая публикация, которая берет существующий проект (из одной из моих других публикаций) и изменяет его, чтобы он мог работать внутри контейнеров. Я сомневаюсь, что этот пост будет содержать что-то впечатляющее, но я знаю, что это поможет мне в будущем и, возможно, поможет вам сейчас.

Прежде чем мы начнем, давайте посмотрим на существующий проект. Вот ссылки на код и соответствующий пост в блоге . Сообщение в блоге охватывает всю информацию о коде. Вот краткое изложение, чтобы мы могли продолжить этот пост. Старый проект представляет собой приложение Spring Boot с базой данных MongoDB и очередью сообщений ActiveMQ. Все эти компоненты являются основным кормом для контейнеризации.

докер

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

Преобразование приложения Spring

Прежде всего, приложение Spring Boot.

Это единственная часть проекта, которая содержит наш код. Остальные — просто изображения, загруженные из чужого репозитория. Чтобы начать движение этого приложения к запуску в контейнере, нам нужно создать Dockerfile который определяет содержимое изображения:

1
2
3
4
5
FROM openjdk:8-jdk-alpine
LABEL maintainer="Dan Newton"
ADD target/spring-boot-jms-tutorial-1.0.0.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/app.jar"]

Это берет базовый образ openjdk:8-jdk-alpine который является хорошей отправной точкой для приложения, добавляет Jar openjdk:8-jdk-alpine созданный из кода приложения (с именем app.jar ) и предоставляет порт для связи между контейнерами. Последняя строка определяет команду, которая выполняется при запуске образа в контейнере. Это то, что запускает приложение Spring.

Чтобы создать образ из Dockerfile выполните команду ниже (при условии, что вы уже создали код приложения):

1
docker build -t spring-boot-jms-tutorial .

Теперь есть образ с именем spring-boot-jms-tutorial ( -t позволяет нам определить имя). Теперь это можно использовать для создания контейнера, который выполняет код, который упакован в Jar образа:

1
docker run --name application -p 4000:8080 spring-boot-jms-tutorial

Это создаст и запустит контейнер, созданный из образа spring-boot-jms-tutorial . Он называет application контейнер, а свойство -p позволяет сопоставить порт с локальной машины порту внутри контейнера. Чтобы получить доступ к порту 8080 контейнера, нам просто нужно использовать порт 4000 на нашей собственной машине.

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

1
docker start application

Где application — это имя контейнера, который мы создали ранее. Если снова docker run он создаст другой новый контейнер, а не повторно использует существующий. На самом деле, поскольку мы предоставили имя контейнеру, выполнение той же команды run ранее, приведет к ошибке.

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

Ошибка подключения к MongoDB:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
Exception in monitor thread while connecting to server mongocontainer:27017
 
com.mongodb.MongoSocketException: mongocontainer: Name does not resolve
    at com.mongodb.ServerAddress.getSocketAddress(ServerAddress.java:188) ~[mongodb-driver-core-3.6.4.jar!/:na]
    at com.mongodb.connection.SocketStreamHelper.initialize(SocketStreamHelper.java:59) ~[mongodb-driver-core-3.6.4.jar!/:na]
    at com.mongodb.connection.SocketStream.open(SocketStream.java:57) ~[mongodb-driver-core-3.6.4.jar!/:na]
    at com.mongodb.connection.InternalStreamConnection.open(InternalStreamConnection.java:126) ~[mongodb-driver-core-3.6.4.jar!/:na]
    at com.mongodb.connection.DefaultServerMonitor$ServerMonitorRunnable.run(DefaultServerMonitor.java:114) ~[mongodb-driver-core-3.6.4.jar!/:na]
    at java.lang.Thread.run(Thread.java:748) [na:1.8.0_171]
Caused by: java.net.UnknownHostException: mongocontainer: Name does not resolve
    at java.net.Inet4AddressImpl.lookupAllHostAddr(Native Method) ~[na:1.8.0_171]
    at java.net.InetAddress$2.lookupAllHostAddr(InetAddress.java:928) ~[na:1.8.0_171]
    at java.net.InetAddress.getAddressesFromNameService(InetAddress.java:1323) ~[na:1.8.0_171]
    at java.net.InetAddress.getAllByName0(InetAddress.java:1276) ~[na:1.8.0_171]
    at java.net.InetAddress.getAllByName(InetAddress.java:1192) ~[na:1.8.0_171]
    at java.net.InetAddress.getAllByName(InetAddress.java:1126) ~[na:1.8.0_171]
    at java.net.InetAddress.getByName(InetAddress.java:1076) ~[na:1.8.0_171]
    at com.mongodb.ServerAddress.getSocketAddress(ServerAddress.java:186) ~[mongodb-driver-core-3.6.4.jar!/:na]
    ... 5 common frames omitted

ActiveMQ также не существует:

1
2
3
Could not refresh JMS Connection for destination 'OrderTransactionQueue' - retrying using FixedBackOff{interval=5000, currentAttempts=1, maxAttempts=unlimited}.
Cause: Could not connect to broker URL: tcp://activemqcontainer:61616.
Reason: java.net.UnknownHostException: activemqcontainer

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

И последнее, прежде чем мы перейдем к рассмотрению Mongo и ActiveMQ.

Также можно использовать dockerfile-maven-plugin чтобы помочь с вышеизложенным, который собирает контейнер как часть запуска mvn install . Я решил не использовать его, так как не мог заставить его работать должным образом с docker-compose . Ниже приведен краткий пример использования плагина:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<build>
    <plugins>
        <plugin>
            <groupId>com.spotify</groupId>
            <artifactId>dockerfile-maven-plugin</artifactId>
            <version>1.4.4</version>
            <executions>
                <execution>
                    <id>default</id>
                    <goals>
                        <goal>build</goal>
                        <goal>push</goal>
                    </goals>
                </execution>
            </executions>
            <configuration>
                <!-- Names the image: spring-boot-jms-tutorial -->
                <repository>${project.artifactId}</repository>
                <buildArgs>
                    <JAR_FILE>${project.build.finalName}.jar</JAR_FILE>
                </buildArgs>
            </configuration>
        </plugin>
    </plugins>
</build>

Это позволяет нам заменить несколько строк в Dockerfile :

1
2
3
4
5
6
FROM openjdk:8-jdk-alpine
LABEL maintainer="Dan Newton"
ARG JAR_FILE
ADD target/${JAR_FILE} app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/app.jar"]

Здесь была добавлена ​​одна строка и изменена одна существующая. Аргумент JAR_FILE заменяет исходное имя Jar, которое вводится плагином из pom.xml . Сделайте эти изменения и запустите mvn install и bam, ваш контейнер собран со всем необходимым кодом.

Использование изображения MongoDB

Существует изображение MongoDB, готовое к использованию. Он идеально назван mongo … Что еще ты ожидал? Все, что нам нужно сделать, это run изображение и дать ему имя контейнера:

1
docker run -d --name mongocontainer mongo

Добавление -d запустит контейнер в фоновом режиме. Имя контейнера не только для удобства, так как приложению Spring оно понадобится позже для подключения к Mongo.

На образ ActiveMQ

Настроить ActiveMQ так же просто, как Mongo. Запустите команду ниже:

1
docker run -d --name activemqcontainer -p 8161:8161 rmohr/activemq

Здесь порты 8161 отображаются из контейнера в машину, на которой он работает, что позволяет получить доступ к консоли администратора из-за пределов контейнера.

Связывая все это вместе

Если бы вы выполняли все эти команды, читая пост, вы бы заметили, что контейнер application самом деле не смог увидеть mongocontainer и activemqcontainer . Это потому, что они не работают в одной сети. Заставить их общаться не сложно и требует всего несколько дополнительных шагов.

По умолчанию Docker создает сеть Bridge при настройке без дополнительной настройки. Ниже описано, как это сделать:

1
docker network create network

Теперь, когда сеть (названная network ) создана, команды, которые выполнялись ранее, необходимо изменить, чтобы создать контейнеры, которые будут подключаться к сети. Ниже приведены 3 команды, использованные для создания контейнеров в предыдущих разделах, каждая из которых была изменена для присоединения к сети.

1
2
3
docker run -d --name mongocontainer --network=network mongo
docker run -d --name activemqcontainer -p 8161:8161 --network=network rmohr/activemq
docker run --name application -p 4000:8080 --network=network spring-boot-jms-tutorial

После того как все они будут запущены, приложение в целом будет работать. Каждый контейнер может видеть друг друга. Разрешение контейнера application для подключения к MongoDB и ActiveMQ в их соответствующих контейнерах.

На данный момент все работает. Он работает так же, как я помню, он работал, когда я все настроил на своем ноутбуке. Но на этот раз ничего не настроено локально… За исключением Docker!

Докер сочиняет это

Мы могли бы остановиться на этом, но в следующем разделе все станет еще проще. Docker Compose позволяет нам эффективно объединять все команды, которые мы запускали ранее, и запускать все контейнеры и их сеть в одну команду. Очевидно, что есть еще некоторые настройки, но, в конце концов, я думаю, что количество набирается на самом деле уменьшается.

Для этого нам нужно создать файл docker-compose.yml :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
version: '3'
services:
  appcontainer:
    build:
      context: .
      args:
        JAR_FILE: /spring-boot-jms-tutorial-1.0.0.jar
    ports:
    - "4000:8080"
  activemqcontainer:
    image: "rmohr/activemq"
    ports:
    - "8161:8161"
  mongocontainer:
image: "mongo"

Используйте с этой версией Dockerfile :

1
2
3
4
5
6
FROM openjdk:8-jdk-alpine
LABEL maintainer="Dan Newton"
ARG JAR_FILE
ADD target/${JAR_FILE} app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/app.jar"]

Это почти все, что нужно сделать.

appcontainer — это контейнер приложений Spring, созданный из кода проекта. Свойство build контейнера указывает Docker создавать образ на основе Dockerfile проектов, найденного в корневом каталоге проекта. Он передает аргумент JAR_FILE в Dockerfile перемещая часть конфигурации в этот файл.

Два других контейнера не требуют особой настройки. Как и в случае с предыдущими командами, определяются образы, из которых они построены, и activemqcontainer добавляет конфигурацию вокруг своих сопоставленных портов.

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

Все, что осталось сделать, это запустить команду up :

1
docker-compose up

Это соберет и запустит все контейнеры. Изображение кода приложения создается при необходимости. Выполнение этой точной команды выведет все журналы контейнеров на консоль, для этого в фоновом режиме добавьте флаг -d :

1
docker-compose up -d

После этого мы можем взглянуть на созданные контейнеры и сеть. Бег:

1
docker ps -a --format "table {{.Image}}\t{{.Names}}"

Показывает нам:

1
2
3
4
IMAGE                          NAMES
mongo                          spring-boot-jms_mongocontainer_1
spring-boot-jms_appcontainer   spring-boot-jms_appcontainer_1
rmohr/activemq                 spring-boot-jms_activemqcontainer_1

И сеть:

1
docker network ls

Производит:

1
2
NETWORK ID          NAME                      DRIVER              SCOPE
163edcfe5ada        spring-boot-jms_default   bridge              local

К именам контейнеров и сети добавляется имя проекта.

Вывод

Вот и все, что нужно … В любом случае, для простой настройки я уверен, что боги Докера могут сделать гораздо больше, но я не один из них … Пока.

В заключение мы взяли существующее приложение, которое я написал, для локальной работы на машине и поместили все в несколько контейнеров. Это означало, что мы пошли с машины, на которой нужно было все настроить, с установленными MongoDB и ActiveMQ. Вместо этого — машина, которая может пропустить все это, используя контейнеры и требуя только установки Docker. Затем Docker управляет всеми зависимостями для нас.

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

Код, используемый в этом посте, можно найти на моем GitHub .

Если вы нашли этот пост полезным, вы можете подписаться на меня в Twitter на @LankyDanDev, чтобы не отставать от моих новых сообщений.

Опубликовано на Java Code Geeks с разрешения Дэна Ньютона, партнера нашей программы JCG . См. Оригинальную статью здесь: Использование Docker для помещения существующего приложения в некоторые контейнеры.

Мнения, высказанные участниками Java Code Geeks, являются их собственными.