服务器资源与角色

身份 角色 ip 版本
服务器 Gitlab 10.0.0.133 Rocky Linux 9
服务器 Jenkins 10.0.0.134 Rocky Linux 9
服务器 Web 10.0.0.135 Rocky Linux 9
服务器 APP 10.0.0.137 Rocky Linux 9
服务器 Database 10.0.0.138 Rocky Linux 9
服务器 Harbor 10.0.0.139 Rocky Linux 9
  • 基于若依分离版(ruoyi-vue)项目的docker版发布

  • 在之前都是采用Jenkins自由风格的任务来进行发布,也就是在任务的配置中通过配置构建步骤构建后步骤,但是这种方式修改时每次需要点进具体任务,并且在修改流程时容易出错,控制台输出过长导致查日志难以定位具体错误环节

  • 因此,Jenkins推荐使用流水线(Pipeline)的方式配置任务流程,简单说就是通过结构化代码的方式定义整个 CI/CD 流程,其中常用的是声明式流水线,这种特点是结构固定、语法清晰

  • 另外,因为流水线是以代码的形式编写的,所以他另一个优势是流程可版本控制,也就是说他可以和代码一同存放到Gitlab仓库,由Jenkins拉取后读取内容在执行。

  • 综上,当你掌握Jenkins流水线的基础语法后,通过某个项目编写测试了一个完整可用的流水线后,在其他同类项目中只需要修改极少数的配置就可以将其应用到新项目。

一、pipeline基础语法

  • 按层级、语块:

1.pipeline { }

  • 声明式pipeline的核心容器,其他的所有配置代码都必须放置到这个大括号中,是固定格式

pipeline {
    // 所有流水线逻辑都写在这里面
}

2.agent

  • 代理配置:指定流水线在哪个Jenkins节点或服务器上执行,配置为any 表示 “任意可用的 Jenkins 节点”

// 一般配置在其他配置之前
agent any

3.tools { }

  • 工具配置:指定流水线需要用到的工具,前提是 Jenkins「全局工具配置」里已经配好了这些工具的名称和路径

tools {
    // 前边是工具类型,后边是在「全局工具配置」中自定义的工具名
    maven 'Maven-3.8.9'
    jdk 'JDK-17'
}

4.environment { }

  • 环境变量:流水线中全局可用的环境变量,类似编程里的 “全局变量”,可以在后续任何地方用 ${变量名} 引用。

  • 对于Jenkins的内置环境变量,通过${env.XXX}的方式引用,比如env.BRANCH_NAME是触发流水线的 Git 分支名,env.BUILD_NUMBER是流水线构建编号

environment {
    // 识别Gitlab仓库分支名
    TAG = "${env.BRANCH_NAME}"
​
    // Harbor配置
    HARBOR_ADDR = '10.0.0.139:80'
}

5.options { }

  • 全局选项:设置流水线的全局规则 / 选项。

options {
    // 不允许并行构建
    disableConcurrentBuilds()
    timestamps()
}

6.stages { }+ stage { }

  • 核心阶段:stages 是所有“阶段或步骤”的容器,每个stage是一个独立的步骤,是流水线的核心逻辑。

stages {
    stage('Maven构建后端') {
        steps {
            script {
                echo "=== 构建后端多模块项目 ==="
                sh "mvn clean package -DskipTests"
            }
        }
    }
    // 其他stage...
}

(1).when

  • 条件判断:基于一定条件控制当前stage是否执行,一般配置在具体stage最上方

stage('Maven构建后端') {
    when {
        branch 'prod*'
    }
    // 其他具体步骤steps
}

(2).steps

  • 步骤执行:其中写当前 stage 要执行的具体操作

    • echo:输出日志,与shell中的echo用法一致

    • script:声明式 Pipeline 中,script块里可以写Groovy脚本多行shell命令

    • sh:执行linux shell命令,单行用sh "命令",多行用sh """ 多行命令 """

    • dir:切换工作目录,等同于cd,如dir('ruoyi-ui')等同于cd ruoyi-ui,进入目录后执行后续命令

7.post { }

  • 后置操作:流水线执行完成后,无论成功或失败都执行的操作。

    • success:流水线全部步骤成功 执行的操作

    • failure:流水线任意步骤失败 执行的操作

