Jenkins教程


Jenkins教程

1. Jenkins介绍

Jenkins 是一款流行的开源持续集成(Continuous Integration)工具,广泛用于项目开发,具有自动化构建、测试和部署等功能。

官网: http://jenkins-ci.org/。

Jenkins的特征:

  • 开源的Java语言开发持续集成工具,支持持续集成,持续部署。
  • 易于安装部署配置:可通过yum安装,或下载war包以及通过docker容器等快速实现安装部署,可方便web界面配置管理。
  • 消息通知及测试报告:集成RSS/E-mail通过RSS发布构建结果或当构建完成时通过e-mail通知,生成JUnit/TestNG测试报告。
  • 分布式构建:支持Jenkins能够让多台计算机一起构建/测试。
  • 文件识别:Jenkins能够跟踪哪次构建生成哪些jar,哪次构建使用哪个版本的jar等。
  • 丰富的插件支持:支持扩展插件,你可以开发适合自己团队使用的工具,如gitsvnmavendocker等。

2. Jenkins安装和持续集成环境配置

2.1 Gitlab代码托管服务器安装

2.1.1 Gitlab简介

官网: https://about.gitlab.com/

GitLab 是一个用于仓库管理系统的开源项目,使用Git作为代码管理工具,并在此基础上搭建起来的web服务。

GitLab和GitHub一样属于第三方基于Git开发的作品,免费且开源(基于MIT协议),与Github类似,可以注册用户,任意提交你的代码,添加SSHKey等等。不同的是,GitLab是可以部署到自己的服务器上,数据库等一切信息都掌握在自己手上,适合团队内部协作开发,你总不可能把团队内部的智慧总放在别人的服务器上吧?简单来说可把GitLab看作个人版的GitHub。

2.1.2 Gitlab安装

官网步骤:

[!tip]

按照上诉步骤走即可。

  1. 安装相关依赖

    sudo yum install -y curl policycoreutils-python openssh-server perl
    
  2. 启动ssh服务&设置为开机启动

    sudo systemctl enable sshd
    
    sudo systemctl start sshd
    
  3. 开放ssh以及http服务,然后重新加载防火墙列表

    sudo firewall-cmd --permanent --add-service=http
    sudo firewall-cmd --permanent --add-service=https
    sudo systemctl reload firewalld
    

    [!tip]

    如果关闭防火墙就不需要做以上配置。

  4. 安装 Postfix

    接下来,安装 Postfix(或 Sendmail)来发送通知电子邮件。如果您想使用其他解决方案发送电子邮件,请跳过此步骤并在安装 GitLab 后配置外部 SMTP 服务器。

    安装之前,先查看Linux Centos查看postfix已经安装,如果安装过,则无需安装,使用命令:

    rpm -qa | grep postfix
    

    [!note]

    笔者不推荐使用postfix发送邮件,postfix相对来说比较复杂一些。官网有很丰富的邮箱服务配置,进行邮件通知。

    如果没有安装,那么就可以按照下面步骤走了。

    sudo yum install postfix
    sudo systemctl enable postfix
    sudo systemctl start postfix
    

    如果安装过程中出现如下错误:

    可以通过如下命令解决:

    wget https://repo.mysql.com/yum/mysql-5.7-community/el/7/x86_64/mysql-community-libs-compat-5.7.32-1.el7.x86_64.rpm
    
    rpm -vih mysql-community-libs-compat-5.7.32-1.el7.x86_64.rpm
    

    之后就可以正常安装了。

  5. 下载gitlab包,并且安装

    关于gitlab-ee和gitlab-ce,二者是基于同样的核心代码进行开发,只是gitlab-ee(企业版)功能更强大,但需要付费使用,有30天试用期。但试用期过后如果不付费,它就跟gitlab-ce(社区版)功能是完全一样的,只是需要付费的功能无法再继续使用而已,所以这两个版本可以随意选择安装,但如果将来有付费的打算,直接安装gitlab-ee版本是个有远见的选择。当然,即使不付费,gitlab-ee使用上和gitlab-ce没有任何区别。

    所以接下来的操作我们就以安装gitlab-ee为例进行。

    curl https://packages.gitlab.com/install/repositories/gitlab/gitlab-ee/script.rpm.sh | sudo bash
    

    这里默认下载的最新版本。
    出现下图所示,标识gitlab reposity镜像下载成功:

    curl命令执行完成后,会在/etc/yum.repos.d目录下生成 gitlab_gitlab-ee.repogitlab_gitlab-ce.repo文件。

    接下来的步骤:

    最重要的一点就是:可以安装的同时,配置git仓库访问的域名,要求域名是有效的。这里由于我暂时没有域名,所以就用IP代替。

    sudo EXTERNAL_URL="https://gitlab.example.com" yum install -y gitlab-ee
    

    [!tip]

    这里的EXTERNAL_URL替换为你自己的域名或者IP即可。

    这里你也可以暂时不用配置,直接安装即可:

    yum install -y gitlab-ee
    

    安装成功

  6. 修改GitLab配置文件

    指定服务器ip和自定义端口,使用命令:

    vim /etc/gitlab/gitlab.rb
    

  7. 重载配置

    gitlab-ctl reconfigure
    

    这个过程有点慢,因为会启动部分核心服务,请耐心等待哈。

    出现下图所示,说明reconfigure finished:

  8. 把端口添加到防火墙

    firewall-cmd --zone=public --add-port=82/tcp --permanent
    
    firewall-cmd --reload
    
  9. 启动GitLab

    gitlab-ctl start
    

    启动成功如图所示:

    可以直接访问了

[!important]

GitLab搭建(初始化)好了之后,其实系统会自动的给你生成一个管理员的账号。

账号的用户名为root;密码存放在文件:/etc/gitlab/initial_root_password中,密码不会含空格,且会在 24 小时后自动被删除

实际上,在第一次使用命令 gitlab-ctl reconfigure 初始化 GitLab 配置时,GitLab 其实已经提示过这些信息,只是很多人对英文不敏感,尤其是一长段英文中夹杂着一个重要信息时。

密码提示

2.1.3 Gitlab添加组、创建用户、创建项目

  1. 创建组

    使用管理员 root 创建组,一个组里面可以有多个项目分支,可以将开发添加到组里面进行设置权限,不同的组就是公司不同的开发项目或者服务模块,不同的组添加不同的开发即可实现对开发设置权限的管理。

    如是填写即可

  2. 创建用户

    用户管理

    用户创建好了之后,管理员可以更改所有用户的密码,用户本人,第一次登陆的时候,也是需要设置密码的。

    管理员更改用户信息

    修改用户信息

  3. 将用户添加到组中

    选择某个用户组,进行Members管理组的成员

    管理某组的用户

    邀请某个用户加入该组

    具体权限说明:

    • Guest:可以创建issue、发表评论,不能读写版本库
    • Reporter:可以克隆代码,不能提交,QA、PM可以赋予这个权限
    • Developer:可以克隆代码、开发、提交、push,普通开发可以赋予这个权限
    • Maintainer:可以创建项目、添加tag、保护分支、添加项目成员、编辑项目,核心开发可以赋予这个权限
    • Owner:可以设置项目访问权限 - Visibility Level、删除项目、迁移项目、管理组成员,开发组组长可以赋予这个权限
  4. 在用户组中创建项目

    以刚才创建的新用户身份登录到GitLab,然后在用户组中创建新的项目。

    这样项目就创建好了。

2.1.4 源码上传到GitLab仓库

[!tip]

这部分是开发者都会的,不多讲。

