Публикация

Как собрать Docker Image?

Если вы работаете с Docker не первый день, то скорее всего уже знаете как собирать проект в Docker image и что с ним делать дальше. Это короткое руководство для тех, кто делает свои первые шаги в сторону Docker и хочет понять как устроен этот процесс.

Подготовка

Для того чтобы успешно повторить этот гайд вам потребуется:

Весь код ниже будет продемонстрирован на примере проекта на Kotlin с использованием Spring Boot, код которого можно найти на нашем GitHub.

Что такое Docker?

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

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

https://api.bcode.dev/v1/content/storage/post/100035/4be72753bed8432cbc587370aa74b5d2.png

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

Image - файл содержащий все необходимые артефакты вашего проекта и для дальнейшего запуска в Docker среде. Может содержать один и более контейнеров.

Container - обычно содержит запущенный процесс на конкретном порту. Например, часто у нас может быть запущен Java проект в контейнере и в дополнительном контейнере будет запущен Mail Server для отправки писем пользователям через наш Java Application.

Registry - хранилище, позволяющее делится вашими images.

Если коротко, то обычно мы создаем Image и загружаем его в Registry, после чего все клиенты или другие пользователи могут получать к ним доступ публично или через аутентификацию в случае приватного registry.

Решение проблемы 2х Jar файлов

Если вы используете Gradlle, то для корректной работы нам нужно добавить следующие строки в build.gradlle.kts:

tasks.getByName<Jar>("jar") {
	enabled = false
}

Или для build.gradlle:

jar {
    enabled = false
}

Это необходимо, только если вы используете Spring Boot 2.5.0+ и/или Gradle 7+. Если этого не сделать, тогда после сборки проекта мы получим два jar файла. А так как мы будем получать путь к файлу игнорируя версию в конце, то будем на выходе получать два файла.

https://api.bcode.dev/v1/content/storage/post/100035/581174f0b97a490cb855f70ded0863ad.png

Способ 1

Данный способ максимально прост в использовании. Заключается он в том, что мы собираем Docker Image на основе уже собранного нами проекта с помощью Gradle/Maven в ручную.

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

Dockerfile - это файл, содержащий инструкции как будет происходить сборка нашего проекта в наш финальный Docker Image.

FROM openjdk:11-jre-slim
COPY build/libs/spring-boot-docker-*.jar /app/spring-boot-docker.jar
CMD ["java", "-jar", "/app/spring-boot-docker.jar"]
  • FROM, будет одинаковой для всех трех способов. Тут мы указываем на базе какого родительского image мы будем строить свой; Я использую Open JDK 11 версию Slim. Slim содержит только тот минимальный набор, который необходим для корректной работы Java.
  • COPY, указывает какие артифакты/файлы необходимо скопировать в наш Image. COPY можно использовать несколько раз.
  • CMD, сообщает Docker команду, которую он выполнит внутри нашего контейнера. после запуска Image.

Для получения Docker Image выполняем следующие команды в корневой папке проекта:

# Собираем проект
> gradle build

# Собираем docker image
> docker build -t spring-boot-docker:1.0.0 .

# Показать все Images
> docker images

REPOSITORY            TAG      IMAGE ID       CREATED          SIZE
spring-boot-docker    1.0.0    9c6419444969   45 seconds ago   243MB

Во второй команде мы собираем проект и с помощью флага -t мы сразу же указываем tag для нашего Image, обычно в качестве Tag используют версию собираемого проекта.

После чего можем запустить наш Image:

# Запустить Image
> docker run -d -p 8080:8080 spring-boot-docker:1.0.0

# Проверить запущеные Containers
> docker ps

CONTAINER ID   IMAGE                      COMMAND                  CREATED              STATUS              PORTS
a26a3b941fb3   spring-boot-docker:1.0.0   "java -jar /app/spri…"   About a minute ago   Up About a minute   0.0.0.0:8080->8080/tcp, :::8080->8080/tcp

https://api.bcode.dev/v1/content/storage/post/100035/6039e39fb4d04465a7f4aead2800648b.png

Где -p указывает какой порт в контейнере нужно сделать видимым в не Docker сети, а именно наружу.

Остановить контейнер можно так:

> docker stop [CONTAINER ID]

Такой подход используется в моей практике чаще всего, так как за сборку проекта отвечает CI инструменты такие как Jenkins, а Dockerfile содержит тот минимум инструкций, чтобы собрать работоспособный Docker Image.

Способ 2

Первы способ максимально простой, где Dockerfile отвечает только за то, чтобы взять готовый Jar файл и запустить его.

Давайте представим ситуацию, когда у нас нет возможности на нашем Jenkins или другом CI инструменте иметь пред-установленный Maven/Gradle и/или по какой-либо другой причине мы не можем его использовать.

Тогда, на помощь приходит Doсker, где в роли родительского Image выступит Image с нужным нам инструментом. Кстати, искать их можно на Docker Hub.

FROM gradle:7.1.1-jdk11-openj9
COPY ./ ./
RUN gradle build
CMD ["java", "-jar", "build/libs/spring-boot-docker-0.0.1-SNAPSHOT.jar"]
  • FROM - говорит какой родительский Image будет взять за основу. В нашем случае это gradle image, который позволит внутри контейнера использовать gradle команды.
  • COPY - копирует все содержимое корневой папки проекта в корень нашего Image.
  • RUN - этой команды не было в первом способе, она позволяет выполнить команду в момент сборки Image. И тут мы внутри нашего контейнера при сборке говорим, что нужно собрать проект используя Gradle.
  • CMD - аналогично способу 1, запускает наш jar.

