Docker Java основные команды. Инструкция для начинающих.

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

@javatg – лучшие материалы Java

Что такое Docker

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

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

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

Преимущества контейнеров:

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

Основные понятия

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

DockerfIle — текстовый файл с набором инструкций по созданию образа, каждая из которых добавляет к образу новый слой.

Контейнер  — конкретный экземпляр приложения, созданный на основе образа. Причём из одного образа можно создать сколько угодно контейнеров. Технически контейнер создаётся путём добавления к образу нового слоя, содержащего результаты работы приложения.

Реестр — хранилище образов (как GitHub для кода приложений). Образы можно скачивать из реестра и создавать на их основе контейнеры. Также в реестр можно загружать новые или изменённые образы для дальнейшего использования.

Пример использования

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

  1. Устанавливаем Docker. Все инструкции можно найти на официальном сайте.
  2. Генерируем проект с помощью конструктора Spring. Задаём spring-docker-simple в полях Artifact и Name.
  3. Выбираем одну зависимость — Spring Web. Готово! Скачиваем и распаковываем архив, открываем его в среде разработки.
Docker Java основные команды. Инструкция для начинающих.

Код приложения

В открывшемся проекте рядом с главным классом, содержащим метод main, создаём ещё один класс — контроллер с методом hello ().import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class HelloDockerController { @GetMapping("/") public String hello() { return "Hello Docker!"; } }