2.2 Jenkins安装

  1. 在安装Jenkins之前,需要安装JDK,JDK版本不同,对应的Jenkins版本也是有区别的

    JDK版本对应不同的Jenkins版本

    下载JDK这里就不赘述了。

  2. 获取jenkins安装包

    下载页面:https://jenkins.io/zh/download/

    这里我选择的是CentOS,按照他的按照步骤走即可:

    安装成功

    [!important]

    这一步安装好了之后,启动Jenkins,可能很多人在启动Jenkins的时候会失败,一定要注意JDK和Jenkins的版本比对。比如我这里我的JDK版本是17,我这里Jenkins的版本是2.462.1-1.1,查看版本比对图,可见我的JDK版本是不支持的:

    所以,这里我最高只能使用LTS版本的2.426.1版本。

  3. 安装方式1:通过war包安装和启动

    由于目前官网直接用他的下载命令,会导致下载的最新的版本,从而导致和我们JDK的版本不匹配,所以,我要去找历史的版本:

    历史版本

    下载了war包到服务器上之后,使用命令启动即可:

    java –jar war包文件 --httpPort=9000
    

    httpPort表示配置指定Jenkins端口,由于Jenkins默认端口是8080,通常8080端口可能会有占用情况,所以换成自己定义的端口9000。

    [!caution]

    这里的端口防火墙要放过,不然无法访问。

    #列出已允许的服务
    firewall-cmd --zone=public --list-services
    #允许 指定TCP 端口 通过防火墙:
    firewall-cmd --zone=public --add-port=要放过的端口/tcp --permanent
    

    之后访问启动并且Jenkins:IP:端口

    启动Jenkins

    启动Jenkins

  4. 安装方式2:通过RPM包安装和启动(推荐)

    访问地址:https://archives.jenkins.io/redhat-stable/

    版本选择

    将对应的包下载到服务器上之后,使用命令安装:

    rpm -ivh jenkins-2.426.1-1.1.noarch.rpm
    

    找到 Jenkins重要的配置文件:

    cd /usr/lib/systemd/system
    

    这个目录下有个 jenkins.service文件,这个文件就是新版Jenkins的配置文件,其余配置文件不用管。

    之后修改配置文件:

    Jenkins配置文件修改

    之后重新加载一下配置:

    systemctl daemon-reload
    

    启动命令:

    systemctl start jenkins
    

    查看Jenkins服务状态:

    systemctl status jenkins
    
  5. 跳过插件安装

    因为Jenkins插件需要连接默认官网下载,速度非常慢,而且经过会失败,所以我们暂时先跳过插件安装。

  6. 添加一个管理员账户,并进入Jenkins后台

    这样就配置OK了。

2.3 Jenkins插件管理

插件管理

Jenkins国外官方插件地址下载速度非常慢,所以可以修改为国内插件地址:

在Jenkins中,修改升级站点的配置

修改之后,在浏览器的输入栏中输入restart重启Jenkins服务,即可生效:http://192.168.254.141:9000/restart

2.4 Jenkins用户权限管理

我们可以利用Role-based Authorization Strategy插件来管理Jenkins用户权限:

当插件安装好了之后,进入安全设置:

安全设置

授权策略更改

当你点击保存之后,Jenkins管理界面就会多出一个角色管理的入口:

点击进去进行具体的角色管理:

角色管理

这里的管理员角色,最好是赋予所有权限:

这里的项目角色,也可以赋予所有权限:

当角色管理设置好了之后,现在我们创建一些用户,并且赋予对应的角色:

创建用户入口

创建用户入口

创建用户

用户创建成功

使用test1登陆

现在用户创建好了,给用户分配角色:

绑定规则如下:

  • test1用户分别绑定common全局角色和test-one项目角色
  • test2用户分别绑定common全局角色和test-two项目角色

现在角色也分配给用户了,现在我们来创建项目来测试权限:

项目创建

现在我分别登陆test1和test2查看:

test1项目列表

test2项目列表

2.5 Jenkins凭据管理

凭据可以用来存储需要密文保护的数据库密码、Gitlab密码信息、Docker私有仓库密码等,以便Jenkins可以和这些第三方的应用进行交互。

要在Jenkins使用凭证管理功能,需要安装Credentials Binding插件:

现在我们来添加凭据,点击凭据管理,之后点击全局

  • Username with password:用户名和密码
  • SSH Username with private key: 使用SSH用户和密钥
  • Secret file:需要保密的文本文件,使用时Jenkins会将文件复制到一个临时目录中,再将文件路径设置到一个变量中,等构建结束后,所复制的Secret file就会被删除。
  • Secret text:需要保存的一个加密的文本串,如钉钉机器人或Github的api token
  • Certificate:通过上传证书文件的方式

常用的凭证类型有:Username with password(用户密码)和SSH Username with private key(SSH密钥)

接下来以使用Git工具到Gitlab拉取项目源码为例,演示Jenkins的如何管理Gitlab的凭证。

[!note]

由于要拉取代码,肯定要用到Git服务,所以Jenkins的服务器以及对应的服务需要安装Git。

Jenkins安装git

服务器安装git服务:

yum install -y git 

添加凭据:

测试凭据是否可用,我们需要修改一些之前创建的项目信息:

修改之前的Jenkins项目信息

选择凭据

修改之后,点击构建,看看代码是否能够拉取下来:

拉取成功

查看工作区有两种方式:

  1. Jenkins查看

    Jenkins查看工作区

  2. 服务器查看

    服务器查看

上诉的凭据管理是基于账号密码的方式,现在再来看看基于SSH密钥的方式:

  1. 使用一个账户生成公钥和私钥,一般选择项目的创建者,或者管理员,这里我就用root

    ssh-keygen -t rsa
    

    在/root/.ssh/目录保存了公钥和使用

  2. 把生成的公钥放到GitLab中

    添加公钥

  3. 在Jenkins中添加凭证,配置私钥

    凭据管理

  4. 使用项目来测试凭据是否可用

[!note]

如果自己确定公钥已经设置到了GitLab中,私钥也设置到了Jenkins的凭据中,并且是没问题的,但是在配置Jenkins项目中的源码管理仍然报错:

则需要看一下GitLab的相关文档:https://docs.gitlab.com/17.2/ee/user/ssh.html

执行如下命令:

#1. 确认你的SSH key已经成功添加
ssh -T git@192.168.254.141 #ssh -T git@gitlab.example.com

这要出现了这个,之后,你再去Jenkins上项目配置,问题就解决了。

2.6 Jenkins整合Maven

我们知道,用Jenkins可以用来持续集成和构建Java代码,在Java项目中, 包管理工具一般都是Maven,所以,这里也需要在Jenkins服务器上安装Maven环境。

2.6.1 Maven下载以及配置

下载以及解压Maven命令此处省略….

配置Maven环境变量:

vim /etc/profile
export JAVA_HOME=/usr/local/src/JDK/jdk-17.0.11
export MAVEN_HOME=/software/maven/apache-maven-3.9.4
export PATH=$PATH:$JAVA_HOME/bin:$MAVEN_HOME/bin

重置配置:

source /etc/profile

检验环境变量是否配置成功:

mvn -v

修改Maven的settings.xml:

#创建本地仓库目录
mkdir /software/maven/repo
vim /software/maven/apache-maven-3.9.4/conf/settings.xml

修改里面的本地仓库地址以及镜像地址:

仓库地址修改

镜像地址修改


2.6.2 全局工具关联JDK以及Maven

全局工具关联JDK以及Maven

JDK配置

Maven配置


2.6.3 添加Jenkins全局变量

添加三个全局变量


测试Maven是否配置成功:

maven命令使用

保存之后构建一次项目:

可见Maven已经配置成功了

