purpose
The purpose of building this project is that if you want to publish your project online every time, you need to go through such a process operation every time.
Package the project -> upload it to the server -> Build the image -> run the container.
Therefore, it is expected to automatically complete the above process once the code is pushed through a platform like Jenkins. I encountered many problems during the construction, and spent two days to build, hoping to help some friends to avoid lightning.
This article is for reference only as a record of my study.
Project address: github.com/ylhao666/si…
The implementation process
The development environment
software | version | note |
---|---|---|
Centos | 8.2.2004 | Tencent Cloud Server |
Docker | 20.10.12 | It runs on a cloud server |
Jenkins | latest | jenkinsci/blueocean The official image |
SpringBoot | 2.6.3 | Implement simple Web applications |
Build Jenkins based on Docker
There are many ways to build Jenkins, for details, you can check the official documents. This time, the container is used to run, and Jenkins is deployed in Docker. The running statements are as follows:
docker run \
-dp 8080:8080 \
-v jenkins-data:/var/jenkins_home \
-v /var/run/docker.sock:/var/run/docker.sock \
-v "$HOME":/home \
--restart=always
jenkinsci/blueocean
Copy the code
Explain roughly what each parameter means
-dp
Map host port 8080 to Jenkins8080Port to access the Jenkins home page while running Jenkins in the background-v jenkins-data:/var/jenkins_home
Mapping to/var/jenkins_home
Path, which saves Jenkins’ basic information. This ensures that the data will not be emptied after the container is run again.advicemapping-v /var/run/docker.sock:/var/run/docker.sock
mappingdocker.sock
File, so that when executing the docker command, the response is the host, you can check the detailsThis article.This operation is the key to the deployment-v "$HOME":/home
Mapping hosthome
Directory to Jenkinshome
上--restart=always
When Docker restarts, follow the restart
After the operation is successful, visit http://your_ip:8080 and follow the instructions to complete Jenkins installation
Note: When installing plug-ins, select the recommended plug-ins to install
Create a project
Write a SpringBoot project
Next, build a simple Web application based on SpringBoot
Introduction of depend on
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
Copy the code
Write a HelloController that provides a/Hello interface for testing
@RestController
public class HelloController {
@GetMapping("hello")
public String hello(a) {
return "Hello World"; }}Copy the code
Specifies the default port to run, as 8080 is already used by Jenkins, switch to another port
server:
port: 9092
Copy the code
Specify build parameters, specify the generated Jar package name, import the SpringBoot Maven plug-in, used to build the Jar package
<build>
<finalName>My-App</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
Copy the code
Test locally, run successfully, access 127.0.0.1:9092/hello, return Hello World
Host the project on Github to be pulled by Jenkins
Github.com/ylhao666/si…
Jenkins creates the project
- Open the Jenkins page and create a new task
- Task name write project name, check pipeline
- The runtime pulls the code from the Git repository by choosing how to pull it
Note: You need to add credentials so that the code can be easily pulled, either by SSH or username & password
- Specifies the build branch, already default
Jenkinsfile
The name of the
With the initial work done on the new project, start writing Jenkinsfile and defining the pipeline logic
Write Jenkinsfile
The key to using Jenkins is to write Jenkinsfile, which requires a little shell knowledge
Automatic Jenkinsfile completion
IDEA does not support Jenkinsfile syntax reminding. You need to configure it manually to make it easier to write
- To obtaingdslDefine the file, first access
http://{{your_ip}}:8080/job/{{your_project_name}}/pipeline-syntax/gdsl
To obtaingdslFile, copy it, into the projectsrc.main.javaCreate a directorypipeline.gdslFile, paste and copy the content.
- Set up IDEA to recognize Groovy support for Jenkinsfile files
- In the project root directory, create a Jenkinsfile file and try to find that the auto-completion is already available
Note: The GDSL file obtained from Jenkins may have some unautocomplete fields
The complete pileline.gdsl file is available from the Git address
Write Jenkinsfile
The assembly line of this implementation, first of all to build Jar package, and package it into Docker image, and then run the image on the Docker container of the host computer
The full Jenkinsfile can be viewed in the appendix
Write the Build Stage
- Start by defining an environment variable
PACKAGE_NAME
forMavenUsed for packaging
environment {
PACKAGE_NAME = 'My-App'
}
Copy the code
- define
Build
Stage, which runs the build process inMavenOn the container
// Construction phase
stage('Build') {
agent {
docker {
image 'the maven: 3.6.3 - slim'
// Mount it to the host and reuse the dependent files
args '-v /root/.m2:/root/.m2'}}}Copy the code
Since our Jenkins runs on the Docker of the host and the mapping of docker.sock file is specified at run time, the Maven container running in the construction phase is running on the host. Maven :3.6.3-slim: run docker run -v /root/.m2:/root/.m2 maven:3.6.3-slim: run docker run -v /root/.m2:/root/.m2 maven:3.6.3-slim: run docker run -v /root/.m2:/root/.m2 maven:3.6.3-slim
- Specify runtime steps
steps
To definebuild.sh
Script, put all instructions in the script execution
steps {
sh 'sh ./jenkins/scripts/build.sh'
// Save the Jar package temporarily to avoid that files cannot be fetched from different agents
stash includes: '**/target/*.jar'.name: 'jar'
}
Copy the code
Note: Because different stages on the pipeline can specify different agents to run in different environments. Therefore, data is not shared among different agents. Therefore, Jar packages built under the Build Stage can be temporarily stored through stash command, and then obtained through unstash at the Deploy Stage.
- write
build.sh
The script
#Build the Jar package and skip the tests
mvn -B -DskipTests clean package
Copy the code
What build.sh does is simple, just package the project.
Write Test stages
- write
Test Stage
// Unit tests
stage('Test') {
steps {
sh 'sh ./jenkins/scripts/test.sh'}}Copy the code
As with Build Stage, separate commands from script execution
- write
test.sh
The script
# test
echo "Test"
Copy the code
This is where you can do some testing of the code, and Jenkins supports showing the results of the testing, as you can see in the official documentation
Write the Deploy Stage
- Define new environment variables
environment {
IMAGE_NAME = 'my-app'
IMAGE_VERSION = '1.0.0'
SERVER_PORT = '7072'
APP_NAME = 'My-App'
APP_VERSION = '1.0.0'
}
Copy the code
Specifies the image name, image version, port on which the service runs, application name, and application version
- define
Deploy Stage
/ / deployment container stage (' the Deploy ') {steps {/ / get the Build stage Build Jar package unstash 'Jar' sh 'sh. / Jenkins/scripts/Deploy. Sh'} post { Failure {echo "failure to deploy"}}}Copy the code
Define steps by first getting the Jar package built for the Build Stage phase from the staging and then running the deploy.sh script. Post provides different responses based on different running results. If the deployment fails, the system displays the failure message and uses email to send an alarm. For details, see Clearing and notification.
post {
failure {
mail to: '[email protected]'.subject: "Failed Pipeline: ${currentBuild.fullDisplayName}".body: "Something is wrong with ${env.BUILD_URL}"}}Copy the code
- Define Dockerfile
FROM openjdk:8-jre-slim
ARG PACKAGE_NAME
WORKDIR /app
COPY ${PACKAGE_NAME}.jar ./${PACKAGE_NAME}.jar
RUN echo "java -jar ${PACKAGE_NAME}.jar \${@}" > ./entrypoint.sh
&& chmod +x ./entrypoint.sh
ENTRYPOINT ["sh"."entrypoint.sh"]
Copy the code
In the project directory, create a docker/Dockerfile file to build the image.
Explain the contents of a Dockerfile in general
- According to the
openjdk:8-jre-slim
Mirror build - Defining build parameters
PACKAGE_NAME
Pass the Jar package name at build time - Specify the working directory as
/app
- Copy the Jar package to the working directory
- Defining the run script
entrypoint.sh
To grant the run permission.The ${@}
Used by the runtime to accept arguments passed from the command. It was expected to be used directlyENTRYPOINT
Command, tried and seemingly unable to accept at the same timeARG
Run-time parameters are already available, so you can only use scripts, as you can seeThis question. - Execute the script
- write
deploy.sh
The script
#Copy the Jar package to the docker directory
cp "target/${PACKAGE_NAME}.jar" "docker/${PACKAGE_NAME}.jar"
#Build the mirror
docker build -t "${IMAGE_NAME}:${IMAGE_VERSION}" --build-arg PACKAGE_NAME="${PACKAGE_NAME}" docker
# run container
#Delete old containers
containerId=$(docker ps -f name="${APP_NAME}-${APP_VERSION}" -aq)
if [ "${containerId}" != "" ]; then
docker rm -f "${containerId}"
fi
#Run a new container
docker run --restart=always -dp "${SERVER_PORT}:${SERVER_PORT}" --name "${APP_NAME}-${APP_VERSION}" "${IMAGE_NAME}:${IMAGE_VERSION}" --server.port="${SERVER_PORT}"
#Determines whether the container is running or not, and throws an exception
docker ps -f name="${APP_NAME}-${APP_VERSION}"
containerId=$(docker ps -f name="${APP_NAME}-${APP_VERSION}" -q)
if [ "${containerId}" = "" ]; then
exit 42
fi
Copy the code
- First, copy from
Build Stage
Build Jar package todocker
directory - Start building the image, with the image name taken from the environment variable and the build parameters passed
PACKAGE_NAME
, specify the context asdocker
directory - Get the id of the old container running in Docker according to the container name, delete the old container
- Run the new container,
${SERVER_PORT}:${SERVER_PORT}
Map running ports,"${APP_NAME}-${APP_VERSION}"
Specify the container name,--server.port
Pass run-time parameters, specifying the run port - If the container is not running, an exception will be thrown, and the pipeline will be terminated for abnormal alarm
Jenkinsfile is done here, so you can try pipelining.
All complete SH files are available in the appendix
Running line
First submit the code, then go to Jenkins’ home page and click on the project you just created
Click to open BlueOcean
Click Run to start the pipeline
You can see that the run was successful and see what each step prints
Green green Wuhu
Visit {{your_IP}}:7072/hello and return Hello World. You can see that the server has been successfully deployed
Set up Git Webhooks for automated deployment
Manual deployment has been implemented above. After submitting the code, you can automatically build and deploy it with one click. Let’s try configuring automated deployment, using Git’s Webhooks.
- Configuration Jenkins
In the Jenkis configuration, configure the Github server. The key is the credentials. You need to apply for access_token on Github. You can click here to apply for access_token. According to the official requirements, at least apply for the following permissions:
- admin:repo_hook – for managing hooks (read, write and delete old ones)
- repo – to see private repos
- repo:status – to manipulate commit statuses
Fill in the generated access_token in the certificate
You can click Connect Test to check if the configuration is successful
- Set the Webhooks
Go to your Git project address and select settings-webhooks. The url is github.com/{{your_account}}/{{your_project_name}}/settings/hooks
Click Add Webhook to Add
Payload URL Enter http://{{your_jenkins_url}}/github-webhook/ and select trigger event. When push is generated, you can click “Try to trigger” to check whether Jenkins logs have received trigger events.
- Configuring project triggers
Open the task, view the build trigger, check GitHub Hook Trigger for GITScm Polling, and save
- validation
Modify the code, push, go to the task home page, click Github Hook Log, refresh, you can see the push record
Looking at Blue Ocean, you can see that the pipeline is already running, so automated deployment is configured.
The final result
And finally, we’ve implemented the whole process of code from push to build to run.
Nice to finally not have to deploy manually
If this article has helped you, please go to 👍 and follow us.
The appendix
Jenkinsfile
Pipeline {agent any environment {APP_NAME = 'my-app' APP_VERSION = '1.0.0' PACKAGE_NAME = 'my-app'} stages {// Build Jar stage('Build') {agent {docker {image 'maven:3.6.3-slim' Reuse dependencies args' - v/root /. M2: / root/m2 '}} steps {sh 'sh. / Jenkins/scripts/build. Sh' / / temporary Jar package, Stash includes: '**/target/*.jar', name: 'jar'}} / / unit testing stage (' Test ') {steps {sh 'sh. / Jenkins/scripts/Test. Sh'}} / / deployment container stage (' the Deploy ') {environment { IMAGE_NAME = 'my-app' IMAGE_VERSION = '1.0.0' SERVER_PORT = '7072'} steps {unstash 'jar' sh 'sh . / Jenkins/scripts/deploy. Sh '} post {failure {echo "fail"}}}} / / global post post {always success {{echo "always"} echo "Success" } failure { echo "Failure" } } }Copy the code
deploy.sh
#Verify that the Jar package existsif ! test -f "target/${PACKAGE_NAME}.jar"; Then echo "${PACKAGE_NAME}.jar "then echo "${PACKAGE_NAME}.jar" "docker/${PACKAGE_NAME}.jar"
#Build the mirror${IMAGE_NAME}:${IMAGE_VERSION}" --build-arg PACKAGE_NAME="${PACKAGE_NAME}" docker
# run container
#Delete old containerscontainerId=$(docker ps -f name="${APP_NAME}-${APP_VERSION}" -aq) if [ "${containerId}" != "" ]; ${containerId}" docker rm -f "${containerId}" fi
#Run a new containerEcho "run new container, ContainerName: ${APP_NAME}-${APP_VERSION}" docker run --restart=always -dp "${SERVER_PORT}:${SERVER_PORT}" --name "${APP_NAME}-${APP_VERSION}" "${IMAGE_NAME}:${IMAGE_VERSION}" --server.port="${SERVER_PORT}"
#Determines whether the container is running or not, and throws an exceptionEcho "docker ps -f name="${APP_NAME}-${APP_VERSION}" containerId=$(docker ps -f name="${APP_NAME}-${APP_VERSION}" -q) if [ "${containerId}" = "" ]; Then echo "Container not running" exit 42 fiCopy the code