# Jenkins教程 - 11 Pipeline部署SpringBoot到docker容器中

现在使用 Pipeline 来部署 SpringBoot 项目,将项目部署到 Docker 容器中。

这里为了保持本章内容的独立性,之前已经配置的步骤,也重新复制了过来,如果你从前面看过来的,就当重新温习了一遍。

# 11.1 准备SpringBoot项目

# 1 新建一个SpringBoot 项目

新建一个 SpringBoot 项目,这里我就在 SpringBoot 中新建一个 Controller。

只提供了一个接口,证明 SpringBoot 能访问运行就好了。

package com.doubibiji.hellospringboot.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {

    @GetMapping("/hello")
    public String sayHello() {
        return "Hello www.doubibiji.com";
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

同时在项目下创建一个 docker 文件夹,并编写 Dockerfile 文件,后面用来生成 Docker 镜像。

Dockerfile 文件内容如下:

# 使用基础的 Java 11镜像
FROM openjdk:11

# 对外暴露的端口
EXPOSE 9000

# 设置环境变量来指定时区
ENV TZ=Asia/Shanghai
# 将时区文件复制到容器中的特定路径
RUN ln -sf /usr/share/zoneinfo/{TZ} /etc/localtime && echo "{TZ}" > /etc/timezone

# 将jar包添加到容器中并更名为app.jar
ADD hello-springboot-0.0.1-SNAPSHOT.jar app.jar

# 运行jar包
RUN bash -c 'touch /app.jar'
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

项目结构如下:

这里还有一个 deploy.sh ,用来执行停止、删除、启动容器等操作,后面介绍。

# 2 将SpringBoot项目托管码云

Jenkins 服务后面需要从 git 仓库拉取代码进行构建,这里使用 gitee 码云来托管代码,所以这里将 SpringBoot 项目托管到码云。

这里细节就不介绍了,git 不太书写的,可以学习本站的 git教程 (opens new window)

# 11.2 配置插件和工具

# 1 安装Maven插件

我们现在构建项目需要使用 Maven,所以还需要安装一个 Maven 插件。


在可用的插件中,搜索 maven,在搜索结果中选中 Maven Integration 进行安装。

拉到最下面,查看安装进度

# 2 配置Maven工具

我们的自动化构建任务是使用 Maven 构建的,需要告诉 Jenkins Maven的安装位置。


配置 Maven 的路径:

使用 mvn -v 可以查看 maven 安装的路径。

# 3 安装SSH插件

这个插件的作用就是将 Jenkins 构建的 SpringBoot 项目的 jar 包发布的业务服务器上。

还是刚才安装插件的步骤:

# 4 配置业务服务器

后面需要将构建的 SpringBoot 的 jar 包发布到业务服务器,所以在这里配置一下要发布到的业务服务器的信息。

拉到最下面找到 Publish over SSH,安装完 SSH 插件才有这个选项。


Server是可以有多个的,新增一个 SSH Server。填写SpringBoot项目要部署到的业务服务器的信息。


配置上面几个选项就可以了,配置完成,在最下面有个测试的按钮,可以测试一下配置有没有问题,没有问题,保存配置。

# 11.3 创建构建任务

# 1 新建任务

在 Jenkins 管理页面,新建Item,也就是新建构建任务。

填写任务名称,选择流水线任务:

# 2 编写脚本

编写流水线脚本,目前我们主要实现 4 个阶段:

代码如下:

pipeline {
    agent any

    stages {
        stage('拉取代码') {
            steps {
                echo '拉取代码完成!'
            }
        }
        stage('执行构建') {
            steps {
                echo '执行构建成功!'
            }
        }
        stage('发送文件') {
            steps {
                echo '发送文件成功!'
            }
        }
        stage('构建并启动容器') {
            steps {
                echo '执行启动容器完成!'
            }
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

我们在编写的时候,可以一步一步去实验,看看有没有问题,例如编写完上面的脚本,可以运行测试一下执行有没有问题。

上面各个阶段确认了,下面来编写各个阶段的执行的脚本。


# 3 编写拉取代码脚本

不会写流水线脚本,没关系,我们可以使用工具。

点击下面的流水线语法,

在新的页面提供了生成脚本的功能,这里是拉取代码,所以示例步骤选择 git: Git

填写生成流水线脚本按钮,会在下面的文本框中生成对应的脚本。


添加凭证的操作:

将生成的脚本填写到之前的步骤中:

pipeline {
    agent any

    stages {
        stage('拉取代码') {
            steps {
                git branch: 'main', credentialsId: 'gitee', url: 'https://gitee.com/doubibiji/hello-springboot.git'
                echo '拉取代码完成!'
            }
        }
        stage('执行构建') {
            steps {
                echo '执行构建成功!'
            }
        }
        stage('发送文件') {
            steps {
                echo '发送文件成功!'
            }
        }
        stage('构建并启动容器') {
            steps {
                echo '执行启动容器完成!'
            }
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

此时拉取代码的阶段就编写好了,可以执行任务测试一下,看看日志有没有执行成功。

# 4 编写执行构建脚本

下面开始编写执行构建阶段的脚本,构建是 maven 构建的,直接执行 maven 命令即可,但是 maven 需要配置一下:

pipeline {
    agent any
  
    tools {
        maven "maven3"  // 这里的maven3的名字是和前面配置maven工具的时候指定的名称是一致的。
    }

    stages {
        stage('拉取代码') {
            steps {
                git branch: 'main', credentialsId: 'gitee', url: 'https://gitee.com/doubibiji/hello-springboot.git'
                echo '拉取代码完成!'
            }
        }
        stage('执行构建') {
            steps {
                sh """
                mvn --version
                mvn clean package
                """
                echo '执行构建成功!'
            }
        }
        stage('发送文件') {
            steps {
                echo '发送文件成功!'
            }
        }
        stage('构建并启动容器') {
            steps {
                echo '执行启动容器完成!'
            }
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

解释一下:

stage('执行构建') {
    steps {
        sh """
        mvn --version
        mvn clean package
        """
        echo '执行构建成功!'
    }
}
1
2
3
4
5
6
7
8
9

在这个步骤中就是执行了 maven 命令进行打包,maven 命令默认就是在 Jenkins 工作目录下的构建任务所在的文件夹中执行的(不清楚的可以去看构建的第一个SpringBoot项目),此时SpringBoot项目 pom.xml 文件就在 workspace/pipeline-springboot 目录下,所以直接执行构建命令。如果 pom.xml 文件不是在这个目录,则进入到所在目录即可。

""" 表示的是执行多行命令,mvn clean package 表示清理然后打包。

配置好以后,可以再执行一下,看看 Pipeline 脚本到这里是否有问题。

# 5 编写发送文件脚本

和编写拉取代码脚本类似,也是使用脚本生成工具。

因为是使用 Publish Over SSH 插件实现传输的,所以这里选择 sshPublisher:Send build artifacts over SSH

再添加一个传输配置,将 Dockerfile 和 deploy.sh 也传输到业务服务器。

这里和之前部署到Docker一样,我们将 jar 包传输到业务服务器,还编写一个构建 docker 镜像、备份、执行容器的脚本文件,也就是 deploy.sh

#!/bin/bash

# 项目名称
projectName=hello-springboot
# 年月日时分的时间戳
timestamp=$(date +%Y%m%d%H%M)
# 新镜像的名称
newImageName=$projectName-$timestamp

# 1.首先使用Dockerfile打镜像
docker build -t $newImageName .

# 2.停止并删除之前运行的容器
runningContainerId=$(docker ps | grep "$projectName" | awk '{print $1}')
# 如果有运行中的容器,停止它,并删除它
if [ -n "$runningContainerId" ]; then
  docker stop "$runningContainerId"
  docker rm "$runningContainerId"
fi
# 万一新的镜像有问题,为了恢复,就不删除之前的镜像了

# 在宿主机上创建logs目录,用于容器挂载,这样查看日志可以直接在宿主机查看,比较方便
mkdir -p ~/projects/$projectName/logs

# 3.运行新的镜像
docker run -d -p 9000:9000 --restart=always -v ~/projects/$projectName/logs:/logs $newImageName
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

deploy.sh 主要是构建镜像,并停止和删除之前的镜像,运行新的镜像。

deploy.sh 是在 SpringBoot 根目录下的 docker 文件中的,所以也会被 Jenkins 拉取到工作目录下,所以将 deploy.sh 传输到业务服务器,然后在构建并启动容器阶段,执行这个脚本。


生成脚本以后,将脚本内容添加到

pipeline {
    agent any
  
    tools {
        maven "maven3"  // 这里的maven3的名字是和前面配置maven工具的时候指定的名称是一致的。
    }

    stages {
        stage('拉取代码') {
            steps {
                git branch: 'main', credentialsId: 'gitee', url: 'https://gitee.com/doubibiji/hello-springboot.git'
                echo '拉取代码完成!'
            }
        }
        stage('执行构建') {
            steps {
                sh """
                mvn --version
                mvn clean package
                """
                echo '执行构建成功!'
            }
        }
        stage('发送文件') {
            steps {
                // 传输文件
                sshPublisher(publishers: [sshPublisherDesc(configName: 'doubibiji-server', transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: '', execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: 'projects/hello-springboot', remoteDirectorySDF: false, removePrefix: 'target', sourceFiles: 'target/*.jar'), sshTransfer(cleanRemote: false, excludes: '', execCommand: '', execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: 'projects/hello-springboot', remoteDirectorySDF: false, removePrefix: 'docker', sourceFiles: 'docker/Dockerfile,docker/deploy.sh')], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false)])
                echo '发送文件成功!'
            }
        }
        stage('构建并启动容器') {
            steps {
                echo '执行启动容器完成!'
            }
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

此时可以再执行构建,查看是否成功将文件传输到业务服务器的 ~/projects/hello-springboot 目录下。

# 6 编写构建并启动容器脚本

这里直接在远程执行脚本就可以了。

因为是在远程执行命令,所以这里还是使用 Publish Over SSH 插件的脚本生成功能生成,只是这里不传输文件,只执行脚本。

将生成的脚本添加到构建和启动容器脚本阶段:

pipeline {
    agent any
  
    tools {
        maven "maven3"  // 这里的maven3的名字是和前面配置maven工具的时候指定的名称是一致的。
    }

    stages {
        stage('拉取代码') {
            steps {
                git branch: 'main', credentialsId: 'gitee', url: 'https://gitee.com/doubibiji/hello-springboot.git'
                echo '拉取代码完成!'
            }
        }
        stage('执行构建') {
            steps {
                sh """
                mvn --version
                mvn clean package
                """
                echo '执行构建成功!'
            }
        }
        stage('发送文件') {
            steps {
                // 传输文件
                sshPublisher(publishers: [sshPublisherDesc(configName: 'doubibiji-server', transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: '', execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: 'projects/hello-springboot', remoteDirectorySDF: false, removePrefix: 'target', sourceFiles: 'target/*.jar'), sshTransfer(cleanRemote: false, excludes: '', execCommand: '', execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: 'projects/hello-springboot', remoteDirectorySDF: false, removePrefix: 'docker', sourceFiles: 'docker/Dockerfile,docker/deploy.sh')], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false)])
                echo '发送文件成功!'
            }
        }
        stage('构建并启动容器') {
            steps {
                sshPublisher(publishers: [sshPublisherDesc(configName: 'doubibiji-server', transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: '''cd ~/projects/hello-springboot
chmod +x deploy.sh
sh deploy.sh''', execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: '', remoteDirectorySDF: false, removePrefix: '', sourceFiles: '')], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false)])
                echo '执行启动容器完成!'
            }
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

现在 4 个阶段的流水线脚本都编写好了,最后再执行构建。

# 11.4 执行构建

现在再执行一遍完整的构建,构建成功,就可以访问业务服务器的 SpringBoot 项目测试的接口

重新修改代码,并推送到 gitee,然后重新使用 Jekins 重新执行构建,发现自动构建更新完成。

至此,完成了整个 Pipeline 部署 SpringBoot 项目到 Docker 容器的自动构建。


从上面的任务也可以看到,使用流水线需要学习 groovy 语言来编写脚本,这本身是很蛋疼的,这也是流水线的缺点,流水线对编程要求较高,比之前界面中操作的声明式的代码更复杂。