2.7 Jenkins整合Tomcat

[!tip]

由于现在Java项目基本上都是采用的SpringBoot项目,SpringBoot项目里面又内置又tomcat,所以本小节就不展开讲解。

3. Jenkins构建Maven项目

3.1 Jenkins构建项目类型介绍

  1. 自由风格项目(Freestyle Project)

    这是 Jenkins 中最基础、最常用的项目类型。它提供了基本的配置选项,用于构建、测试和部署应用程序。

    特点:

    • 配置简单,适合小型或中型项目。
    • 支持源代码管理(SCM)集成,如 Git、SVN 等。
    • 可以添加多个构建步骤,如执行 shell 脚本、调用批处理命令等。
    • 通过 Jenkins 插件,能扩展各种功能,如邮件通知、打包、发布等。
  2. Maven 项目(Maven Project)

    专门用于 Maven 构建工具的项目类型,Maven 是一个项目管理和构建自动化工具,特别适合 Java 项目。

    特点:

    • 直接集成 Maven 构建步骤,支持 Maven 生命周期(如 clean, install, deploy 等)。
    • 自动检测和解析 pom.xml 文件。
    • 支持标准的 Maven 插件和报告生成。
  3. Pipeline 项目(重点🌟🌟🌟)

    这是 Jenkins 中最强大、最灵活的项目类型,允许你通过代码定义整个构建过程。Pipeline 是用 Groovy 语言编写的脚本,定义从代码拉取到部署的整个过程。

    特点:

    • 支持 Declarative Pipeline (声明式)和 Scripted Pipeline(脚本式)两种语法:
      • Declarative Pipeline: 语法更简洁,适合大多数用例,易于阅读和维护。
      • Scripted Pipeline: 灵活性更高,可以编写复杂的 Groovy 逻辑。
    • 支持多阶段构建(如构建、测试、部署阶段)。
    • 支持并行执行和条件分支。
    • 与现代 CI/CD 流程高度集成,如 GitOps、容器化部署等。

每种类型的构建其实都可以完成一样的构建过程与结果,只是在操作方式、灵活度等方面有所区别,在实际开发中可以根据自己的需求和习惯来选择。

[!tip]

个人推荐使用**流水线类型(Pipeline )**,因为灵活度非常高

3.2 自由风格项目(Freestyle Project)

下面演示创建一个自由风格项目来完成项目的集成过程:拉取代码->编译->打包->部署

  1. 拉取代码

  2. 编译打包部署

    编译和打包之前都使用过maven命令实现过,这里的部署,由于现在的项目都是jar包了,所以直接java -jar 来执行项目

    [!note]

    这里保存之后,你出去构建,可能会出现这样的提示:

    这里暂时不用担心,我们这里只是模拟部署,并不是符合要求的部署,一般是不允许直接java -jar的,这个后面会讲到。

3.3 Maven 项目(Maven Project)

  1. 安装Maven Integration插件

  2. 创建Maven项目

  3. 配置项目

    拉取代码和远程部署的过程和自由风格项目一样,只是”构建”部分不同

最后测试,测试通过。

3.4 Pipeline流水线项目构建

Jenkins Pipeline 是一种用于定义和自动化持续集成/持续交付 (CI/CD) 流程的强大工具。通过 Pipeline,开发者可以使用代码来描述整个构建、测试、部署过程,使得流程更加灵活和可复用。Pipeline 提供了两种主要的语法:Declarative Pipeline(声明式)和 Scripted Pipeline(脚本式)。

3.4.1 Pipeline 的基本概念

  • Pipeline: Jenkins 中的 Pipeline 是一系列自动化步骤的组合。每个 Pipeline 都描述了从源代码的拉取、编译、测试到部署的整个过程。
  • Node: Jenkins 中的 Pipeline 通常运行在一个或多个 “Node” 上,这些 Node 可以是 Jenkins 主机或从机。
  • Stage: Pipeline 的核心组成部分,用于表示构建过程中的不同阶段,如 “Build”、”Test”、”Deploy” 等。
  • Step: 在每个 Stage 中具体执行的动作,如执行脚本、调用命令行工具、发送通知等。

3.4.2 Declarative Pipeline(声明式 Pipeline)

Declarative Pipeline 是一种更简单和结构化的方式来定义 Jenkins Pipeline,适合大多数用例。它使用了更易读的语法,强制执行某些最佳实践。

示例:

pipeline {
    agent any
    
    stages {
        stage('Build') {
            steps {
                echo 'Building...'
                // 示例:执行构建命令,如 `mvn clean install`
            }
        }
        
        stage('Test') {
            steps {
                echo 'Testing...'
                // 示例:执行测试命令,如 `mvn test`
            }
        }
        
        stage('Deploy') {
            steps {
                echo 'Deploying...'
                // 示例:执行部署命令,如将构建产物部署到服务器
            }
        }
    }
}

关键组件:

  • pipeline: Pipeline 块是声明式 Pipeline 的根元素,所有配置都在其中定义。
  • agent: 定义 Jenkins 运行 Pipeline 的位置。agent any 表示可以在任何可用的 Node 上运行。
  • stages: 定义了一系列的构建阶段,每个阶段由一个 stage 块表示。
  • steps: 每个 stage 中包含实际执行的步骤,使用 steps 块定义。

高级特性:

  • post:在 Pipeline 结束后执行的一些操作,如 always(无论成功与否都会执行)、success(仅在成功时执行)、failure(仅在失败时执行)等。

    post {
        always {
            echo 'This will always run'
        }
        success {
            echo 'This will run only if the pipeline succeeds'
        }
        failure {
            echo 'This will run only if the pipeline fails'
        }
    }
    
  • options: 可以为 Pipeline 设置一些全局选项,比如 timeout, retry, timestamps 等。

    options {
        timeout(time: 1, unit: 'HOURS')
        retry(3)
        timestamps()
    }
    

3.4.3 Scripted Pipeline(脚本式 Pipeline)

Scripted Pipeline 提供了更多的灵活性,可以使用 Groovy 语言的全部功能,适合需要复杂逻辑的 CI/CD 流程。

示例:

node {
    stage('Build') {
        echo 'Building...'
        // 示例:执行构建命令
    }
    
    stage('Test') {
        echo 'Testing...'
        // 示例:执行测试命令
    }
    
    stage('Deploy') {
        echo 'Deploying...'
        // 示例:执行部署命令
    }
}

关键组件:

  • node:在脚本式 Pipeline 中,node 是顶层的概念,用于指定在哪个 Node 上运行 Pipeline。
  • stage:和声明式 Pipeline 一样,stage 用于定义构建的不同阶段。
  • steps:直接在 node 块中定义的具体执行步骤。

高级特性:

  • 条件逻辑:由于 Scripted Pipeline 是基于 Groovy 语言的,可以使用条件语句、循环等控制流。
node {
    stage('Build') {
        if (env.BRANCH_NAME == 'master') {
            echo 'Building for master branch'
        } else {
            echo 'Building for feature branch'
        }
    }
}
  • 并行执行:Scripted Pipeline 可以通过 parallel 块并行执行多个任务。
node {
    stage('Test') {
        parallel(
            unitTests: {
                echo 'Running unit tests...'
            },
            integrationTests: {
                echo 'Running integration tests...'
            }
        )
    }
}

3.4.4 Pipeline 的优势

  • 版本控制:Pipeline 脚本可以存储在代码库中,与项目代码一同版本控制,确保构建和部署流程的一致性。
  • 可移植性:通过定义 Pipeline,开发者可以轻松地将 CI/CD 流程从一个 Jenkins 实例迁移到另一个实例,甚至可以在本地调试 Pipeline。
  • 可视化:Jenkins 提供了直观的 Pipeline 可视化界面,可以实时监控每个阶段的执行情况。