Класс помечен аннотацией @RestController, означающей, что он предназначен для обработки web-запросов. А метод помечен аннотацией @GetMapping c адресом «/» — перейдя по нему (выполнив get-запрос к http://localhost:8080/), мы получим сообщение «Hello Docker!»

Docker Java основные команды. Инструкция для начинающих.

Теперь открываем терминал и вводим команду:./mvnw package && java -jar target/spring-docker-simple-0.0.1-SNAPSHOT.jar

Она упакует приложение в jar-файл и запустит его. Чтобы убедиться в корректности работы приложения — перейдём на http://localhost:8080/ в браузере и увидим заветное сообщение.

Теперь создаём файл с именем Dockerfile в корне проекта, который содержит инструкции для сборки образа со следующим текстом:FROM adoptopenjdk/openjdk11:alpine-jre ARG JAR_FILE=target/spring-docker-simple-0.0.1-SNAPSHOT.jar WORKDIR /opt/app COPY ${JAR_FILE} app.jar ENTRYPOINT ["java","-jar","app.jar"]

Docker Java основные команды. Инструкция для начинающих.

Вот что происходит, когда мы вводим этот код:

КомандаОписание
FROM adoptopenjdk/openjdk11:alpine-jreOбраз создаётся на основе alpine linux с установленной openjdk11
ARG JAR_FILE=target/spring-docker-simple-0.0.1-SNAPSHOT.jarПеременной JAR_FILE присваивается путь к jar- архиву
WORKDIR /opt/appНазначаем рабочую директорию, в которой будут выполняться дальнейшие команды (перемещаемся в папку app)
COPY ${JAR_FILE} app.jarНаш jar-файл, указанный в JAR_FILE, копируется в папку app, и копии задаётся имя app.jar
ENTRYPOINT [“java”,”-jar”,”app.jar”]jar-файл запускается, собирается команда java -jar app.jar из заданной рабочей директории

После этого в терминале вводим команду, с помощью которой собираем образ и запускаем контейнер.docker build -t spring-docker-simple:0.0.1 .

Точка в конце важна, она указывает на расположение Dockerfile (символ «точка» означает текущую директорию. Проверьте, что образ создан командой docker images. Вывод должен быть таким:

Docker Java основные команды. Инструкция для начинающих.

Запускаем контейнер командой:docker run -d -p 8080:8080 -t spring-docker-simple:0.0.1

Опция -d означает старт процесса в фоновом режиме. Опция -p тоже важна — дело в том, что контейнер собирается в полностью изолированном окружении. Тот факт, что приложение внутри контейнера запущено на порту 8080, не означает, что оно доступно вне контейнера на этом порту.

Требуется явно указать, что порту 8080 в контейнере (здесь второе значение — это порт, на котором работает наше приложение в контейнере) соответствует порт 8080 на локальной машине, который будет использоваться при обращении к контейнеру. Поэтому пишем через двоеточие -p 8080:8080.

Теперь введём в терминале команду:curl http://localhost:8080

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

Частые команды при работе с Docker

docker ps — выводит список запущенных контейнеров. Также ей можно передать параметр -a, чтобы вывести все контейнеры, а не только запущенные.

Docker Java основные команды. Инструкция для начинающих.

docker build — собирает образ Docker из Dockerfile и набора файлов, расположенных по определённому пути.

Docker Java основные команды. Инструкция для начинающих.

Параметр -t используется, чтобы задать имя образа, последний параметр. — наименование каталога (в нашем случае текущий каталог).

docker images — выводит список образов в вашей системе.

Docker Java основные команды. Инструкция для начинающих.

docker logs — позволяет вывести на консоль логи указанного контейнера. Для этого необходимо указать имя или id контейнера. Можно использовать флаг –follow, чтобы следить за логами работающего контейнера: например, docker logs –follow c5ecc88de8f9.

Docker Java основные команды. Инструкция для начинающих.

docker run — запускает контейнер на основе указанного образа.

Docker Java основные команды. Инструкция для начинающих.

docker stop — останавливает контейнер. Можно передать опцию $(docker ps -a -q) для остановки всех запущенных контейнеров.

Docker Java основные команды. Инструкция для начинающих.

docker rm и docker rmi — команды, удаляющие контейнер и образ соответственно.

Удалить все контейнеры:

Docker Java основные команды. Инструкция для начинающих.

Контейнеры создаются на основе image (образа): то есть, это определенный шаблон в котором написано все необходимое для создания докер контейнера. Как создать этот образ? В нашем случае нам нужно будет создать файл Dockerfile в корне проекта с описанием того, что должно быть в контейнере. Так как мы не хотим где-то показывать токен бота, придется извернуться и передавать его каждый раз, когда мы захотим развертывать приложение. Более детально об этой теме почитать можно здесь и здесь.

Пишем JRTB-13

Нужно настроить быстрый и легкий процесс развертывания (деплоя) нашего приложения на сервер. То есть на машину, которая работает 24/7. За основу возьмем докер. Но задачи в нашем списке, которая бы отвечала за добавление этой функциональности, нет. Как-то я его пропустил при создании. Ничего страшного, сейчас создадим. Заходим на вкладку создания issue на гитхаб и выбираем Feature Request:

“Java-проект от А до Я”: Реализуем развертывание приложения - 5

Добавляем описание задачи, критерии его приемки, устанавливаем, к какому проекту этот issue относится и можно создавать новое issue:

“Java-проект от А до Я”: Реализуем развертывание приложения - 6

Теперь чтобы показать, что задача взята в работу, сменим статус задачи с To do на In Progress:

“Java-проект от А до Я”: Реализуем развертывание приложения - 7

Это будет сложная статья. Если будут проблемы — пишите в комментариях: я буду следить и отвечать на них в меру сил. Такой будет небольшой Customer Support 😀

Создаем Dockerfile

Что такое докерфайл? Для докера это скрипт (пошаговая инструкция), как создавать образ для докер контейнера. Для работы нашего приложения нужна JDK, причем 11-й версии. То есть, нам нужно найти докер-образ JDK 11 и добавить его в наш образ. Это что-то сродни с тем, как мы добавляем зависимость в помник. Для этого дела у докера есть DockerHub. Чтобы локально загружать образы, нужно там зарегистрироваться. После регистрации идем искать нам JDK11. Из того, что получилось найти — вот этот контейнер: adoptopenjdk/openjdk11. В описании этого контейнера есть то, что нужно для докерфайла:

FROM adoptopenjdk/openjdk11:ubi
RUN mkdir /opt/app
COPY japp.jar /opt/app
CMD ["java", "-jar", "/opt/app/japp.jar"]

Поправим папку, из которой мы берем jar файл. У нас он находится в target папке после того, как мы запускаем mvn package задачу мавена. Перед тем, как все это делать, на основе обновленной main ветки создаем новую, для нашей задачи: STEP_4_JRTB-13. Теперь можно работать. В корне проекта создаем файл без расширения Dockerfile и добавим внутрь следующее:

FROM adoptopenjdk/openjdk11:ubi
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]

Первая строка — на основе чего будет состоять образ — adoptopenjdk/openjdk11. Вторая строчка — добавляем аргумент в образ с именем JAR_FILE, который находится в папке target. Причем нынешняя папка определяется по месту Dockerfile. Третья строка — копируем в докер-образ jar нашего проекта. Последняя строка по сути содержит массив, созданный из команды в терминале, которую разделили по пробелу. То есть, в итоге будет выполнено следующее: “java -jar /app.jar” Чтобы держать в тайне токен бота, при запуске контейнера нам нужно будет передавать две переменные — имя бота и его токен. Для этого напишем запрос, который должен запустить наш проект с переменными. А как это сделать? Нужно загуглить: вот первая ссылка с нормальным описанием. А что мы хотим сделать? У нас в application.properties файле есть две переменные, которые мы там определяем:

  • bot.username
  • bot.token

Я хочу запускать докер контейнер и каждый раз передавать туда свое значение, чтобы никто не видел эти значения. Я знаю, что в SpringBoot переменные окружения, которые задаются в момент запуска jar проекта, будут более приоритетнее чем те, которые находятся в файле application.properties. Чтобы передать переменную в запросе, нужно добавить следующую конструкцию: -D{имя переменной}=”{значение переменной}”. Фигурные скобки не дописываем 😉 Получим запрос, при котором будет запущено наше приложение с предопределенными значениями — имя и токена бота: java -jar -Dbot.username=”test.javarush.community_bot” -Dbot.token=”dfgkdjfglkdjfglkdjfgk” *.jar Теперь нужно передать эти переменные внутрь докер контейнера. Это environment variable. Чтобы в будущем у нас база данных работала четко и без проблем с нашим приложением, будем использовать docker-compose. Это отдельный инструмент, в котором можно упорядочить работу, запуск и зависимости между контейнерами. Иными словами, это надстройка над докером, чтобы управлять контейнерами одной инфраструктуры. Плюс перед тем, как запустить docker-compose, нужно быть уверенным, что мы стянули все изменения кода с сервера, собрали приложение и остановили старую версию. Для этого будем использовать баш скрипт. Ух… Звучит все непросто, согласен. Но работа с настройкой развертывания приложений — это всегда муторный и сложный процесс. Поэтому у нас вырисовывается нехилая схема:

  1. Запускаем баш скрипт.
  2. Баш скрипт запускает docker-compose.
  3. Docker-compose запускает docker контейнер с нашим приложением.
  4. Docker контейнер запускает наше приложение.

И вот нужно сделать так, чтобы две переменные — имя бота и его токен — прошли из 1 пункта в 4. Причем так, чтобы эти две переменные использовались при запуске нашего java-приложения. Пойдем с конца в начало. Мы уже знаем, какую команду нужно выполнить, чтобы запустить джарник. Поэтому будем настраивать Dockerfile, чтобы он научился принимать две переменные и передавать их в запрос. Для этого приведем Dockerfile к следующему виду:

FROM adoptopenjdk/openjdk11:ubi
ARG JAR_FILE=target/*.jar
ENV BOT_NAME=test.javarush_community_bot
ENV BOT_TOKEN=1375780501:AAE4A6Rz0BSnIGzeu896OjQnjzsMEG6_uso
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java", "-Dbot.username=${BOT_NAME}", "-Dbot.token=${BOT_TOKEN}", "-jar", "/app.jar"]

Видно, что мы добавили две строки и обновил ENTRYPOINT. Строки:

ENV BOT_NAME=test.javarush_community_bot
ENV BOT_TOKEN=1375780501:AAE4A6Rz0BSnIGzeu896OjQnjzsMEG6_uso

объявляют переменные внутри кодер файла. По умолчанию у них значение указано. Если при создании образа из этого докерфайла будут переданы переменные окружения с такими именами, значения будут другие. А в ENTRYPOINT мы добавили еще несколько элементов, которые будут считывать эти переменные среды:

"-Dbot.username=${BOT_NAME}", "-Dbot.token=${BOT_TOKEN}"

Здесь видно, что внутри строки при помощи ${} конструкции будут переданы значения BOT_NAME и BOT_TOKEN. Далее нам нужно научить получать и передавать эти переменные в docker-compose.

Создаем docker-compose.yml

Хорошо бы вам про YAML формат почитать отдельно, а то статья и так уже растет, как на дрожжах. Для нас это просто еще одно описание переменных по типу .properties. Только в пропертях записывается через точку, а в YAML это делается немного красивее. Например, так. Две переменные в .properties: javarush.telegram.bot.name=ivan javarush.telegram.bot.token=pupkin А вот в .yaml (тоже самое что и .yml) будет это так:

javarush:
	telegram:
		bot:
		  name: ivan
		  token: pupkin

Второй вариант более красивый и понятный. Пробелы должны быть именно такие, как указаны выше. Как-нибудь переведем наши application.properties и application.yml. Для начала нужно его создать. В корне проекта создаем файл docker-compose.yml и записываем туда следующее:

version: '3.1'

services:
 jrtb:
   build:
     context: .
   environment:
     - BOT_NAME=${BOT_NAME}
     - BOT_TOKEN=${BOT_TOKEN}
   restart: always

Первая строка — это версия docker-compose. services: говорит о том, что все следующие строки после этого (будут сдвинуты) — относятся к сервисам, которые мы настраиваем. У нас такой пока только один — java-приложение под названием jrtb. И уже под ним будут все его настройки. Например, build: context: . говорит о том, что мы будем искать Dockerfile в той же директории, что и docker-compose.yml. А вот секция environment: будет отвечать за то, чтобы мы передали в Dockerfile необходимые переменные среды (environment variables). Как раз то, что нам и нужно. Поэтому ниже мы переменные и передаем. Их docker-compose будет искать в переменных операционной среды сервера. Добавим их в баш скрипте.

Создаем баш скрипты

И последний шаг — создать баш скрипт. Создаем в корне проекта файл с именем start.sh и пишем туда следующее:

#!/bin/bash

# Pull new changes
git pull

# Prepare Jar
mvn clean
mvn package

# Ensure, that docker-compose stopped
docker-compose stop

# Add environment variables
export BOT_NAME=$1
export BOT_TOKEN=$2

# Start new deployment
docker-compose up --build -d

Первая строка нужна для всех баш скриптов: без нее работать не будет. А далее — просто набор команд в терминале, которые нужно выполнить. Я добавил комментарии в каждой команде, поэтому должно быть понятно. Единственное, что хочется объяснить — это то, что значит $1 и $2. Это две переменные, которые будут переданы в запуске баш скрипта. При помощи команды export они будут добавлены в переменные сервера и считаны уже в docker-compose. Это работает для убунты, для виндоуса, наверно, нет, но я не уверен. Теперь нужно добавить скрипт stop.sh, который будет останавливать работу. В нем будет несколько строк:

#!/bin/bash

# Ensure, that docker-compose stopped
docker-compose stop

# Ensure, that the old application won't be deployed again.
mvn clean

Здесь мы останавливаем docker-compose и зачищаем джарник проекта, который лежит еще с прошлой сборки. Делаем мы это для того, чтобы наш проект точно пересобирался. Были прецеденты, поэтому и добавляю) В итоге у на получается 4 новых файла:

  • Dockerfile — файл для создания образа нашего приложения;
  • docker-compose.yml — файл с настройкой того, как мы будем запускать наши контейнеры;
  • start.sh — баш скрипт для развертывания нашего приложения;
  • stop.sh — баш скрипт для остановки нашего приложения.

Также обновим версию нашего приложения с 0.2.0-SNAPSHOT на 0.3.0-SNAPSHOT. Добавим в RELEASE_NOTES описание к новой версии и немного отрефакторим то, что было:

# Release Notes ## 0.3.0-SNAPSHOT * JRTB-13: added deployment process to the project ## 0.2.0-SNAPSHOT * JRTB-3: implemented Command pattern for handling Telegram Bot commands ## 0.1.0-SNAPSHOT * JRTB-2: added stub telegram bot * JRTB-0: added SpringBoot skeleton project И в README добавим новый параграф с описанием того, как деплоить наше приложение:

## Deployment Deployment process as easy as possible: Required software: – terminal for running bash scripts – docker – docker-compose to deploy application, switch to needed branch and run bash script: $ bash start.sh ${bot_username} ${bot_token} That’s all. Разумеется, все пишет на английском. Уже как обычно, в нашей новосозданной ветке STEP_4_JRTB-13 создаем новый коммит с именем: JRTB-13: implement deployment process via docker и делаем пуш. Я перестаю подробно останавливаться на вещах, которые я уже описывал в прошлых статьях. Не вижу смысла повторять одно и тоже. К тому же, кто разобрался и сделал у себя, у того вопросов не возникнет. Это я о том, как создать новую ветку, как создать коммит, как запушить коммит в репозиторий.

Заключение

В этом руководстве мы попробовали самостоятельно работать с Docker. Для этого создали простой Spring проект с одной конечной точкой REST и собрали для него образ Docker. В результате научились запускать образ Docker внутри контейнера и протестировали конечную точку REST внутри этого образа.

Источник1

Источник2

+1
3
+1
4
+1
0
+1
0
+1
0

Ответить

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