В отличии от способа 1, тут нам нет необходимости иметь Maven/Gradle для сборки проект, достаточно просто иметь установленный Docker:

# Собираем Image
> docker build -t spring-boot-docker:1.0.1 .

# Запускаем
> docker run -d -p 8080:8080 spring-boot-docker:1.0.1

Недостатком такого подхода является большой физический объем финально Docker Image и содержание всех исходных файлов.

> docker image ls

REPOSITORY            TAG      IMAGE ID        SIZE
spring-boot-docker    1.0.1    41cf3100e892    1.31GB
spring-boot-docker    1.0.0    9c6419444969    243MB

Способ 3

Этот подход совмещает два прошлых, а также позволяет выполнять боле одного этапа сборки.

Допустим, нам нужно собрать проект с помощью Gradle, потом собрать вторую часть проекта используя Maven и после собрать это все в готовый контейнер под управлением Tomcat. Такую очередность можно достичь используя Multi-Stage Build.

В примере ниже мы придерживаемся подхода в способе 2, но хотим чтобы наш Docker Image имел меньше размер, использовал в качестве родительского Image - Open JRE Slim и содержал Jar файл проекта.

Для этого, мы воспользуемся Multi-stage сборкой нашего Image.

FROM gradle:7.1.1-jdk11-openj9 AS GRADLE_BUILD
COPY ./ /home/app
WORKDIR /home/app
RUN gradle build

FROM openjdk:11-jre-slim
COPY --from=GRADLE_BUILD /home/app/build/libs/spring-boot-docker-*.jar /app/spring-boot-docker.jar
CMD ["java", "-jar", "/app/spring-boot-docker.jar"]

Первый этап сборки:

  • FROM .. AS .. - позволяет нам дать имя данному этапу сборки, используя которое мы сможем обращаться к файловой системе контейнера первого этапа по имени GRADLE_BUILD. Не дав имя этому этапу, он получит имя по умолчанию 0.
  • COPY - скопирует все содержимое нашего проекта в контейнер по указанному пути.
  • WORKDIR - указывает на директорию, где будут выполнены последующие команды. Говоря проще, то просто переходит в указанную папку в файловой системе нашего контейнера.
  • RUN - запускает сборку используя Gradle, который представлен родительским Image.

Второй этап сборки:

  • FROM - говорим, что будем использовать для в качестве родительского Image Open JRE 11 Slim, так как для запуска Jar его вполне чем достаточно.
  • COPY - тут мы обращаемся к файловой системе контейнера первого этапа сборки по имени указанном в первом этапе FROM .. AS [NAME] и копируем с первого этапа финальный Jar файл в наш финальный Image, который будет построен после второго этапа сборки.
  • CMD - знакомая нам ранее инструкция, которая указывает на то как будет происгодить запуск нашего приложения после запуска контейнера в Docker.

Далее собираем и запускаем наш Image:

# Собираем Image
> docker build -t spring-boot-docker:1.0.2 .

# Запускаем
> docker run -d -p 8080:8080 spring-boot-docker:1.0.2

Теперь сравним размер Image во всех трех способах:

> docker image ls

REPOSITORY            TAG         IMAGE ID       SIZE
spring-boot-docker    1.0.2       e574134f3080   243MB
spring-boot-docker    1.0.1       a87b7414437b   1.28GB
spring-boot-docker    1.0.0       9c6419444969   243MB

Как можем видеть первый и последний способ не отличаются размером, так как они собраны на базе одного и того же родительского Image, отличие лишь в том, что сборку Jar файла мы также доверили Docker.

Как загрузить в Registry?

Ну вот, мы определились какой способ использовать и даже собрали наш Docker Image. Но как мне им поделится с другими? Для этого идем на Docker Hub и регистрируемся там.

После чего жмем "Create Repository", даем ему имя, обычно оно такое же как имя вашего проекта:

https://api.bcode.dev/v1/content/storage/post/100035/aa9615f20e3b4ccf8f6afd230af0dc57.png

Жмем создать. Ну вот, у нас есть репозиторий и мы можем загрузить в него наш первый Image. Для этого нам нужно либо изначально собирать Docker Image с нужным нам тегом, либо создать новый tag для уже существующего image, что я и сделаю ниже:

# Добавляем новый тег существующему Image
docker tag spring-boot-docker:1.0.2 axbarchuk/spring-boot-docker:1.0.0

# Заходим в Docker Hub, введя логин и пароль
docker login

# Загружаем Image в registry
docker push axbarchuk/spring-boot-docker:1.0.0

Наш Image загружен! Теперь даже вы можете его скачать и запустить:

> docker pull axbarchuk/spring-boot-docker:1.0.0
> docker run -d -p 8080:8080 axbarchuk/spring-boot-docker:1.0.0

> curl localhost:8080/hello

Hello bcode.dev
Опубликовано: 19 августа 2021 г.Просмотров: 195

Комментарии 1

Мы не даем слово анониму 😶
Войдите, пожалуйста.

ax.barchuk Начиная с этого комментария на сайте возможно высказать свои мысли 🤔

Оветить3 месяца назад