3.4.5 如何开始使用 Pipeline

[!tip]

  • Declarative Pipeline:是新手的首选,因为它提供了更多的结构化引导和错误检查。
  • Scripted Pipeline:适合需要复杂控制流和逻辑的高级用户
  1. 下载Pipeline插件

  2. 安装插件后,创建项目的时候多了“流水线”类型

  3. 编写一个简单声明式Pipeline

    声明式Pipeline

  4. 编译看看

    [!note]

    这里如果没有编译视图,需要安装插件:Pipeline Stage View

    插件安装好了之后,再回去看看:

    可见编译视图就有了,而且名称都是在声明式Pipeline脚本中写的名称。


之前的Pipeline语法是我们自己手动写的,在Jenkins中,其实也是可以自动生成目标Pipeline语句的:

目标步骤选择:

目标步骤选择

脚本生成

构建一下试试:

可见构建成功

3.4.6 Pipeline Script from SCM

[!tip]

SCM 是 Source Code Management(源代码管理)的缩写,是用于管理软件项目源代码的系统或工具。SCM 系统的主要功能包括代码的版本控制、变更管理、分支和合并、协作开发、历史记录追踪等。例如我们常说的GitLab、GitHub、Gitee等拥有Git功能的都是SCM。

刚才我们都是直接在Jenkins的UI界面编写Pipeline代码,这样不方便脚本维护,建议把Pipeline脚本放在项目中(一起进行版本控制)

  1. 在项目根目录建立Jenkinsfile文件,把内容复制到该文件中

  2. 把Jenkinsfile上传到Gitlab

  3. 在项目中引用该文件

构建测试成功。

3.5 Jenkins常用的构建触发器

Jenkins常见的构建触发器有如下几种:

  1. Build after other projects are built(在其他项目构建后触发):

    当某个项目构建完成后,自动触发当前项目的构建。可以用来设置项目之间的依赖关系,例如,当A项目完成后触发B项目构建。

  2. Build periodically(定时构建):

    通过定时任务在特定的时间间隔触发构建。

    使用Cron表达式来设定构建时间。定时字符串从左往右分别为: 分 时 日 月 周;例如,H/15 * * * * 表示每15分钟触发一次构建,这里的H其实是一个变量,例如现在是10:16分,则下一次构建就是10:31分,并不是整点构建的,当然,如果你想整点构建,则H就用0代替。

    常用于周期性任务,例如每晚定时触发构建,进行夜间测试。

    [!note]

    一些定时表达式的例子:

    • 每30分钟构建一次:H代表形参 H/30 * * * * 10:02 10:32
    • 每2个小时构建一次: H H/2 * * *
    • 每天的8点,12点,22点,一天构建3次: (多个时间点中间用逗号隔开) 0 8,12,22 * * *
    • 每天中午12点定时构建一次 H 12 * * *
    • 每天下午18点定时构建一次 H 18 * * *
    • 在每个小时的前半个小时内的每10分钟 H(0-29)/10 * * * *
    • 每两小时一次,每个工作日上午9点到下午5点(也许是上午10:38,下午12:38,下午2:38,下午4:38) H H(9-16)/2 * * 1-5
  3. Poll SCM(轮询SCM):

    [!caution]

    注意:这种构建触发器,Jenkins会定时扫描本地整个项目的代码,增大系统的开销,不建议使用

    定期轮询代码库(如Git、SVN),检查是否有代码变化,如果有变化,则触发构建。同样使用Cron表达式设置轮询的频率,例如每5分钟检查一次代码库是否有新提交。注意轮询并不会每次都触发构建,只有当检测到代码发生变化时才会触发。

  4. Quiet period(静默期):

    触发构建后,会等待一段设定的“静默期”后再开始构建。静默期可以防止频繁的构建触发,例如在持续提交的过程中减少不必要的构建次数。可以在项目配置中设置静默期的时长,单位是秒。

    [!caution]

    这里的触发构建需要排除手动触发构建这种情况。

  5. **Trigger builds remotely (e.g., with scripts)**(触发远程构建 (例如, 使用脚本)):

    通过HTTP请求来远程触发构建任务。常用于外部系统或脚本触发构建

    需要设置一个触发令牌(token),并通过Jenkins_URL/job/项目名/build?token=TOKEN_NAME来触发。

    常用于与其他系统集成,例如GitLab、GitHub或自定义脚本触发构建。

  6. Git hook自动触发构建

    [!tip]

    这种方式较为推荐

    当代码托管在GitLab、GitHub、Gitee等平台上时,可以配置Webhooks来触发Jenkins构建。每当有代码提交到仓库时,对应平台会通知Jenkins触发构建。

    这里需要安装插件,这里我使用的是GitLab,所以需要安装一个GitLab的插件

    GitLab插件

3.5.1 Build after other projects are built(在其他项目构建后触发)

这种构建,会在前一个项目构建后才会触发,所以这里我们这里新建一个前置项目:pre-demo

现在我们先手动构建一下pre-demo,然后回来看看pipeline-demo是否触发自动构建:

测试成功。

3.5.2 Build periodically(定时构建)

这相当于是一种定时任务,通过写cron表达式来触发构建,现在我们测试每2分钟触发构建:

可见确实是如期构建的。

3.5.3 轮询SCM(Poll SCM)

现在我们改一下代码,然后上传Git,看看他是否会构建:

我代码上传的时间是11:34,由于我轮询SCM的时间设置的是每3分钟,所以下一次自动触发构建大概时间就在11:37,我们看看:

自动构建触发

可见确实是这个时间。

3.5.4 静默期(Quiet period)

触发构建后,会等待一段设定的“静默期”后再开始构建。

现在我在54分的时候手动构建了一下pre-demo

手动构建pre-demo

此时在返回去看pipeline-demo,可见确实还没构建,而是要等静默期过了,再触发构建。

等待静默期之后触发构建

[!caution]

注意,静默期构建手动触发以及与定时构建想结合起来是没有用的,仍然会在手动触发构建的时候构建,以及到达定时构建配置的时候定时触发构建。

3.5.5 触发远程构建 (例如, 使用脚本)(Trigger builds remotely (e.g., with scripts))

触发远程构建是通过HTTP请求来触发的,所以,现在我们访问:JENKINS_URL/job/pipeline-demo/build?token=token,然后回来看看是否触发了自动构建:

请求如下地址触发构建

测试成功。

3.5.6 Git hook自动触发构建

刚才我们看到在Jenkins的内置构建触发器中,轮询SCM可以实现Gitlab代码更新,项目自动构建,但是该方案的性能不佳。那有没有更好的方案呢? 有的。就是利用Gitlab的webhook实现代码push到仓库,立即触发项目自动构建。

之后来到GitLab中设置:

[!tip]

如下GitLab的设置需要管理员权限。

来到Admin area –>Settings–>Network–>Outbound requests

之后来到你需要集成的项目中:

点击测试:

如果出现这个报错,表示没有权限,这里其实是Jenkins服务阻止了请求,去Jenkins中设置修改一下:

再次点击测试查看:

可见自动构建成功。

3.6 Jenkins的参数化构建

在Jenkins中,”This project is parameterized” 是一个非常有用的选项,允许你为构建任务定义参数化的构建。这意味着在构建开始之前,用户可以提供一些输入值,这些值会作为构建过程中的参数使用。这使得构建任务更加灵活,可以根据不同的输入条件进行不同的操作。