post {
    success {
        echo "=== 流水线执行成功! ==="
    }
    failure {
        echo "=== 流水线执行失败! ==="
    }
}

- 声明式固定框架

  • 声明式pipeline的固定框架一般是: pipeline { agent → tools → environment → options → stages → post }

  • 因为是代码且存放到Gitlab仓库的,所以将这种文件命名为Jenkinsfile,存放点默认是项目根目录

二、普通、多分支流水线

  • Jenkins流水线任务分为普通流水线和多分支流水线,显著区别是普通流水线只管理一条分支,分支需要手动改,而多分支流水线会自动检测Gitlab的项目所有分支,并自动创建对应的任务

  • 比如在项目中有devtest-1test-2prod等分支,要构建这个项目需要建立4个普通流水线,或者建立一条多分支流水线,自动检测分支

  • 流水线一般都是读取Jenkinsfile文件后根据内容配置按阶段执行的,对于Jenkinsfile文件存放位置,普通流水线可以将文件内容直接写入任务配置,也可以存放到Gitlab仓库的Jenkinsfile文件,多分支流水线则要求必须放在项目分支中,且命名为Jenkinsfile的文件

  • 构建区别:如上,当test-1分支有代码更改需要发布时,需要在Jenkins中找到对应分支的Jenkins任务,然后点击构建,而多分支流水线任务对应了这个项目中的所有分支,所以只需要点击扫描多分支流水线,任务会自动检测分支的更改、合并等,然后检测到test-1分支有变更,会触发该分支的构建

如下,这就是多分支流水线任务在Jenkins中的展示页,这里设置了这里检测prod*也就是prod开头的分支,所以只有prod-peizhi分支是启用的,其他分支默认禁用

多分支流水线

三、流水线编写思路

  • 要编写Jenkinsfile,首先得确定流水线类型,因为在公司开发中都是多人各自拉取分支后独立开发,并且有测试、生产等多个分支,所以我们选择多分支流水线的Jenkins任务

  • 根据之前Ruoyi-Vue-Docker项目,根据pipeline的语法结构和固定框架列出任务阶段流程,而因为多分支流水线需要读取Gitlab中的Jenkinsfile文件,所以自带拉取代码的步骤,在Jenkinsfile中不必单独配置拉取阶段

触发条件:指定分支有代码提交 或 手动触发流水线
				↓
1. 选任意Jenkins节点,加载Maven和JDK
				↓
2. 定义全局环境变量,如:Harbor配置、部署服务器等
				↓
3. 执行各阶段:
   a. Maven打包后端 	→ 生成jar包;
   b. Npm打包前端 		→ 压缩成zip;
   c. 构建Docker镜像 	→ 推送到Harbor仓库;
   d. SSH部署前端zip包到前端服务器;
   e. SSH在后端服务器执行脚本	→ 拉取镜像并启动后端容器;
				↓
4. 流水线结束:成功则输出成功日志,失败则提示看日志。

四、配置具体任务

1.真实生产设计

  • 在任务配置之前,要对流水线进行操作分支的选择,这里先分析一下公司业务集群架构,才能明确这个多分支流水线具体操作哪些分支

    • 在目前的互联网公司中,web业务的开发、测试、生产都搭建有独立的服务器集群,而运维进行的就是自动化发布不同业务线的项目到对应分支

    • 生产环境的项目发布一般需要在代码合并到Gitlab上的生产分支后,提交发布请求经过领导审批同意定时发布,所以生产环境Jenkins一般只专项服务于生产服务器集群,这个Jenkins的多分支流水线任务只扫描生产分支

    因此,我们建立的这个多分支流水线任务只检测发布生产分支

2.Gitlab建立生产分支

  • 在Gitlab的Ruoyi-Vue项目仓库中,点击新建分支

  • 分支名命名为prod-peizhi,创建自之前发布过的master分支

任务配置

3.Gitlab编写Jenkinsfile

  • 在Gitlab的Ruoyi-Vue项目仓库中,选择分支prod-peizhi,点击新建文件

  • filename命名为Jenkinsfile,先编写大致流程模板而不实现,用来查看其他配置是否正确

