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

  1. -dpMap host port 8080 to Jenkins8080Port to access the Jenkins home page while running Jenkins in the background
  2. -v jenkins-data:/var/jenkins_homeMapping to/var/jenkins_homePath, which saves Jenkins’ basic information. This ensures that the data will not be emptied after the container is run again.advicemapping
  3. -v /var/run/docker.sock:/var/run/docker.sockmappingdocker.sockFile, 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
  4. -v "$HOME":/homeMapping hosthomeDirectory to Jenkinshome
  5. --restart=alwaysWhen 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
  1. Open the Jenkins page and create a new task

  1. Task name write project name, check pipeline

  1. 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

  1. Specifies the build branch, already defaultJenkinsfileThe 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

  1. To obtaingdslDefine the file, first accesshttp://{{your_ip}}:8080/job/{{your_project_name}}/pipeline-syntax/gdslTo obtaingdslFile, copy it, into the projectsrc.main.javaCreate a directorypipeline.gdslFile, paste and copy the content.

  1. Set up IDEA to recognize Groovy support for Jenkinsfile files

  1. 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

  1. Start by defining an environment variablePACKAGE_NAMEforMavenUsed for packaging
environment {
    PACKAGE_NAME = 'My-App'
}
Copy the code
  1. defineBuildStage, 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

  1. Specify runtime stepsstepsTo definebuild.shScript, 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.

  1. writebuild.shThe 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

  1. writeTest Stage
// Unit tests
stage('Test') {
    steps {
        sh 'sh ./jenkins/scripts/test.sh'}}Copy the code

As with Build Stage, separate commands from script execution

  1. writetest.shThe 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

  1. 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

  1. defineDeploy 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
  1. 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 theopenjdk:8-jre-slimMirror build
  • Defining build parametersPACKAGE_NAMEPass the Jar package name at build time
  • Specify the working directory as/app
  • Copy the Jar package to the working directory
  • Defining the run scriptentrypoint.shTo grant the run permission.The ${@}Used by the runtime to accept arguments passed from the command. It was expected to be used directlyENTRYPOINTCommand, tried and seemingly unable to accept at the same timeARGRun-time parameters are already available, so you can only use scripts, as you can seeThis question.
  • Execute the script
  1. writedeploy.shThe 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 fromBuild StageBuild Jar package todockerdirectory
  • Start building the image, with the image name taken from the environment variable and the build parameters passedPACKAGE_NAME, specify the context asdockerdirectory
  • 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.portPass 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.

  1. 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

  1. 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.

  1. Configuring project triggers

Open the task, view the build trigger, check GitHub Hook Trigger for GITScm Polling, and save

  1. 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