参数化构建的作用:

  1. 灵活构建:通过参数化,用户可以在每次构建时提供不同的输入,控制构建流程或输出的内容。比如,你可以让用户选择不同的环境(开发、测试、生产)来部署应用,或选择不同的分支进行构建。

  2. 减少项目数量:通过参数化,可以避免为每种构建场景创建单独的项目。一个项目可以通过不同的参数值来执行不同的构建任务。

  3. 提高可重用性:参数化使得构建脚本和流程可以在多个场景中重复使用,提高了项目的可维护性和可扩展性。

常见的参数类型:

  • String Parameter:允许用户输入一个字符串,通常用作文件名、版本号等。
  • Boolean Parameter:提供一个布尔值选择框(勾选或不勾选),可用于控制构建中的某些开关。
  • Choice Parameter:提供一组选项供用户选择,常用于选择环境或部署策略。
  • File Parameter:允许用户上传一个文件,构建过程中可以使用该文件。
  • Run Parameter:允许用户选择某个已执行的构建结果作为参数。
  • Password Parameter:用于输入密码,密码会被隐藏。
  • Multi-line String Parameter:允许用户输入多行字符串,常用于输入复杂的配置或脚本。

在构建脚本(如Shell脚本、批处理命令、Groovy脚本)中,你可以通过 ${PARAMETER_NAME} 这样的方式引用参数值。

例如,如果你定义了一个名为 ENV 的字符串参数,你可以在Shell脚本中使用 echo "Deploying to ${ENV} environment"

接下来演示通过输入GitLab项目的分支名称来部署不同分支项目。

  1. 创建新分支dev:

  2. 通过参数来构建不同的分支

    对于流水线的类型,如果你是寻找的SCM的方式,如下指定分支需要更改:

  3. 改动项目中的Pipeline脚本

    pipeline脚本参数化

    这里的参数名称要与在Jenkins中配置的参数名称一致

  4. 选择不同分支编译构建

  5. 测试构建


还有一种也能够选择不同分支的构建方式:

  1. 安装插件

  2. 选择Git参数

  3. 配置Git参数

  4. 流水线定义

这样才能够正常运行。

3.7 Jenkins邮件整合

[!tip]

这里暂时不准备讲解,因为我们公司目前是用的企业微信通知,所以这里我只讲解企业微信。

3.9 Jenkins+SonarQube代码审查

3.9.1 安装SonarQube

SonarQube是一个用于管理代码质量的开放平台,可以快速的定位代码中潜在的或者明显的错误。目前支持java、C#、C/C++、Python、PL/SQL、Cobol、JavaScrip、Groovy等二十几种编程语言的代码质量管理与检测。

官网:https://www.sonarqube.org/

环境要求:

我这里选择安装10.6的版本,这里面有一些环境的要求需要提前阅读一下。

Java版本需求:

Java版本需求

数据库版本需求:

数据库版本需求

这里后面我将使用PG数据库。

[!tip]

数据库以及JDK的安装这里就不展开讲解了。

PG数据库安装教程可以参考:https://juejin.cn/post/7108728363472617486

我这里已经有PostgreSQL数据库了,所以我们直接使用PostgreSQL,创建一个数据库:sonar

sonar数据库创建

之后参照官网说明:

在之前创建了sonar数据库的基础上,继续执行如下命令:

#切换数据库
\c sonar
create schema sonar;
CREATE USER sonarqube WITH PASSWORD 'sonar';
GRANT USAGE ON SCHEMA sonar TO sonarqube;
GRANT CREATE, CONNECT, TEMPORARY ON DATABASE sonar TO sonarqube;
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA sonar TO sonarqube;
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA sonar TO sonarqube;
ALTER DEFAULT PRIVILEGES IN SCHEMA sonar GRANT ALL ON TABLES TO sonarqube;
ALTER DEFAULT PRIVILEGES IN SCHEMA sonar GRANT ALL ON SEQUENCES TO sonarqube;
GRANT ALL PRIVILEGES ON SCHEMA sonar TO sonarqube;

之后下载sonar压缩包,准备安装sonar。

sonar下载地址:https://www.sonarsource.com/products/sonarqube/downloads/

这里有多个版本可以选择,我们直接选择社区版,他是免费的:

社区版选择

这里下载的是一个ZIP的压缩包,需要将下载好的文件上传到Linux中,然后使用解压文件:

unzip sonarqube-10.6.0.92116.zip

现在来修改配置:

数据库修改

sonar web端口修改

这里我由于我使用的是9998端口,如果有防火墙,需要放行这个端口:

firewall-cmd --zone=public --add-port=9998/tcp --permanent
firewall-cmd --reload

从这里开始,就是配置的重点了,很多坑

  1. 你必须使用创建一个sonar用户来启动sonar,否则启动会报错

    useradd sonar
    
  2. 更改sonar相关文件夹的权限,这里直接更改用户组和所属组,即可

    chown -R sonar sonar安装目录
    
  3. 启动命令

    脚本启动

    sh sonar.sh start #启动
    sh sonar.sh status #查看状态
    sh sonar.sh stop #停止
    
  4. 如果启动过程中报错

    如果启动过程中报错,可以先看看sonar.log这个日志文件,然后结合es.log来看,看是不是es启动失败,导致sonar启动失败。

    比如如下sonar.log报错:

    sonar.log

    此时可知是ES报错了,我们再来看看es.log:

    es.log

    从上诉报错可知,不能使用root用户来启动sonar。

    再比如如下sonar.log报错:

    sonar.log

    此时又是ES报错了,我们再来看看es.log:

    es.log

    可见是ES需要的配置比较高,当前服务器的配置不能满足,所以我们查看官网的解决办法:

    也就是以root用户执行如下命令:

    sysctl -w vm.max_map_count=524288
    
    sysctl -w fs.file-max=131072
    
    ulimit -n 131072
    
    ulimit -u 8192
    

    然后切换到非系统用户,比如我这里的sonar,执行查看:

    sysctl vm.max_map_count
    
    sysctl fs.file-max
    
    ulimit -n
    
    ulimit -u
    

    这个就能够解决ES配置不足的问题。

    最后再比如如下sonar.log报错:

    sonar.log

    此时也是ES报错了,我们再来看看es.log,再看了一圈之后,也没有发现ERROR级别的错误,这时候,我发现logs目录下多了几个日志文件,其中比较重要的是:web.log,他表示以及再执行web相关的配置了,我们再来看看这个日志:

    可见报错了,再往下找找,找到根本的报错原因:

    看到这个基本上能够断定你的数据库配置相关有问题了,当时我是只创建了sonar数据库,没有创建对应的模式,所以这里报错了。

    再比如如下报错:

    表示你新建的数据库角色对于数据库这个模式的权限不够,我这里执行的权限参考我本小节的数据库配置那段,基本上不会出现问题。

这是我目前搭建sonar发现的坑,后续发现可再补充。


现在访问IP:端口即可使用SonarQube了:

[!tip]

初始化账号密码均为admin。

3.9.2 Jenkins中SonarQube基本配置

Jenkins使用SonarQube步骤如下:

  1. Jenkins调用SonarScanner
  2. SonarScanner提交审查结果到SonarQube
  3. SonarQube保存审查结果到数据库

从上面步骤可知,我们需要一个SonarScanner,这个其实不用特别去安装,只需要在Jenkins中安装插件即可:

插件安装好了之后,我们需要在来到凭证管理中添加一个SonarQube凭证,这个凭证需要在SonarQube创建:

凭证生成

[!important]

这个凭证需要保存下来,他只显示一次,后面就不显示了。sqa_949de1c87d2d472e48cd4fc05cee93a8a12d5ad5

Jenkins生成SonarQube凭证