pipeline {
    agent any
    tools {
        maven 'Maven-3.8.9'
        jdk 'JDK-17'
    }
    environment {
        TAG = "${env.BRANCH_NAME}"
    }
    options {
        disableConcurrentBuilds()
        timestamps()
    }
    stages {
        stage('Maven构建后端') {
            steps {
                script {
                    sh "mvn -v"
                    echo "=== 后端构建成功"
                }
            }
        }
        stage('Npm构建前端') {
            steps {
                script {
                    sh "npm -v"
                    echo "=== 前端构建成功"
                }
            }
        }
        stage('本地构建镜像') {
            steps {
                script {
                    echo "=== 将基于分支:${TAG} 构建镜像"
                }
            }
        }
        stage('镜像推送Harbor') {
            steps {
                script {
                    echo "=== 已推送镜像:${JOB_NAME}:${TAG}"
                }
            }
        }
        stage('部署前端') {
            steps {
                script {
                    echo "=== 前端部署成功"
                }
            }
        }
        stage('拉取镜像并运行容器') {
            steps {
                script {
                    echo "=== 已拉取镜像,运行为容器"
                }
            }
        }
    }
}
Jenkinsfile

4.创建Jenkins任务

  • Jenkins主页点击新建任务,任务名Ruoyi-Vue-Pipeline,选择多分支流水线确定

    • 分支源:点击增加源-->Git

    • 项目仓库:输入Gitlab的Ruoyi-Vue项目地址

    • 凭据:选择之前创建好的Gitlab账号密码的凭据

    • 行为:此处已自动配置好发现分支,点击Add添加根据名称过滤(支持通配符)

      • 包含表示该多分支流水线会扫描哪些分支,写入已创建的生产分支prod-peizhi

      • 排除则表示不扫描的分支,默认不写

    • Build Configuration中的mode:by Jenkinsfile就是多分支流水线默认按Jenkinsfile文件的配置执行,脚本路径默认Jenkinsfile,这也是为何需要在Gitlab项目根目录创建文件并命名为Jenkinsfile

    • 点击应用后保存,此时任务会自动扫描Gitlab仓库的所有分支,并按照配置去构建配置的生产分支

    • 点击分支名可以进入查看阶段图,点击阶段图可以查看阶段日志

Jenkinsfile

5.丰满Jenkinsfile

  • 以上简单的Jenkinsfile可以提现出多分支流水线的阶段执行特性,之后只需要按照二、流水线编写思路的流程将具体命令填写流程模板的每个步骤

  • 编辑Gitlab中Jenkinsfile,写入新的内容

