# Jenkins教程 - 12 Jebkinsfile的多分支构建

在前面我们创建流水线的时候,定义选择的是 Pipeline script,然后直接写脚本。

还有一种是 Pipeline script from SCM ,这种方式我们可以在项目目录下创建 Jenkinsfile 文件,在文件中编写脚本文件,然后 Jenkins 从 Git 中拉取 Jenkinsfile 文件,然后执行文件中的脚本。这有点类似于 Dockerfilie。

我们可以使用 Jenkinsfile 来创建多分支的构建任务。

也就是我们的 SpringBoot 项目在 gitee 中有两个分支,每个分支的项目目录下都有一个 Jenkinsfile 文件,这样 Jenkins 在构建的时候,会分别拉取两个分支的代码,根据各个分支的 Jenkinsfile 文件中的脚本来执行构建,可以将不同分支的代码部署到不同的服务器。

下面简单演示一下使用 Jenkins 创建多分支构建任务。

# 12.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 用来执行停止、删除、启动容器等操作:

#!/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

在项目根目录下新建 Jenkinsfile,前面说了 Jenkins 会根据各个分支下的 Jenkinsfile 来构建项目,这样会有一个问题,各个分支进行代码合并的时候,Jenkinsfile 文件可能会产生冲突。为了避免这个问题,这里也可以各个分支的 Jenkins 分支脚本代码相同,在脚本代码里通过分支名称来区分执行的代码,例如下面这样:

pipeline {
    agent any
    stages {
        stage('构建项目') {
            steps {
                sh """
                mvn --version
                mvn clean package
                """
                echo '构建完成!'
            }
        }
        stage('传送文件') {
            steps {
                script {
                    // 根据分支名称不同,传输到不同的服务器
                    if (env.BRANCH_NAME == 'develop') {
                        echo '传输到测试服务器...'
                        // 这里按照之前编写 Pipeline 脚本的方式生成脚本来传输,只是传输的目标服务器不同
                        sshPublisher(publishers: [sshPublisherDesc(configName: 'doubibiji-server-test', 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)])

                    } else if (env.BRANCH_NAME == 'main') {
                        echo '传输到生产服务器...'
                        sshPublisher(publishers: [sshPublisherDesc(configName: 'doubibiji-server-pro', 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 {
                script {
                    // 这里两个分支只是发布到的服务器不同,其他要执行的指令都是相同的
                    if (env.BRANCH_NAME == 'develop') {
                        sshPublisher(publishers: [sshPublisherDesc(configName: 'doubibiji-server-test', 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)])
                    } else if (env.BRANCH_NAME == 'main') {
                        sshPublisher(publishers: [sshPublisherDesc(configName: 'doubibiji-server-pro', 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
41
42
43
44
45
46
47
48

如果你看了之前的 Pipeline部署SpringBoot到Docker 章节,上面的代码应该是比较简单的,也是从那个章节拷贝过来的,只是修改了部署到的业务服务器。

根据分支名称修改代码要传输到的业务服务器,我这里在Jenkins中添加了两个业务服务器,'doubibiji-server-testdoubibiji-server-pro,分别将 main 分支和 develop 分支的代码部署到这两台服务器。

# 2 将SpringBoot项目托管码云

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

项目创建两个分支,main 和 develop 分支。

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

# 12.2 创建多分支流水线任务

新建多分支流水线:

配置分支源:

先选择 Git:

配置 Git 仓库:

然后保存即可,保存后 Jenkins 会自动扫描仓库的分支并执行构建。

也可以手动使用 立即扫描 多分支流水线 触发扫描构建。

# 12.3 查看构建任务

回到构建任务页面,可以看到两个分支的构建情况。


点击进入到每个分支的详情页面,可以单独对这个分支进行构建,也可以查看到构建的情况。

查看构建情况:

# 12.4 访问项目

现在项目已经根据 Jenkinsfile 的配置将 SpringBoot 项目部署到服务器上了。可以同时访问测试环境和生产环境的服务器,都可以访问接口。此时 develop 分支和 main 分支的代码是一样的,所以访问


此时如果修改 develop 分支的代码,并提交到远程,重新执行扫描分支,或重新对 develop 分支执行构建,会发现测试环境服务器的接口发生变化了。