之后来到系统配置配置SonarQube:

之后来到工具配置中配置SonarQube Scanner:

之后回到SonarQube中关闭审查结果上传SCM功能:

3.9.3 在项目添加SonaQube代码审查(非流水线项目)

先创建一个非流水线的项目:sonar-porject

配置文件:

#项目的唯一标识,不能重复
sonar.projectKey=test-project 

#项目在SonarQube上的显示名称
sonar.projectName=test-project 

#版本,这有助于跟踪不同版本的代码质量
sonar.projectVersion=1.0 

#扫描哪个项目的源码
sonar.source=. 

#排除那些文件扫描
sonar.exclusions=**/test/**,**/target/** 

#设置为 UTF-8,以确保正确解析和分析代码文件的内容。
sonar.sourceEncoding=UTF-8 

# 指定编译后的 .class 文件的路径
sonar.java.binaries=target/classes

之后构建你会看见如下内容:

最终构建成功了,去SonarQube上看看:

审查成功

3.9.4 在项目添加SonaQube代码审查(流水线项目)

在之前的项目中添加一个sonar相关的配置文件:

#项目的唯一标识,不能重复
sonar.projectKey=test-project 

#项目在SonarQube上的显示名称
sonar.projectName=test-project 

#版本,这有助于跟踪不同版本的代码质量
sonar.projectVersion=1.0 

#扫描哪个项目的源码
sonar.source=. 

#排除那些文件扫描
sonar.exclusions=**/test/**,**/target/** 

#设置为 UTF-8,以确保正确解析和分析代码文件的内容。
sonar.sourceEncoding=UTF-8 

# 指定编译后的 .class 文件的路径
sonar.java.binaries=target/classes

之后修改Jenkins文件,加入代码审查的脚本:

pipeline {
    agent any

    stages {
        stage('pull code') {
            steps {
                checkout scmGit(branches: [[name: '*/${branch}']], extensions: [], userRemoteConfigs: [[credentialsId: '7c2082d3-6825-48ce-81a1-04941585a40c', url: 'http://192.168.3.46:82/test-group/test-project.git']])
            }
        }
        stage('SonarQube代码审查') {
            steps{
                script {
                    scannerHome = tool 'sonarqube-scanner'
                }
                withSonarQubeEnv('SonarQube10.6') {
                    sh "${scannerHome}/bin/sonar-scanner"
                }
            }
        }
        stage('master分支编译构建') {
            steps {
                sh 'mvn clean package -Dmaven.test.skip=true'
            }
        }
        stage('project deploy') {
            steps {
                echo 'master项目部署'
            }
        }
    }
}

这里有几个注意点:

  1. SonarQube代码审查一般放在拉取代码之后,部署之前,强烈推荐放在maven编译构建之后

  2. 在script代码块中,执行了语句:tool 'sonarqube-scanner',其含义是 Jenkins 中配置的一个工具,它返回 SonarQube Scanner 的安装路径,并将路径存储在 scannerHome 变量中。

    这里tool 后面要的名称要与之前在Jenkins中tool配置的一致:

  3. 在script之后的语句中:withSonarQubeEnv('SonarQube10.6'),这个命令是在 SonarQube 的环境中执行接下来的脚本。withSonarQubeEnv 接受一个参数,这个参数是 SonarQube 服务器的名字,你在 Jenkins 中配置的 SonarQube 服务器名为:

    之后执行脚本:sh "${scannerHome}/bin/sonar-scanner",通过 Shell 执行 SonarQube Scanner 命令。${scannerHome}/bin/sonar-scanner 指向 SonarQube Scanner 的可执行文件路径,并使用该路径运行代码审查。

这些都准备好了之后,就可以去Jenkins创建一个流水线项目然后构建了。

4. Jenkins+Docker+SpringCloud微服务持续集成

Jenkins+Docker+SpringCloud持续集成大致流程说明:

  1. 开发人员每天把代码提交到Gitlab代码仓库
  2. Jenkins从Gitlab中拉取项目源码,编译并打成jar包,然后构建成Docker镜像,将镜像上传到Harbor私有仓库。
  3. Jenkins发送SSH远程命令,让生产部署服务器到Harbor私有仓库拉取镜像到本地,然后创建容器。
  4. 最后,用户可以访问到容器

[!tip]

这里需要额外安装Docker以及Harbor,Docker以及Harbor的安装就不多讲了,可以去看我往期Docker专栏(https://blog.cqwulyj.cn/posts/bd770737.html)。

[!note]

默认账户:admin/你harbor.yml配置的密码。

Harbor的项目分为公开和私有的:

  • 公开项目:所有用户都可以访问,通常存放公共的镜像,默认有一个library公开项目。
  • 私有项目:只有授权用户才可以访问,通常存放项目本身的镜像。

我们可以为微服务项目创建一个新的项目:

创建项目

新建成员

添加成员

权限说明:

以下是 Harbor 项目成员的权限表格:

角色/权限 创建项目 删除项目 拉取镜像 推送镜像 删除镜像 管理成员 管理Webhook 查看日志
项目管理员
开发者
访客
受限访客

表格中展示了 Harbor 中不同角色的项目权限分配情况,具体权限如下:

  • 创建项目:是否有权限创建新项目。
  • 删除项目:是否有权限删除现有项目。
  • 拉取镜像:是否有权限从项目中拉取镜像。
  • 推送镜像:是否有权限向项目中推送镜像。
  • 删除镜像:是否有权限删除项目中的镜像。
  • 管理成员:是否有权限管理项目成员。
  • 管理Webhook:是否有权限管理项目的 Webhook 配置。
  • 查看日志:是否有权限查看项目的操作日志。

之后用新创建的用户登陆。

将微服务的前后端代码上传GitLab:

4.1 Jenkins从GitLab上拉取源码

在Jenkins上新建一个pipeline项目:

在微服务的根目录下创建Jenkinsfile文件

pipeline {
    agent any

    stages {
        stage('pull code') {
            steps {
                checkout scmGit(branches: [[name: '*/master']], extensions: [], userRemoteConfigs: [[credentialsId: '7c2082d3-6825-48ce-81a1-04941585a40c', url: 'http://192.168.3.50:82/test-springcloud/test-springcloud.git']])
            }
        }
    }
}

我的后端微服务完整的项目结构如下:

微服务整体项目结构说明

4.2 微服务提交到SonarQube审查

为每一个真正能启动的微服务添加sonar-project.properties文件:

[!tip]

这里的真正能启动的意思是有启动类的微服务,那些公共模块等没有启动类的微服务无需添加。

sonar-project.properties文件模版:

#项目的唯一标识,不能重复
sonar.projectKey=

#项目在SonarQube上的显示名称
sonar.projectName=

#版本,这有助于跟踪不同版本的代码质量
sonar.projectVersion=1.0 

#扫描哪个项目的源码
sonar.source=. 

#排除那些文件扫描
sonar.exclusions=**/test/**,**/target/** 

#设置为 UTF-8,以确保正确解析和分析代码文件的内容。
sonar.sourceEncoding=UTF-8 

# 指定编译后的 .class 文件的路径
sonar.java.binaries=target/classes

之后,返回Jenkins项目中进行修改:

参数配置

[!important]

可见我这里加了一个参数化配置,为什么呢?因为SonarQube的审查执行只能从sonar-project.properties所在的目录运行sonar- scanner

如果你不这样做,就会出现如下报错:

并且你仔细观察我的参数配置,有部分是这样的:

这是因为我的项目结构如下:

根据自己的项目结构动态调整即可。

修改Jenkinsfile脚本文件:

pipeline {
    agent any

    stages {
        stage('pull code') {
            steps {
                checkout scmGit(branches: [[name: '*/master']], extensions: [], userRemoteConfigs: [[credentialsId: '7c2082d3-6825-48ce-81a1-04941585a40c', url: 'http://192.168.3.50:82/test-springcloud/test-springcloud.git']])
            }
        }
        stage('master分支编译构建') {
            steps {
                sh 'mvn clean package -Dmaven.test.skip=true'
            }
        }
        stage('SonarQube代码审查') {
            steps{
                script {
                    scannerHome = tool 'sonarqube-scanner'
                }
                withSonarQubeEnv('SonarQube10.6') {
                    sh """
                        cd ${project_module}
                        ${scannerHome}/bin/sonar-scanner
                    """
                }
            }
        }
    }
}

