Всем привет. Продолжаем цикл статей по написанию своего проекта.
![“Java-проект от А до Я”: Реализуем развертывание приложения - 1]()
Под каждой веткой нажимаем на карандашик и переименовываем ветку:
И в результате получим:
Кстати, все, кто подписан на мой телеграм-канал, узнали сразу же, что я переименовал ветки.
Добавляем описание задачи, критерии его приемки, устанавливаем, к какому проекту этот issue относится и можно создавать новое issue:
Теперь чтобы показать, что задача взята в работу, сменим статус задачи с To do на In Progress:
Это будет сложная статья. Если будут проблемы — пишите в комментариях: я буду следить и отвечать на них в меру сил. Такой будет небольшой Customer Support :D

Сортируем ветки
Из важного, чтобы не потеряться в ветках и их порядке в репозитории, я решил их переименовать, добавив приставку STEP_{number}. Например, у нас есть три ветки помимо главной:- JRTB-0
- JRTB-2
- JRTB-3
- STEP_1_JRTB-0 — первый шаг
- STEP_2_JRTB-2 — второй шаг
- STEP_3_JRTB-3 — третий шаг



Немного о докере
Что такое Docker? Вкратце — это инструмент, с помощью которого можно быстро и безопасно развертывать (деплоить) приложения, создавая для них закрытую инфраструктуру, необходимую только для них. Пока что сложно, я понимаю. В общем и целом докер можно понимать как платформу для разработки, где можно быстро и эффективно работать. Докер можно понимать как программу, которая работает на сервере. Эта программа имеет возможность хранить контейнеры с приложениями. Что такое контейнер? Это отдельная инфраструктура, в которую можно добавить все, что нужно. Например для Java-приложения нам нужна JRE, чтобы запустить приложение, вот контейнер будет иметь это, нужно будет еще какое-то программное обеспечение — можно добавить это. А может быть, нам нужен Линукс и Tomcat сервлет контейнер. Такое тоже можно будет сделать. Контейнеры создаются на основе image (образа): то есть, это определенный шаблон в котором написано все необходимое для создания докер контейнера. Как создать этот образ? В нашем случае нам нужно будет создать файл Dockerfile в корне проекта с описанием того, что должно быть в контейнере. Так как мы не хотим где-то показывать токен бота, придется извернуться и передавать его каждый раз, когда мы захотим развертывать приложение. Более детально об этой теме почитать можно здесь и здесь.Пишем JRTB-13
Нужно настроить быстрый и легкий процесс развертывания (деплоя) нашего приложения на сервер. То есть на машину, которая работает 24/7. За основу возьмем докер. Но задачи в нашем списке, которая бы отвечала за добавление этой функциональности, нет. Как-то я его пропустил при создании. Ничего страшного, сейчас создадим. Заходим на вкладку создания issue на гитхаб и выбираем Feature Request:


Создаем 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
- Запускаем баш скрипт.
- Баш скрипт запускает docker-compose.
- Docker-compose запускает docker контейнер с нашим приложением.
- Docker контейнер запускает наше приложение.
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 — баш скрипт для остановки нашего приложения.
# 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 и делаем пуш.
Я перестаю подробно останавливаться на вещах, которые я уже описывал в прошлых статьях. Не вижу смысла повторять одно и тоже. К тому же, кто разобрался и сделал у себя, у того вопросов не возникнет. Это я о том, как создать новую ветку, как создать коммит, как запушить коммит в репозиторий.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