pipeline {
    agent any
    tools {
        maven 'Maven-3.8.9'     // 使用名为Maven-3.8.9的Maven版本
        jdk 'JDK-17'    // 使用名为JDK-17的JDK版本
    }
    environment {
        // 读取Gitlab分支名,作为之后的docker镜像标签
        TAG = "${env.BRANCH_NAME}"

        // Harbor私有镜像仓库配置
        HARBOR_ADDR = '10.0.0.139:80'    // Harbor仓库地址(IP+端口)
        HARBOR_REPO = 'ruoyi'             // Harbor里的项目仓库名
        PROJECT_NAME = 'ruoyi-vue'        // 项目名称(作为Docker镜像名)
        HARBOR_USER = 'admin'             // Harbor登录账号
        HARBOR_PWD = 'Harbor12345'        // Harbor登录密码

        // 之前在Publish Over SSH配置的SSH服务器名称
        FRONT_DEPLOY_SERVER = 'web'    // 前端部署服务器
        BACK_DEPLOY_SERVER = 'App' // 后端部署服务器
    }
    options {
        disableConcurrentBuilds()   // 禁止并行构建
        timestamps()    // 日志中添加时间戳
    }
    stages {
        stage('Maven构建后端') {
            // 条件判断,仅当触发分支是prod开头时执行此阶段
            when {
                branch 'prod*'
            }
            steps {
                script {
                    echo "=== 构建后端多模块项目 ==="  // 输出日志,方便定位步骤
                    sh "mvn clean package -DskipTests"  // 执行Maven打包命令
                }
            }
        }
        stage('Npm构建前端') {
            steps {
                script {
                    // dir:切换工作目录到ruoyi-ui
                    dir('ruoyi-ui') {
                        // """包裹多行Shell命令
                        sh """
                            # 删除旧的前端打包压缩包(避免残留)
                            rm -rf dist*.zip
                            # 安装前端依赖:使用淘宝镜像加速,依赖安装到缓存目录
                            npm install --registry=https://registry.npmmirror.com
                            # 打包前端生产环境代码为dist目录
                            npm run build:prod
                            # 压缩dist目录,命名包含构建编号
                            zip -r dist-${BUILD_NUMBER}.zip ./dist/*
                        """
                    }
                }
            }
        }
        stage('本地构建镜像') {
            steps {
                script {
                    sh """
                        # 移动后端打包好的jar包到docker目录
                        mv ruoyi-admin/target/*.jar docker/ruoyi-admin.jar
                        # 构建Docker镜像:-t指定镜像名:标签,docker/是为build指定Dockerfile所在目录
                        docker build -t ${PROJECT_NAME}:${TAG} docker/
                    """
                }
            }
        }
        stage('镜像推送Harbor') {
            steps {
                script {
                    sh """
                        # 登录Harbor仓库
                        docker login -u ${HARBOR_USER} -p ${HARBOR_PWD} ${HARBOR_ADDR}
                        # 为镜像打Harbor标签,符合Harbor镜像命名规范
                        docker tag ${PROJECT_NAME}:${TAG} ${HARBOR_ADDR}/${HARBOR_REPO}/${PROJECT_NAME}:${TAG}
                        # 推送镜像到Harbor仓库
                        docker push ${HARBOR_ADDR}/${HARBOR_REPO}/${PROJECT_NAME}:${TAG}
                        # 清理本地镜像
                        docker rmi ${PROJECT_NAME}:${TAG}
                    """
                }
            }
        }
        // 通过SSH推送压缩包到前端服务器并解压
        stage('部署前端') {
            steps {
                script {
                    echo "=== 部署前端 ==="
                    // sshPublisher:Jenkins SSH插件,实现远程服务器操作
                    sshPublisher(publishers: [
                        sshPublisherDesc(
                            configName: FRONT_DEPLOY_SERVER,  // 指定前端部署的SSH服务器
                            transfers: [
                                sshTransfer(
                                    sourceFiles: "ruoyi-ui/dist-${BUILD_NUMBER}.zip",  // 要推送的前端压缩包
                                    removePrefix: '',  // 不删除压缩包的前缀路径
                                    remoteDirectory: '',  // 推送至远程服务器的当前目录
                                    // 远程服务器执行的命令:解压压缩包并删除源文件
                                    execCommand: """
                                        // 切换到前端部署目录-->解压并覆盖旧文件-->删除压缩包
                                        cd /usr/local/Ruoyi-Vue/ruoyi-ui && unzip -o dist-${BUILD_NUMBER}.zip && rm -rf dist-${BUILD_NUMBER}.zip

                                    """
                                )
                            ],
                            verbose: true  // 输出详细SSH操作日志
                        )
                    ])
                }
            }
        }
        stage('拉取镜像并运行容器') {
            options {
                timeout(time: 10, unit: 'MINUTES')  // 整个stage超时时间10分钟
            }
            steps {
                script {
                    echo "=== 部署后端 ==="
                    sshPublisher(publishers: [
                        sshPublisherDesc(
                            configName: BACK_DEPLOY_SERVER,  // 指定后端部署的SSH服务器
                            transfers: [
                                sshTransfer(
                                    sourceFiles: '',  // 无需推送文件
                                    removePrefix: '',
                                    remoteDirectory: '',
                                    // 远程执行部署脚本:传递Harbor地址、仓库名、项目名、镜像标签、端口等参数
                                    execCommand: """
                                        sh /usr/bin/deploy.sh ${HARBOR_ADDR} ${HARBOR_REPO} ${PROJECT_NAME} ${TAG} 8080 8080
                                    """
                                )
                            ],
                            verbose: true
                        )
                    ])
                }
            }
        }
    }
}
  • 然后点击任务的立即扫描 多分支流水线在代码成功有变动提交时候,prod-peizhi的流水线就可以看到正在构建

  • 进入服务器docker ps可以看到容器状态健康

扫描多分支流水线

三、访问测试

  • 通过浏览器访问10.0.0.135:82,可以看到验证码正常显示,登录后功能正常使用admin/admin123

更多推荐