[!important]

同理,因为SonarQube的审查执行只能从sonar-project.properties所在的目录运行sonar- scanner;所以,这里的脚本和之前略有不同:

之后我们来简单构建一下各个模块:

可见基本上都是通过了的。

4.3 使用Dockerfile编译生成镜像

4.3.1 Dockerfile文件提供

在Docker中,应用都是部署在容器中的,而容器又由镜像生成,镜像则通常是通过Dockerfile文件构建的,所以微服务与Docker整合的第一步就是要提供Dockerfile文件。

之前我们说了项目的整体结构:参考4.1小节,我们针对每一个真正能启动的微服务的根目录下提供Dockerfile文件:

#FROM java:8
FROM openjdk:8-jdk-alpine
ARG JAR_FILE
COPY ${JAR_FILE} app.jar
EXPOSE 23121
ENTRYPOINT ["java","-jar","/app.jar"]

文件解释:

  1. #FROM java:8

    这一行被注释掉了,表示它不会被执行。最初可能打算使用 java:8 作为基础镜像,但后来改用了 openjdk:8-jdk-alpine。

  2. FROM openjdk:8-jdk-alpine

    这行指令指定了基础镜像。openjdk:8-jdk-alpine 是一个基于 Alpine Linux 的 OpenJDK 8 镜像。Alpine 是一个非常轻量级的 Linux 发行版,所以这个镜像比较小,适合用来运行 Java 应用。

  3. ARG JAR_FILE

    这行定义了一个构建时的参数 JAR_FILE,它可以在 docker build 命令中通过 --build-arg JAR_FILE=your-jar-file.jar 来传递。在构建时,这个参数会被替换为你指定的值。

  4. COPY ${JAR_FILE} app.jar

    这行指令将构建上下文中的 ${JAR_FILE} 文件复制到镜像的 /app.jar 中。${JAR_FILE}会被替换为你在构建时指定的 JAR 文件的名称。

  5. EXPOSE 23121

    这行指令声明容器将暴露的端口 23121。这个端口通常是应用程序监听的端口。需要注意的是,EXPOSE 只是在 Dockerfile 中标记端口,而不会实际地将它暴露给外部网络。实际访问需要在运行容器时使用 -p 选项映射端口。

    [!caution]

    这里不同的微服务的端口要不同。

  6. ENTRYPOINT [“java”,”-jar”,”/app.jar”]

    这行定义了容器启动时的入口命令。当容器启动时,java -jar /app.jar 会被执行,用来运行 app.jar 文件。

[!tip]

具体的Dockerfile文件解读,可以参考我往期Docker的专栏:https://blog.cqwulyj.cn/posts/55d16a1a.html#

例如:

4.3.2 dockerfile-maven-plugin插件提供

在每个真正能够启动的微服务项目的pom.xml加入dockerfile-maven-plugin插件:

<build>
      <plugins>
          <plugin>
              <groupId>org.springframework.boot</groupId>
              <artifactId>spring-boot-maven-plugin</artifactId>
              <executions>
                  <execution>
                      <id>repackage</id>
                      <goals>
                          <goal>repackage</goal>
                      </goals>
                  </execution>
              </executions>
          </plugin>
          <plugin>
              <groupId>com.spotify</groupId>
              <artifactId>dockerfile-maven-plugin</artifactId>
              <version>1.3.6</version>
              <configuration>
                  <repository>${project.artifactId}</repository>
                  <buildArgs>
                      <JAR_FILE>target/${project.build.finalName}.jar</JAR_FILE>
                  </buildArgs>
              </configuration>
          </plugin>
      </plugins>
  </build>

[!tip]

这里的如图:

4.3.3 修改Jenkinsfile构建脚本

添加构建步骤:

stage('编译,构建镜像') {
    steps {
        script {
            // 编译并安装公共模块
            sh """
                mvn clean install -pl personal-blog-commons,personal-blog-model,personal_blog-feign -am -Dmaven.test.skip=true
            """
            // 编译目标模块并构建 Docker 镜像
            sh """
                mvn -f ${project_module}/pom.xml clean package -Dmaven.test.skip=true dockerfile:build
            """
        }
    }
}

完整如下:

pipeline {
    agent any

    stages {
        stage('pull code') {
            steps {
                checkout scmGit(branches: [[name: '*/master']], extensions: [], userRemoteConfigs: [[credentialsId: '7c2082d3-6825-48ce-81a1-04941585a40c', url: 'http://192.168.3.50:82/test-springcloud/test-springcloud.git']])
            }
        }
        stage('master分支编译构建') {
            steps {
                sh 'mvn clean package -Dmaven.test.skip=true'
            }
        }
        stage('SonarQube代码审查') {
            steps{
                script {
                    scannerHome = tool 'sonarqube-scanner'
                }
                withSonarQubeEnv('SonarQube10.6') {
                    sh """
                        cd ${project_module}
                        ${scannerHome}/bin/sonar-scanner
                    """
                }
            }
        }
        stage('编译,构建镜像') {
            steps {
                script {
                    // 编译并安装公共模块
                    sh """
                        mvn clean install -pl personal-blog-commons,personal-blog-model,personal_blog-feign -am -Dmaven.test.skip=true
                    """
                    // 编译目标模块并构建 Docker 镜像
                    sh """
                        mvn -f ${project_module}/pom.xml clean package -Dmaven.test.skip
                        =true dockerfile:build
                    """
                }
            }
        }
    }
}

这里的编译,构建镜像这步我简单解释一下:

  1. 因为微服务有些模块没有启动类,相当于作为一种工具和公共服务的,但是其他有启动类的微服务又是必须依赖这些工具或公共微服务的,所以,在构建真正的微服务之前,需要先将工具或公共等没有启动类的微服务给使用maven来编译打包为一个jar包,所以上部分为先做的编译并安装公共模块:

    mvn clean install -pl personal-blog-commons,personal-blog-model,personal_blog-feign -am -Dmaven.test.skip=true
    

    -pl:参数用于指定要构建的模块或项目列表(以逗号分隔)。这适用于多模块项目中的情况。

    -am:当你使用 -pl(–projects)参数指定了一个或多个模块时,-am 参数会确保这些模块所依赖的其他模块也会一并被构建。这在多模块项目中尤其有用,可以避免手动逐个编译依赖模块的麻烦。

    举个例子:

    假设你有一个多模块项目,结构如下:

    parent-project
    │
    ├── module-a
    │   └── pom.xml
    ├── module-b (depends on module-a)
    │   └── pom.xml
    └── module-c (depends on module-b)
        └── pom.xml
    

    如果你运行以下命令:

    mvn clean install -pl module-c -am
    

    maven 会执行以下操作:

    • 首先编译 module-a,因为 module-b 依赖于它。

    • 然后编译 module-b,因为 module-c 依赖于它。

    • 最后编译你指定的 module-c 模块。

    这保证了 module-c 在构建时所有依赖模块都已经编译并安装到本地仓库了。

    简单来说就是:**-am 参数确保在构建某个模块时,Maven 会同时构建它所依赖的其他模块,从而避免由于未构建依赖模块而导致的构建失败问题。**

  2. 在保证公共模块编译打包为jar包之后,我们就要针对每一个微服务进行docker的镜像构建了,不够在构建之前,也是需要将这个微服务打包为jar包的,所以前面先执行了:

    mvn -f ${project_module}/pom.xml clean package -Dmaven.test.skip=true
    

    其中mvn 命令中的 -f 参数用于指定一个特定的 POM 文件 (pom.xml) 来执行构建,而不是默认的当前目录下的 pom.xml 文件。这个参数的完整形式是 –file。

    使用场景:**当你需要从一个非当前目录的 pom.xml 文件进行构建时,可以使用 -f 参数。 **

    例如:

    mvn -f /path/to/your/pom.xml clean install
    

    之后使用命令:

    dockerfile:build
    

    这是一个 Maven 插件目标:

    • 这里的 dockerfile:build 指的是使用 Maven 的 Dockerfile 插件来构建 Docker 镜像。
    • dockerfile 是插件的 artifactId,build 是这个插件的目标(goal)。执行这个目标时,Maven 会根据项目目录中的 Dockerfile 构建 Docker 镜像。

