Hi all. We continue the series of articles on writing your project.
Sort the branches
Of the importance, in order not to get lost in the branches and their order in the repository, I decided to rename them by adding the STEP_{number} prefix . For example, we have three branches in addition to the main one:- JRTB-0
- JRTB-2
- JRTB-3
- STEP_1_JRTB-0 - first step
- STEP_2_JRTB-2 - second step
- STEP_3_JRTB-3 - third step
A little about Docker
What is Docker? In short, it is a tool with which you can quickly and securely deploy (deploy) applications, creating a closed infrastructure for them, necessary only for them. It's still difficult, I understand. In general, Docker can be understood as a development platform where you can work quickly and efficiently. Docker can be understood as a program that runs on a server. This program has the ability to store containers with applications. What is a container? This is a separate infrastructure into which you can add everything you need. For example, for a Java application we need a JRE to run the application, the container will have this, we will need some other software - we can add this. Or maybe we need Linux and a Tomcat servlet container. This can also be done. Containers are created based on an image: that is, this is a specific template that contains everything necessary to create a Docker container. How to create this image? In our case, we will need to create a Dockerfile at the root of the project describing what should be in the container. Since we don't want to expose the bot's token anywhere, we'll have to resort to passing it along every time we want to deploy the application. You can read more about this topic here and here .We write JRTB-13
We need to set up a quick and easy deployment process for our application to the server. That is, for a machine that works 24/7. Let's take Docker as a basis. But there is no task on our list that would be responsible for adding this functionality. Somehow I missed it when creating it. No problem, we’ll create it now. We go to the issue creation tab on GitHub and select Feature Request: Add a description of the task, criteria for its acceptance, set which project this issue belongs to and you can create a new issue: Now, to show that the task has been accepted for work, change the status of the task from To do on In Progress: This is going to be a difficult article. If you have any problems, write in the comments: I will monitor and answer them to the best of my ability. This will be a small Customer Support :DCreating a Dockerfile
What is a dockerfile? For Docker, this is a script (step-by-step instructions) on how to create an image for a Docker container. For our application to work, we need the JDK, version 11 at that. That is, we need to find the JDK 11 docker image and add it to our image. This is something akin to how we add a dependency to a memory. Docker has DockerHub for this purpose . To download images locally, you need to register there. After registration, let's go look for JDK11. From what I found, this is the container: adoptopenjdk/openjdk11 . The description of this container has what is needed for the dockerfile:FROM adoptopenjdk/openjdk11:ubi
RUN mkdir /opt/app
COPY japp.jar /opt/app
CMD ["java", "-jar", "/opt/app/japp.jar"]
Let's fix the folder from which we take the jar file. We have it in the target folder after we run the mvn package maven task. Before doing all this, based on the updated main branch, we create a new one for our task: STEP_4_JRTB-13 . Now you can work. In the root of the project, create a file without the Dockerfile extension and add the following inside:
FROM adoptopenjdk/openjdk11:ubi
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
The first line is what the image will be based on - adoptopenjdk/openjdk11. The second line is to add an argument to the image named JAR_FILE, which is located in the target folder. Moreover, the current folder is determined by the location of the Dockerfile. Third line - copy the jar of our project into the docker image. The last line essentially contains an array created from the command in the terminal, which was separated by space. That is, in the end the following will be executed: “java -jar /app.jar” To keep the bot token secret, when starting the container we will need to pass two variables - the name of the bot and its token. To do this, we will write a query that should launch our project with variables. And how to do it? You need to Google it: here is the first link with a normal description. What do we want to do? We have two variables in the application.properties file that we define there:
- bot.username
- bot.token
- Let's run the bash script.
- The bash script runs docker-compose.
- Docker-compose launches a docker container with our application.
- The Docker container runs our application.
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"]
You can see that we added two lines and updated ENTRYPOINT. Lines:
ENV BOT_NAME=test.javarush_community_bot
ENV BOT_TOKEN=1375780501:AAE4A6Rz0BSnIGzeu896OjQnjzsMEG6_uso
declare variables inside the encoder file. By default they have a value specified. If, when creating an image from this dockerfile, environment variables with such names are passed, the values will be different. And in ENTRYPOINT we added a few more elements that will read these environment variables:
"-Dbot.username=${BOT_NAME}", "-Dbot.token=${BOT_TOKEN}"
Here you can see that inside the line, using the ${} construct, the values of BOT_NAME and BOT_TOKEN will be passed. Next, we need to teach how to receive and pass these variables to docker-compose.
Create docker-compose.yml
It would be good for you to read about the YAML format separately, otherwise the article is already growing by leaps and bounds. For us, this is just another description of variables of the .properties type. Only in properties it is written through a dot, but in YAML this is done a little more beautifully. For example, like this. Two variables in .properties: javarush.telegram.bot.name=ivan javarush.telegram.bot.token=pupkin But in .yaml (the same as .yml) it will be like this:javarush:
telegram:
bot:
name: ivan
token: pupkin
The second option is more beautiful and understandable. The spaces should be exactly as indicated above. Let's translate our application.properties and application.yml somehow. First you need to create it. In the root of the project, create a file docker-compose.yml and write the following there:
version: '3.1'
services:
jrtb:
build:
context: .
environment:
- BOT_NAME=${BOT_NAME}
- BOT_TOKEN=${BOT_TOKEN}
restart: always
The first line is the docker-compose version. services: says that all the following lines after this (will be shifted) refer to the services that we are configuring. We have only one of these so far - a java application called jrtb . And already under it will be all its settings. For example, build: context: . says that we will look for the Dockerfile in the same directory as docker-compose.yml. But the environment: section will be responsible for ensuring that we pass the necessary environment variables to the Dockerfile. Just what we need. Therefore, we pass variables below. Docker-compose will look for them in the server operating environment variables. Let's add them to the bash script.
Creating bash scripts
The last step is to create a bash script. Create a file called start.sh in the root of the project and write the following there:#!/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
The first line is needed for all bash scripts: it won’t work without it. And then - just a set of commands in the terminal that need to be executed. I've added comments in each command so it should be clear. The only thing I want to explain is what $1 and $2 mean. These are two variables that will be passed when the bash script is launched. Using the export command, they will be added to server variables and read in docker-compose. This works for Ubuntu, probably not for Windows, but I'm not sure. Now you need to add the stop.sh script, which will stop the work. It will contain several lines:
#!/bin/bash
# Ensure, that docker-compose stopped
docker-compose stop
# Ensure, that the old application won't be deployed again.
mvn clean
Here we stop docker-compose and clean up the project jarnik, which has been lying around since the last build. We do this to ensure that our project is rebuilt accurately. There were precedents, that’s why I’m adding) As a result, we end up with 4 new files:
- Dockerfile - a file for creating an image of our application;
- docker-compose.yml - a file with settings for how we will launch our containers;
- start.sh - bash script for deploying our application;
- stop.sh is a bash script to stop our application.
# 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
And in the README we will add a new paragraph describing how to deploy our application:
## 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.
Of course, everything is written in English. As usual, in our newly created branch STEP_4_JRTB-13 we create a new commit with the name: JRTB-13: implement deployment process via docker and push it. I stop dwelling in detail on things that I have already described in previous articles. I don't see the point in repeating the same thing. Besides, those who figured it out and did it themselves will not have any questions. This is me talking about how to create a new branch, how to create a commit, how to push a commit to the repository.
GO TO FULL VERSION