在提供了Dockerfile–>dockerfile-maven-plugin–>Jenkins脚本修改之后,我们去Jenkins分别构建打包:

Jenkins构建情况

我们的微服务镜像其实就已经构建好了:

镜像构建情况


4.4 上传镜像到Harbor仓库

现在我们修改Jenkins脚本:

//定义一些全局变量
def HarborUrl = "192.168.3.50:80" //Harbor服务器的地址,跟上端口
def HarborProjectName = "personal-blog" //Harbor服务上的项目地址
def tag = "latest" //镜像的版本
pipeline {
    agent any

    stages {
        //之前的阶段省略
        stage('上传镜像到Harbor'){
            steps {
                script {
                    def imageName = "${project_module.split('/').last()}:${tag}"
                    def fullImageName = "${HarborUrl}/${HarborProjectName}/${imageName}"
                    sh """
                        set -e  # 当命令失败时,脚本将立即退出

                        # 给镜像打标签
                        docker tag ${imageName} ${fullImageName}

                        # 登录Harbor并且上传镜像
                        echo "Logging into Harbor..."
                        echo Harbor123456 | docker login -u nxz --password-stdin ${HarborUrl}
                        echo "Pushing image to Harbor..."
                        docker push ${fullImageName}

                        # 删除本地镜像
                        echo "Removing local images..."
                        docker rmi -f ${imageName} || true  # 忽略镜像不存在的错误
                        docker rmi -f ${fullImageName} || true  # 忽略镜像不存在的错误

                        # 清理本地的悬空镜像(none)
                        echo "Removing dangling images..."
                        docker rmi \$(docker images -f "dangling=true" -q) || true  # 忽略没有悬空镜像的错误
                    """
                }
            }
        }
    }
}

这里有个细节:

因为我的参数project_module有些是多层级的:

但是我的docker构建镜像的时候,不是多层级的,所以,如果这里我不这样处理:project_module.split('/').last(),那么这里在打标签的时候就会出错。

[!note]

总结下来就是这里要根据自己的情况来定,不同的项目,不同的构建方式,这里都会导致不同。

同时,这里登陆Harbor可能会报错:

这是因为Docker没有把Harbor加入受信列表中,只需要编辑文件:/etc/docker/daemon.json,将Harbor服务器的地址加入其中即可:

之后重启Docker服务即可:

systemctl daemon-reload
systemctl restart docker

之后再次使用Jenkins构建,最后我这里是构建成功,查看Harbor仓库:

可见镜像也都上传成功了。

4.5 拉取镜像以及发布应用

[!tip]

有些公司的生产部署服务器和Jenkins服务器不是一个服务器,是分开的,并且部署项目是用的shell脚本来部署的,此时脚本存在于生产服务器,与Jenkins服务器没有在一个服务器,所以,此时就需要Jenkins有远程执行shell脚本的功能,此时需要安装插件:

后续的配置自己可以在网上查询资料。因为我这里是一个服务器,所以我不用远程执行shell。

当前服务器上的镜像:

现在编写部署脚本:

#接收外部参数
harbor_url=$1
harbor_project_name=$2
project_module=$3
tag=$4
port=$5
imageName=$harbor_url/$harbor_project_name/$project_module:$tag
echo "接收到到参数:harbor_url = $harbor_url ; harbor_project_name = $harbor_project_name ; project_module = $project_module ; tag = $tag ; port = $port ; imageName = $imageName"

#查询容器是否存在,存在则删除
containerId=`docker ps -a | grep -w ${project_module}:${tag} | awk '{print $1}'`
if [ "$containerId" != "" ] ; then
  #停掉容器
  docker stop $containerId
  #删除容器
  docker rm $containerId
  echo "成功删除容器"
fi

#查询镜像是否存在,存在则删除
imageId=`docker images | grep -w $project_module | awk '{print $3}'`
if [ "$imageId" != "" ] ; then
  #删除镜像
  docker rmi -f $imageId
  echo "成功删除镜像"
fi

# 登录Harbor私服
docker login -u nxz -p Harbor123456 $harbor_url
# 下载镜像
docker pull $imageName
# 启动容器
docker run -di -p $port:$port $imageName

echo "容器启动成功"

修改Jenkins文件:

def HarborUrl = "192.168.3.50:80"
def HarborProjectName = "personal-blog"
def tag = "latest"
pipeline {
    agent any

    stages {
        //其他阶段省略
        stage('项目部署'){
            steps {
                script {
                    sh """
                        sh deploy.sh ${HarborUrl} 
                        ${HarborProjectName} ${project_module.split('/').last()} ${tag} ${port}
                    """
                }
            }
        }
    }
}

注意:我的部署脚本和Jenkins文件都在项目的根目录下,所以才有上图的执行,如果目录不一样,脚本的执行也是需要自己更改的。

deploy脚本目录

又由于是微服务项目,相当于不同的微服务启动是需要不同的端口的,同时我们上部分也是用的port变量,所以,这里我们要去Jenkins也要配置port参数:

构建之后:

可见确实是先拉取的镜像,也可以去Harbor上看下载次数有没有新增:

我们的脚本中还包括运行镜像的命令,看看这个微服务是否启动了:

可见没有启动起来,我们输入命令:

docker logs 镜像ID

可见确实是去启动了,只是nacos没有启动,导致程序报错,所以,只是我们的流程是跑通了,这些报错自行解决即可。

4.6 部署前端Web网站

[!tip]

部署前端,首先需要nginx,关于nginx的安装,可以查看我的niginx专栏:https://blog.cqwulyj.cn/posts/34e2af00.html#

看见如下界面就代表nginx安装成功了:

nginx

在Jenkins安装NodeJS插件,因为前端项目的包都是靠NodeJS来管理的:

之后去Jenkins的工具配置中配置NodeJS:

[!caution]

这里我选择的NodeJS的版本为22,有可能你都服务器的环境并不支持这么高的版本。

例如可能会报如下错误:

错误参考:https://blog.csdn.net/qq_38225558/article/details/128641561

不过最稳当的方法还是降低NodeJS版本。

之后前端也去创建一个流水线项目:springcloud-web-demo:


[!tip]

剩下的内容基本上就是k8s相关的了,这里暂时不讨论k8s。


文章作者: 念心卓
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 念心卓 !
  目录