CI/CD详解 & Jenkins 介绍与实战

本文大纲概览:

  • CI/CD 概念与工作流程 详解
  • Jenkins安装、启动、初始化配置
  • 使用Jenkins部署github上的 xzll-im 微服务项目
    • freestyle方式
    • push代码后自动部署
    • pipeline方式部署
    • pipeline + push 方式

本文实操过程中,相关工具版本信息如下:

  • centos版本: CentOS Linux release 7.9.2009 (Core)
  • jdk: jdk-11.0.22
  • maven: 3.8.8
  • git: 1.8.3.1
  • jenkins 2.252
  • docker: 26.1.4
  • docker-compose: v2.27.2
  • 其它项目中使用到的中间件版本请见docekr-compose.yml 中的镜像版本,戳这里

什么是 持续集成(CI)& 持续交付(CD)?

大白话: 对于CI/CD的理解,可能每个人都不一样(一千个人有一千个哈姆雷特!😂),我理解他其实就是一种更加自动化的工作模式,使得开发、测试、运维之间的沟通成本降低,从之前的手动 编译、打包、构建、测试到现在的自动化、流程化去执行这些步骤,从而更加快速,及时发现问题,最终实现:加快软件迭代周期,提高交付速度与质量 的目的!

CI(持续集成) 介绍

CI全称: (Continuous Intergration)

持续集成的重点在于 构建编译测试 (这里更多的指通过脚本和自动化项目来进行的自动化测试),开发人员每天要提交很多次代码到分支,在分支合并到主干前,需要通过编译和测试识别出问题。持续集成的流程就是通过自动化的构建(主要是构建编译、自动化测试)来验证,从而尽早地发现集成错误。

CI(持续集成)的工作流程

  1. CI初始化:包括但不限于(CI服务的安装&启动,脚本、任务定义与配置,webhook设置,凭证设置,系统配置,插件等等)
  2. 开发人员从代码库中获取最新代码(pull)。
  3. 开发人员在本地进行开发和测试。
  4. 开发人员将代码提交(commit & push)到代码仓库(github、 gitlib)
  5. 通过回调方式通知CI服务器,ci服务器自动拉取最新代码并触发构建任务。
  6. 构建(build)-> 自动化测试(test)-> 反馈结果(result)
  7. 将自动化测试的结果反馈给开发人员(钉钉邮件短信等方式),如果测试失败,开发人员会及时修复代码并重新提交
  8. 构建和测试成功后,代码可以部署到进一步的测试环境或生产环境中

集成工具

集成工具有很多种(我所接触的主要还是jenkinsgitlib),包括但不限于以下:

  • Jenkins (常见):一个开源的自动化服务器,支持复杂的构建、测试和部署流水线。
  • GitLab CI/CD (常见):GitLab 内置的 CI/CD 工具,支持从代码提交到生产部署的全流程自动化。
  • CircleCI:一个基于云的持续集成和持续交付平台,支持多种编程语言和构建环境。
  • Travis CI:一个基于云的持续集成服务,特别适用于 GitHub 项目。
  • Spinnaker:一个开源的持续交付平台,支持多云环境的自动化部署

持续集成的好处

  • 提高代码质量:通过频繁的自动化测试和代码质量检查,可以早期发现并修复错误。
  • 加快交付速度:自动化构建和测试减少了手动操作的时间,可以提高开发速度从而加快交付。
  • 提高团队协作效率:开发人员可以更快地得到反馈,后期与测试人员协作更加顺畅。
  • 总之概括起来就是一句话: 省去人工操作,push后自动构建、测试(这里指自动化测试)、反馈,从而尽早的在开发环境发现代码问题并修复

CI 流程图

下边是持续集成(CI) 的流程图:

知道了CI , 那么CD又是指啥呢?接着看

CD(持续交付&持续部署) 介绍

CD 有两个意思

  1. 一是 持续交付(Continuous Delivery)
  2. 另一个是持续部署(Continuous Deployment)

说一句:其实做到持续交付就已经很不错了,持续部署听听就行了,别当真!为什么这么说? 往下看。

何为持续交付?

持续交付指的是将产品尽可能快的发布上线的过程。持续交付是在持续集成基础上的扩展 ,也就是说在成功通过dev环境的自动化部署与测试之后,为了尽快上线我们还需要自动化发布fat或者uat环境(如果需要的话),整个流程实现后,根据实际需要,可以人工控制是否发布prod环境。一般在公司都是开发环境(dev)、测试环境(fat)、预发布环境(uat)和正式生产环境(prod),如果代码在预发布环境测试通过,那么就可通过 手动方式 部署生产环境,从而尽可能的实现产品快速迭代上线。

持续交付的几个关键因素:

  • 自动化构建和测试: 与持续集成(CI)类似,持续交付(CD)依赖于自动化的构建和测试,以确保每次代码更改都能被快速验证。
  • 自动化部署: 持续交付要求自动化部署流程,这意味着每次代码更改都可以自动部署到不同的环境中(如开发、测试、预生产)。
  • 环境一致性: 确保所有环境(从开发到生产)的一致性,以减少部署问题。
  • 持续反馈: 提供持续反馈,确保开发团队及时了解相应服务在不同环境中的状态和问题。
  • 手动发布控制 : 尽管大部分部署过程都是自动化的,但 持续交付 通常 保留手动发布的控制权。发布到生产环境的最终决定由相关人员评估,可以的话再通过手动的方式来发布生产环境

持续交付流程图

何为持续部署?

持续部署是持续交付的进一步扩展。在持续部署中,所有的代码更改在通过自动化测试和验证后,都会自动部署到生产环境中。与持续交付不同,持续部署不通过手动发布来发布生产环境,而是自动发布生产环境。一般来说,非生产环境的持续部署基本都能实现。但生产环境的持续部署并不是每个企业都能做到 ,主要原因是受限于各种系统功能依赖、自动化测试不完善等因素,自动化方式部署到生产,将可能造成严重生产事故。 所以实际我觉得没几个企业这样做。能做到持续交付就已经很不错了。生产环境的部署 一定要认为控制。持续部署 听听就行了,不要玩火呀! 哈哈!😂😂😂😂😂😂😂😂😂😂😂😂

持续部署的关键要素:

  • 自动化构建、测试和部署: 持续部署依赖于完全自动化的构建、测试和部署流程,以确保每次代码更改都能被快速、可靠地部署到生产环境中。
  • 强大的精准的测试覆盖: 持续部署需要非常高的测试覆盖率,包括单元测试、集成测试、端到端测试等,以确保所有代码更改都能被全面验证。
  • 快速回滚机制: 当新代码引入问题时,必须有快速回滚的机制,以确保生产环境的稳定性。
  • 持续监控: 持续监控生产环境中的应用性能和健康状态,确保及时发现和解决问题

注意: 无论是持续集成、持续交付还是持续部署,如果要想实现,都离不开CI服务器!

ok到这里你对CI/CD有自己的理解了吗?下边我们来学习一个非常重要且常见功能丰富的CI/CD 工具 : Jenkins ! 想玩CI/CD以及再往大一点的DevOps? 那么首先玩转Jnekins是必不可少的。换句话说,其实Jenkins是CI/CD、DevOps 这些概念的 其中一个重要 "实现"


介绍完CI/CD 下边我们开始学习Jenkins

Jenkins 安装、启动、初始化

以下说明是我选择哪种方式安装jenkins的过程记录,这里也记录下来,做个备忘。

  • 说明1:为什么不使用docker安装jenkins? 其实我也想使用docker安装,但是当我从docker hub中找到对应的镜像进行下载时,发现要么是下载不下来镜像,要么是下载下来的镜像 运行时的jenkins版本过低,导致一些插件安装报错,使得我很恼火。
    • 下载不下来镜像: 到docker hub 去找到对应镜像 ,戳此进: jenkins 的 docker hub 执行docker pull 镜像 发现下载失败,都是超时。。。。

    • 版本过低插件安装不了: ,后来终于有两个镜像可以下载了 就是这个镜像:jenkins/jenkins:lts-jdk11jenkins/jenkins:latest镜像,但是却提示jenkins版本过低导致一些插件安装不了,报错如下:因为这个搞了半天,其实最主要原因可能就是欺负我的虚拟机没开vpn,哎 手头紧,暂时先不开了毕竟有点小贵,所以就决定弃启用docker安装jenkins, 我用war或RPM也行呀,真是的,😄

  • 说明2:为什么安装jenkins 2.252版本?
    • 由于在使用jenkins过程中我需要一些最新插件,而 LTS(官方长期支持版本)版本缺少对最新插件的支持,所以这里弃用LTS版本而使用较新的版本来安装部署学习,我使用的是: 2.452 (生产环境还是建议使用LTS版本来安装启动jenkins)
  • 说明3:为什么选择war包安装jenkins?
    • 首先第一原因是因为上边的说明1才迫使我使用war安装jenkins,另外因为我是centos7,所以其实我本应该使用RPM软件包方式安装jenkins,对应的版本是redhat的 LTS Release 的 redhat-stable(已发布的LTS版本)来安装jenkins 下边的引导页面戳这里,但是因为遇到了一些问题主要还是网络问题(还是欺负我虚拟机没vpn 😂😂 再欺负我真要买Strong vpn了啊 哈哈),所以我不用此方式,如果网络好(能fq)可以使用,(安装指南戳这里
    • 最终:我选择使用war包安装jenkins ,因为经过实践我发现此方式比较好用不用担心被q的问题,选择war安装的话点击下边这个LTS Release:war-stable 然后找到我要安装的版本(注意在安装此版本之前,我安装过较低版本 (LTS版本的2.361)但是我发现有些插件不能用,必须高版本才行所以这里选择较高的一个版本 2.452,ok接下来开干

ps: 其实某些场合下,docker部署不一定是最优方案。不一定要执着于所有东西都往docker堆,当然如果是开发测试环境,当我没说。

下载war、启动并初始化 jenkins

有了上边的铺垫,接下来我们首先找到2.452版本的war包:

下载war包

之后使用命令下载war包:

bash 复制代码
wget https://mirrors.jenkins-ci.org/war/2.452/jenkins.war --no-check-certificate

编写启动脚本 来启动jenkins

ok从上边截图可知下载成功,接下来编写jenkins启动脚本: 完整jenkins启动脚本在这:

bash 复制代码
#!/bin/bash

args=$1
# 注意修改jenkinswar包的目录
jenkins_war_path="/usr/local/soft_hzz/jenkins"
# jenkins开放端口
jenkins_http_port="8079"
# java安装路径
java_home="/usr/lib/jvm/java-11-openjdk-11.0.22.0.7-1.el7_9.x86_64"
# 日志文件路径
jenkins_log_path="/tmp/data/logs/jenkins.log"

function isRunning(){
    local jenkinsPID=$(ps -ef | grep jenkins.war | grep -v grep | awk '{print $2}')
    if [ -z ${jenkinsPID} ]; then
        echo "0"
    else
        echo ${jenkinsPID}
    fi
}

# 停止jenkins
function stop(){
    local runFlag=$(isRunning)
    if [ ${runFlag} -eq "0" ]; then
        echo "Jenkins is already stopped."
    else
        kill -9 ${runFlag}
        echo "Stop Jenkins success."
    fi
}

# 启动jenkins
function start(){
    local runFlag=$(isRunning)
    echo "${runFlag}"
    if [ ${runFlag} -eq "0" ]; then
        # nohup ${java_home}/bin/java -jar ${jenkins_war_path}/jenkins.war --httpPort=${jenkins_http_port} > ${jenkins_log_path} 2>&1 &

      # 对jenkins内存大小进行限制 否则总是超内存 被系统kill
        nohup ${java_home}/bin/java -Xms512m -Xmx2g -jar ${jenkins_war_path}/jenkins.war --httpPort=${jenkins_http_port} > ${jenkins_log_path} 2>&1 &
        if [ $? -eq 0 ]; then
            echo "Start Jenkins success."
            exit
        else
            echo "Start Jenkins fail."
        fi
    else
        echo "Jenkins is running now."
    fi
}

# 重启jenkins
function restart(){
    local runFlag=$(isRunning)
    if [ ${runFlag} -eq "0" ]; then
        echo "Jenkins is already stopped."
        exit
    else
        stop
        start
        echo "Restart Jenkins success."
    fi
}

# 根据输入的参数执行不同的动作
# 参数不能为空
if [ -z ${args} ]; then
    echo "Arg can not be null."
    exit
# 参数个数必须为1个
elif [ $# -ne 1 ]; then
    echo "Only one arg is required: start|stop|restart"
# 参数为start时启动jenkins
elif [ ${args} = "start" ]; then
    start
# 参数为stop时停止jenkins
elif [ ${args} = "stop" ]; then
    stop
# 参数为restart时重启jenkins
elif [ ${args} = "restart" ]; then
    restart
else
    echo "One of following args is required: start|stop|restart"
    exit 0
fi

接下来启动:

初始化jenkins

好,现在我们通过 172.30.128.65:8079访问jenkins,首次访问会出现下边这个解锁jenkins的页面,按提示找到密码并填进去点击继续就行了:

设置初始密码

将启动日志中的密码复制: 填入下边的管理员密码中:

安装jenkins推荐的插件

点击继续,之后,jenkins会推荐你安装一些插件,这里建议都安装上以免后续还得再手动安装,如下: 插件有点多需要等待一会:

在这里替换插件源(注意 : 替换后一定要重启jenkins

创建管理账号

之后提示你创建个管理员账号: 然后填写上你的jenkins地址:

完成后查看安装的插件

看到下边这个,代表 初始化工作就完成了: 在这里你可以看到你初始化时候安装的那些插件:

一些插件介绍:

插件名 作用
Pipeline 流水线部署项目
Role-based Authorization Strategy 提供了一种基于角色(Role)的用户权限管理策略,支持创建global角色、Project角色、Slave角色,以及给用户分配这些角色。这款插件是最常用的Jenkins权限策略和管理插件
Git 支持使用Github、GitLab、Gerrit等系统管理代码仓库,创建普通job时会用到
Gitee Gitee Jenkins Plugin 是Gitee基于 GitLab Plugin 开发的 Jenkins 插件。用于配置 Jenkins 触发器,接受Gitee平台发送的 WebHook 触发 Jenkins 进行自动化持续集成或持续部署,并可将构建状态反馈回Gitee平台。
Git Parameter 可用于把git的tag branch当作构建参数传进来,方便使用branch构建
Extended Choice Parameter 参数化构建
Maven Integration 这个插件为Maven 2 / 3项目提供了高级集成功能
SonarQube Scanner 代码扫描
Email Extension 扩展了发送告警邮件的控制力度。可以定义邮件触发器、邮件内容、收件人
Workspace Cleanup 每次build之前删除workspace目录下指定的文件
Monitoring 监控Jenkins节点的CPU、系统负载、平均响应时间和内存使用
Build Monitor View 将Jenkins项目以一块看板的形式呈现
ThinBackup 备份与恢复
jacoco 单元测试覆盖率
Generic Webhook Trigger webohook

使用Jenkins部署项目

这里声明个前提:我使用的代码仓库是github,gitlib、gitee的操作略有不同。

全局工具配置

非pipeline的Java项目如果要构建成功,全局工具的环境需要配置好

  • 配置入口:Manage Jenkins->Global Tool Configuration
  • 配置方式:指定服务器上已经安装好的服务位置(不需要勾选自动安装)
  • 配置前提:服务器已经安装好jdk、maven、git

首先找到我的git、maven、jdk的安装路径,如下:

bash 复制代码
mvn -version
which git 
echo $JAVA_HOME

然后点击这个进行工具添加:

添加git、maven、jdk

添加后点击应用并保存。

方式一、 使用freestyle方式部署项目

新建freestyle类型的任务

首先我们需要建一个任务,点击新建item:

然后定义任务: 简单描述一下此任务干的事情,以及勾选上github项目并填入url,如下:

配置源码管理(这里使用ssh方式与github认证)

之后往下拉来到源码管理 填入仓库地址(ssh的话是git协议),之后点击添加,点击jenkins弹出认证设置 我这里选择ssh方式认证:

之后可以填写你想要的认证方式(我这里使用ssh认证,也就是说把你github上公钥对应的私钥(私钥一般在你本地使用 cat ~/.ssh/id_rsa 命令查看),粘到下边的private key中) 当然如果没有生成过或者删除了,那么使用下边方式重新生成,并将公钥粘到github,步骤如下:

bash 复制代码
# 注意在生成前最好是先删除一下旧的

# 删除公钥和私钥
rm -f ~/.ssh/id_rsa ~/.ssh/id_rsa.pub
# 清除之前的主机秘钥
rm -f ~/.ssh/known_hosts

# 重新生成
ssh-keygen -t rsa -b 4096 -C "h163361631@163.com"

# 查看私钥
cat ~/.ssh/id_rsa

# 查看公钥
`cat ~/.ssh/id_rsa.pub`

# 测试与github的连接是否正常(在github配完公钥之后操作)
`ssh -T git@github.com`

将公钥粘到github:

在将公钥配到github后可以使用命令ssh -T git@github.com 来测试是否连接成功,如果输出: Hi xxx! You've successfully authenticated, but GitHub does not provide shell access. 则说明ssh配置没问题了。

选择 SSH Username with private key 认证方式(有好几种认证方式比如 用户名密码,access token, ssh等 我们这里使用ssh方式),并使用cat ~/.ssh/id_rsa找到私钥并粘到下图的private key中去:

之后我们填入仓库地址,以及刚刚新加的认证方式,以及要部署的分支: 然后在构建环境这里,选择jdk版本:

编写你的shell脚本(用来定义你如何构建&启动等动作)

编写shell脚本,定义启动的一些动作: shell脚本内容:

bash 复制代码
#!/bin/bash

# 使用freestyle方式启动xzll-im项目时的shell脚本,此脚本在 jenkins的  build steps处填写。

log() {
  echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"
}

exit_on_error() {
  if [ $? -ne 0 ]; then
    log "$1"
    exit 1
  fi
}

log ">>>>>>>>>>>> 开始部署 >>>>>>>>>>>>>"


# 确保 docker-compose 目录存在 不存在则创建
COMPOSE_DIR="/usr/local/soft_hzz/xzll-im/jar-file/jenkins_way_build_docker_compose/"
if [ ! -d "$COMPOSE_DIR" ]; then
  log ">>>>>>>>>>>> 目录不存在,创建目录 $COMPOSE_DIR >>>>>>>>>>>>>"
  mkdir -p "$COMPOSE_DIR" || { log "Failed to create directory $COMPOSE_DIR"; exit 1; }
fi

# 进入 docker-compose 目录
log ">>>>>>>>>>>> 进入到 docker-compose 目录中 >>>>>>>>>>>>>"
cd "$COMPOSE_DIR" || { log "Failed to enter directory $COMPOSE_DIR"; exit 1; }



# 如果项目目录不存在,则执行 clone,否则执行 pull
if [ ! -d "xzll-im" ]; then
  log ">>>>>>>>>>>> 项目目录不存在,执行 git clone >>>>>>>>>>>>>"
  git clone git@github.com:598572/xzll-im.git || { log "Git clone failed"; exit 1; }
else
  log ">>>>>>>>>>>> 项目目录已存在,进入目录并执行 git pull >>>>>>>>>>>>>"
  cd xzll-im/ || { log "Failed to enter directory"; exit 1; }
  git pull || { log "Git pull failed"; exit 1; }
  cd ..
fi

# 进入项目根目录
log ">>>>>>>>>>>> 进入项目根目录 >>>>>>>>>>>>>"
cd xzll-im/ || { log "Failed to enter project directory"; exit 1; }

git fetch

git checkout jenkins_build_20240709

# 开始打包
log ">>>>>>>>>>>> 开始打包 >>>>>>>>>>>>>"

mvn clean package -P prod || { log "Maven build failed"; exit 1; }

log ">>>>>>>>>>>> 打包完成 >>>>>>>>>>>>>"

# 确保 Docker 构建上下文包含所有 JAR 文件
declare -A JAR_FILES=(
  ["im-gateway/target/im-gateway.jar"]="im-gateway/src/main/resources"
  ["im-connect/im-connect-service/target/im-connect-service.jar"]="im-connect/im-connect-service/src/main/resources"
  ["im-auth/target/im-auth.jar"]="im-auth/src/main/resources"
  ["im-business/im-business-service/target/im-business-service.jar"]="im-business/im-business-service/src/main/resources"
  ["im-console/im-console-service/target/im-console-service.jar"]="im-console/im-console-service/src/main/resources"
)

for JAR_FILE in "${!JAR_FILES[@]}"; do
  TARGET_DIR="${JAR_FILES[$JAR_FILE]}"
  log ">>>>>>>>>>>> 复制 $JAR_FILE 到 $TARGET_DIR >>>>>>>>>>>>>"
  cp "$JAR_FILE" "$TARGET_DIR" || { log "Failed to copy $JAR_FILE to $TARGET_DIR"; exit 1; }
done


cp docker-compose.yml ../

# 返回 docker-compose 目录
log ">>>>>>>>>>>> 返回到 docker-compose 目录 >>>>>>>>>>>>>"
cd .. || { log "Failed to return to docker-compose directory"; exit 1; }




# 停止所有 docker-compose 运行的容器
log ">>>>>>>>>>>> 停止所有 docker-compose 运行的容器 >>>>>>>>>>>>>"
docker-compose down || { log "Docker-compose down failed"; exit 1; }

# 构建镜像并启动容器
log ">>>>>>>>>>>> 构建镜像并启动容器 >>>>>>>>>>>>>"
docker-compose up -d --build || { log "Docker-compose up failed"; exit 1; }

log ">>>>>>>>>>>> 部署结束 >>>>>>>>>>>>>"

# 清理 Docker 相关的垃圾
log ">>>>>>>>>>>> 清理 Docker 相关的垃圾 >>>>>>>>>>>>>"
docker system prune -f --volumes || { log "Docker system prune failed"; exit 1; }

log ">>>>>>>>>>>> 清理完成 >>>>>>>>>>>>>"

部署xzll-im项目

之后我们在这里点击build now开始构建并启动: 部署结果:

看到Finished: SUCCESS说明构建成功了(注意构建成功不等于微服务相关项目启动成功哦) 看一下启动情况(可知都正常启动了): 当然最好看下各个服务的日志,这样才能确保真的启动成功(以im-connect服务为例):

以上就是使用freestyle方式部署github项目的流程。挺简单的,但是大部分场景下不使用此方式,需要手动服务多的话比较费时费力。

方式二、 push代码后自动部署

一般在dev环境(测试环境一般不需要push即部署,因为那样影响测试稳定性)可能需要每次push后都需要让最新代码生效(也就是希望每次push后部署一遍,而如果这个工作靠人去完成的话服务多的话将会很累很烦,所以jenkins提供了个回调接口,也就是在github中配上jenkins的回调地址,当有某些事件时(一般是push事件)那么就回调jenkins 然后jenkins去进行自动部署)下边我们就看下如何 在push后让jenkins自动部署?

整个流程简单梳理为以下步骤:

  1. 首先是配置github和jenkins (详情见下边)
  2. 开发者push代码到github仓库
  3. github通过此项目设置的webhook 告知(回调)jenkins接口,哎,有push事件了啊,你看你该怎么处理。
  4. jenkins收到事件,进行处理(也就是部署项目)

在github设置webhook

webhook的url其实就是 jenkins提供的回调接口的地址

进入项目,找到setting , 找到webhooks 并点击 add webhook 如下:

注意: 由于我的虚拟机是在局域网,github在外网访问不到我的虚拟机ip, 所以需要做一下 内网穿透 ,我这里使用内网穿透 工具为 :灵曜 , 一个好用、配置简单的内网穿透工具, 戳此进官网 www.http01.cn/

内网穿透配置:

首先安装客户端(linux版本):

直粘贴脚本然后新建个目录执行就行,脚本如下: 执行脚本 如下: 完事你会在客户端这里看到你机器的信息: 之后编辑你的隧道:

暂时我先配成8079端口,也就是说通过此隧道只能访问jenkins服务(8079是jenkins端口),后续搞个nginx自己转发一下就更好了。

配置之后你可以看到分配给你的公网域名以及过期时间等等信息:

注意:(灵曜支持域名绑定,也就是说可以将你的腾讯云,阿里云域名绑定到你的隧道,并且可以设置https ,从而实现https访问方式访问你的局域网上的机器, 这里我为了简便就不做绑定了也不做https了 ,而是使用http 方式)

ok现在我们就将此公网域名配到github。

配置webhook 以及触发回调的事件(这里为push代码时)

在github配置webhook地址:(注意接口url 是 github-webhook,这是固定的) 设置好webhook后,点击add webhook,就可以看到已经添加的webhook了,注意,一定要保证图标是小对勾✅ 时说明github能访问通jenkins ,如果是红色叹号说明不行这时需要排查你的jenkins是否启动成功以及内网穿透是否好使,(当然,也可以从下边提示语知道成功与否)

之后我们还需要在github干一件事,就是生成access token ,从而让jenkins访问到github 服务(注意access token和 ssh方式不同,他可以更细粒度的控制用户的操作权限)。

接下来生成access token : 起个名字并设置此access token的相关权限: 之后点击最底部的生成,就看到下边的界面了:

(注意一定要粘贴保存起来最好是你本地防止遗忘,因为下次再进来就不展示这个token了)

ok到这一步,github设置好了,接下来就是设置jenkins了

配置jenkins

在jenkins 配置github server信息:

在jenkins的System config 添加github服务器设置:

注意想要配置这一步,你一定要安装以下插件: 配置github server:

新增access token凭证

具体如下截图: 选择刚创建的凭证并 连接测试

修改 xzll-im-freestyle-project 项目的触发器构建环境

修改xzll-im-freestyle-project项目对应的配置,改动点如下: 到此github和jenkins相关的配置就配完了,点击应用。接下来我们push代码到github仓库,试试能不能触发jenkins自动部署!

push代码测试一下jenkins能不能自动部署

随便改点东西,push上去:

观察日志,收到github回调通知

查看jenkins日志发现 收到了github的回调,如下:

观察并验证启动结果

查看jenkins,可以看到32号任务正在执行:

看一下控制台的日志:

看一下各容器的状态以及随便找一个服务看看日志: 可以看出,各容器启动正常,日志也正常都起来了。

ok到这里,就实现了 通过push 触发jenkins自动构建 的目的了。当然按照CI的概念来说,你还需要在部署后自动运行自动化测试脚本,以及将测试的结果通知给开发人员以便进行问题修复(如果有的话)。

方式三、 pipleine构建

首先你要确保这一堆pipeline插件一键安装了,想使用pipeline功能,相关插件是不可少的。

官方中文文档: www.jenkins.io/zh/doc/book...

pipeline简介与使用方式

pipeline是什么?

注:pipeline使用中文可以理解为流水线、管道。

Pipeline 是Jenkins 2.X 的最核心的特性,帮助Jenkins 实现从CI 到 CD 与 DevOps的转变。 Pipeline 是一组插件,让jenkins 可以实现持续交付管道的落地和实施。持续交付管道是将软件从版本控制阶段到交付给用户/客户的完整过程的自动化表现。

大白话说其实Pipeline就是一个:逻辑上的管道,在该管道中 你可以通过脚本 定义一系列的动作,pipeline会根据你的定义,自动化的完成整个构建、测试、交付等过程,从而实现(CD)持续交付的目的。

如何创建pipeline?

创建并定义pipeline有两种方式(这里我们简单描述下这两种方式 ,下边会进行实战演示),如下:

  1. 直接在 Jenkins Web 界面输入脚本
  2. 编写 Jenkinsfile 文件并将文件存放到项目根目录。 何为Jenkinsfile? 对Jenkins 流水线的定义可以被写在一个文本文件中 (此文件称为Jenkinsfile),该文件可以放在项目的根目录下,从而将 流水线的定义作为应用程序的一部分,像其他代码一样进行版本控制和代码审查。

在哪里配置pipeline?

  1. 将流水线定义放在名为 Jenkinsfile 的文件中,存储在项目的根目录。使用 Jenkins 的 "Pipeline from SCM" 功能从版本控制系统中读取 Jenkinsfile 并执行流水线。
  2. 直接在 Jenkins 的 Web 界面中编写和配置流水线脚本。在创建或配置一个 Pipeline 项目时,可以在 "Pipeline" 部分选择 "Pipeline script" 并直接输入脚本。

pipeline 的两种书写方式

分别是:

  1. Declarative Pipeline(声明式流水线)
  2. Scripted Pipeline(脚本化流水线)
声明式流水线(Declarative Pipeline)

声明式流水线通过结构化、简单化的方式来定义流水线。它使用起来更为简单直观,使得新用户更容易上手。

声明式流水线 示例(语法看不懂没关系,下边会有语法介绍):

groovy 复制代码
pipeline {
    agent any

    stages {
        stage('构建') {
            steps {
                echo '构建阶段...'
            }
        }
        stage('测试') {
            steps {
                echo '测试阶段...'
            }
        }
        stage('交付') {
            steps {
                echo '交付阶段...'
            }
        }
    }
}
脚本化流水线(Scripted Pipeline)

脚本化流水线提供了更为灵活和强大的语法,适用于需要复杂逻辑的场景。它完全基于 Groovy 脚本,可以使用 Groovy 的所有特性。

脚本化流水线 示例

groovy 复制代码
node {
    stage('构建') {
        echo '构建中...'
    }
    stage('测试') {
        echo '测试中...'
    }
    stage('交付') {
        echo '交付中...'
    }
}
两者对比

Jenkinsfile能使用两种语法进行编写 - Declarative Pipeline声明式和Scripted Pipeline脚本化,两者都支持建立连续输送的Pipeline。这两种语法对比如下:

  • 共同点: 两者都是pipeline代码的持久实现,都能够使用pipeline内置的插件或者插件提供的steps,两者都可以利用共享库扩展。

  • 区别: 两者不同之处在于语法和灵活性。Declarative pipeline对用户来说,语法更严格,有固定的组织结构,更容易生成代码段,使其成为用户更理想的选择。

但是Scripted pipeline更加灵活,因为Groovy本身只能对结构和语法进行限制,对于更复杂的pipeline来说,用户可以根据自己的业务进行灵活的实现和扩展

建议选择哪种语法? 通常建议使用Declarative Pipeline的方式进行编写,从jenkins社区的动向来看,很明显这种语法结构也会是未来的趋势。声明式流水线比 Jenkins 脚本化流水线的特性:

  • 相比脚本化的流水线语法,它提供更丰富的语法特性
  • 是为了使编写和读取流水线代码更容易而设计的

注:在下边实战时,有关pipeline的脚本也是基于Declarative Pipeline(声明式流水线)的方式进行编写。

pipeline语法

声明式流水线 的基本语法和表达式遵循 groovy语法,但是有以下例外

  • 声明式pipeline : 必须包含在固定格式的pipeline{} 块内,比如:

    arduino 复制代码
    pipeline {
        /* insert Declarative Pipeline here */
    }
    • 每个声明语句必须独立一行, 行尾无需使用分号
    • 块只能由阶段(stages{})、指令、步骤 (steps{})或赋值语句组成
    • 属性引用语句被视为无参数方法调用, 如input()
  • agent :指定在哪个节点上执行流水线。可以是任意节点(agent any),特定标签的节点(agent { label 'my-label' }),或 Docker 容器(agent { docker { image 'maven:3-alpine' } })。

  • stages : 定义流水线的不同阶段,每个阶段包含多个步骤。

  • steps : 每个阶段中执行的具体操作,如构建、测试和部署等。

  • post :在流水线的各个阶段结束后执行的操作,例如清理、发送通知等

语法很多,上边只列了一些比较重要的。

以上是常用的一些语法,,更详细的语法请参见官方文档: 英文版戳这里中文版戳这里,中文版本翻译的有点那啥,将就看吧,英文好的直接看英文版官方文档。

声明式pipeline 语法demo

由于本文准备使用声明式语法来构建JenkinsFile 所以在实战之前,写个简单的demo来熟悉下,声明式 pipeline内容 如下:

typescript 复制代码
// Jenkinsfile (Declarative Pipeline)
pipeline { 
    agent any 
    stages {
        stage('Build') { 
            steps { 
                sh 'make' 
                echo "hello world"
                script {
                    def browsers = ['chrome', 'firefox']
                    for (int i = 0; i < browsers.size(); ++i) {
                        echo "Testing the ${browsers[i]} browser"
                    }
                }
            }
        }
        stage('Test'){
            steps {
                sh 'make check'
                junit 'reports/**/*.xml' 
            }
        }
        stage('Deploy') {
            steps {
                sh 'make publish'
            }
        }
    }
}

下边是解释:

  • pipeline是声明式流水线的一种特定语法,他定义了包含执行整个流水线的所有内容和指令的 "block" 。
  • agent 是声明式流水线的一种特定语法,指示 Jenkins 为整个流水线分配一个执行器(在 Jenkins 环境中的任何可用代理/节点上)和工作区。一般用作于指定在哪个节点上构建,如果不指定就写any表示任意节点。
  • 定义 "Build" 、 "Test" 、 "Deploy" 三个阶段,每个阶段内部执行不同的步骤。
  • stage 是一个描述 stage of this Pipeline的语法块。stage 块定义了在整个流水线的执行任务的概念性地不同的子集(比如 "Build", "Test" 和 "Deploy" 阶段), 它被许多插件用于可视化 或Jenkins流水线目前的 状态/进展,stage 块是可选的。
  • steps 是声明式流水线的一种特定语法,它描述了在这个 stage 中要运行的步骤。
  • sh 是一个执行给定的shell命令的流水线。
  • echo 写一个简单的字符串到控制台输出。
  • junit 是另一个聚合测试报告的流水线。
  • 注意stage括号的值表示阶段名称,值内容不是固定的,根据需要自定义即可。

在有了基本的认识后,接下来就是对我的xzll-im进行 Jenkins pipeline实战了。


使用pipeline的方式,部署我的xzll-im项目

注意:本实战演示中使用的是声明式流水线(Declarative Pipeline)方式编写的pipeline脚本,且 web控制台方式项目根目录创建JenkinsFile方式 都会演示。

在Jenkins web页面配置声明式流水线脚本

首先创建个pipeline类型的任务:

然后点击配置,如下: 进入配置页面后,拉到最下边,在定义这个栏目中,选择 Pipeline Script

注意,这里有两种类型,一个是 我们现在选择的 Pipeline script,一个是Pipeline script from SCM。

Pipeline script :代表就从下边的脚本输入框中寻找 pipeline脚本并执行

Pipeline script from SCM: 则代表从项目根目录中寻找 JenkinsFile中的内容作为 pipeline脚本,当是此模式时 ,无需在jenkins web中配置脚本了。

因为本小节演示的是jenkins web控制台配置 声明式pipeline脚本,所以在这里就选择 Pipeline script了,如下:

声明式脚本内容是我提前写好的(其实就是将方式一的脚本 改一下(但增加了参数选择,从而在构建时可以选择分支),改成遵循声明式pipeline语法的shell,本质上主流程还是不变的),声明式pipeline脚本如下:(每个步骤都有注释,这里不过多解释了,详情看注释好了

groovy 复制代码
pipeline {
    agent any

    parameters {
        choice(
            name: 'GIT_BRANCH',
            choices: ['main', 'dev01', 'jenkins_build_20240709', 'docker_compose_way_bak','group_chat_dev_20240623'], 
            description: '选择Git分支' // 选择Git分支
        )
    }

    environment {
        COMPOSE_DIR = "/usr/local/soft_hzz/xzll-im/jar-file/jenkins_way_build_docker_compose/" // Docker Compose目录
        GIT_REPO = "git@github.com:598572/xzll-im.git" // Git仓库地址
        GIT_BRANCH = "${params.GIT_BRANCH}" // 选择的Git分支
    }

    stages {
        stage('Prepare Environment') {
            steps {
                script {
                    log("开始部署") // 日志记录

                    if (!fileExists(COMPOSE_DIR)) {
                        log("目录不存在,创建目录 $COMPOSE_DIR")
                        sh "mkdir -p $COMPOSE_DIR" // 创建Docker Compose目录
                        exit_on_error("Failed to create directory $COMPOSE_DIR")
                    }

                    log("进入到 docker-compose 目录中")
                    dir(COMPOSE_DIR) {} // 进入Docker Compose目录
                }
            }
        }

        stage('Clone or Update Repo') {
            steps {
                script {
                    dir(COMPOSE_DIR) {
                        if (!fileExists('xzll-im')) {
                            log("项目目录不存在,执行 git clone")
                            sh "git clone $GIT_REPO" // 克隆Git仓库
                            exit_on_error("Git clone failed")
                        } else {
                            log("项目目录已存在,进入目录并执行 git pull")
                            dir('xzll-im') {
                                sh "git pull" // 更新Git仓库
                                exit_on_error("Git pull failed")
                            }
                        }
                    }
                }
            }
        }

        stage('Checkout Branch') {
            steps {
                script {
                    dir("$COMPOSE_DIR/xzll-im") {
                        log("进入项目根目录")
                        sh "git fetch" // 拉取最新的分支信息
                        sh "git checkout $GIT_BRANCH" // 切换到选择的分支
                        exit_on_error("Failed to checkout branch")
                    }
                }
            }
        }

        stage('Build Project') {
            steps {
                script {
                    dir("$COMPOSE_DIR/xzll-im") {
                        log("开始打包")
                        sh "mvn clean package -P test" // 使用Maven打包项目
                        exit_on_error("Maven build failed")
                        log("打包完成")
                    }
                }
            }
        }

        stage('Copy JAR Files') {
            steps {
                script {
                    def jarFiles = [
                        'im-gateway/target/im-gateway.jar': 'im-gateway/src/main/resources',
                        'im-connect/im-connect-service/target/im-connect-service.jar': 'im-connect/im-connect-service/src/main/resources',
                        'im-auth/target/im-auth.jar': 'im-auth/src/main/resources',
                        'im-business/im-business-service/target/im-business-service.jar': 'im-business/im-business-service/src/main/resources',
                        'im-console/im-console-service/target/im-console-service.jar': 'im-console/im-console-service/src/main/resources'
                    ]
                    
                    dir("$COMPOSE_DIR/xzll-im") {
                        jarFiles.each { jar, targetDir ->
                            log("复制 $jar 到 $targetDir")
                            sh "cp $jar $targetDir" // 复制JAR文件到相应目录
                            exit_on_error("Failed to copy $jar to $targetDir")
                        }
                        sh "cp docker-compose.yml ../" // 复制docker-compose.yml文件
                    }
                }
            }
        }

        stage('Deploy with Docker Compose') {
            steps {
                script {
                    dir(COMPOSE_DIR) {
                        log("返回到 docker-compose 目录")
                        sh "docker-compose down" // 停止并移除Docker容器
                        exit_on_error("Docker-compose down failed")

                        log("构建镜像并启动容器")
                        sh "docker-compose up -d --build" // 构建镜像并启动容器
                        exit_on_error("Docker-compose up failed")
                    }
                }
            }
        }

        stage('Clean Up') {
            steps {
                script {
                    log("清理 Docker 相关的垃圾")
                    sh "docker system prune -f --volumes" // 清理Docker垃圾
                    exit_on_error("Docker system prune failed")
                    log("清理完成")
                }
            }
        }
    }

    post {
        always {
            script {
                log("部署结束") // 部署结束的日志
            }
        }
        success {
            script {
                log("构建成功") // 构建成功的日志
            }
            emailext (
                subject: "Jenkins 构建成功通知: ${currentBuild.fullDisplayName}",
                body: """Jenkins 构建成功通知:
                        项目: ${env.JOB_NAME}
                        构建编号: ${env.BUILD_NUMBER}
                        构建URL: ${env.BUILD_URL}""",
                to: 'h163361631@163.com' // 构建成功发送邮件通知
            )
        }
        failure {
            script {
                log("构建失败") // 构建失败的日志
            }
            emailext (
                subject: "Jenkins 构建失败通知: ${currentBuild.fullDisplayName}",
                body: """Jenkins 构建失败通知:
                        项目: ${env.JOB_NAME}
                        构建编号: ${env.BUILD_NUMBER}
                        构建URL: ${env.BUILD_URL}""",
                to: 'h163361631@163.com' // 构建失败发送邮件通知
            )
        }
    }
}

def log(message) {
    echo "[${new Date().format('yyyy-MM-dd HH:mm:ss')}] ${message}" // 日志记录函数
}

def exit_on_error(message) {    
    if (currentBuild.result == 'FAILURE') {
         error(message) // 构建失败时退出并记录错误信息
    }
}

提示:发邮件的话需要安装个插件:

配完pipeline后,来试一把,首先我们点击build with parameters 然后选择你要部署的分支,我这里是:jenkins_build_20240709,之后点击build: 随后jenkins就开始构建了,找到控制台日志,可以看到已经部署成功(任务编号是13) 构建成功后会发送邮件,如下: 你可以直观的查看整个管道的步骤,用时等等,如下: 看下docker容器和日志发现,服务都启动成功了:

接下来我们看下pipeline 的第二种方式

在项目根目录的JenkinsFile 文件中编写声明式流水线脚本的方式启动构建任务

首先我们创建一个jenkins 任务,命名为: xzll-im-pipeline-build-by-Jenkinsfile,如下:

之后我们点击配置:

之后我们在项目根目录创建Jenkinsfile文件,并将上边的脚本粘到此文件中,并提交到代码仓库,如下:

然后选择你想要部署的分支进行部署

(注意:第一次时可能不展示build with parameters 还是 build now 那么这时你点击下budil now 再给他停掉,刷新下就展示 build with parameters 构建类型了 ),如下:

看下控制台打印的部署日志、构建过程视图、邮件、以及docker容器启动情况,如下:

ok到这里使用pipeline方式部署项目的实战演示就结束了,在这两种方式中(两种方式指的是 jenkins web配置pipeline脚本项目根目录创建并编写Jenkinsfile文件),我们都是手动build触发任务的,如果想做到push代码即部署?好办,参考方式2 配一个 构建触发器就好啦! 下边我们试试。

pipeline + push

由于现在是push就提交,所以没发选择了只能是在push时指定,所以把choice选择参数这个注掉,然后push到仓库,如下:

之后我发现Jenkins 已经收到了github的回调,并且开始执行23号部署任务:

部署结果【成功部署,以及启动docker容器和发通知邮件】,如下:

至此,本文就结束了。

别看Jenkins是个工具,但是他很重要,东西也挺多的,想用好的话也得下点功夫研究。 到此,你get了什么是CI/CD 以及Jenkins的几种构建方式了吗??

相关推荐
ifanatic2 分钟前
[面试]-golang基础面试题总结
面试·职场和发展·golang
儿时可乖了8 分钟前
使用 Java 操作 SQLite 数据库
java·数据库·sqlite
ruleslol9 分钟前
java基础概念37:正则表达式2-爬虫
java
xmh-sxh-131426 分钟前
jdk各个版本介绍
java
XINGTECODE39 分钟前
海盗王集成网关和商城服务端功能golang版
开发语言·后端·golang
天天扭码1 小时前
五天SpringCloud计划——DAY2之单体架构和微服务架构的选择和转换原则
java·spring cloud·微服务·架构
程序猿进阶1 小时前
堆外内存泄露排查经历
java·jvm·后端·面试·性能优化·oom·内存泄露
FIN技术铺1 小时前
Spring Boot框架Starter组件整理
java·spring boot·后端
小曲程序1 小时前
vue3 封装request请求
java·前端·typescript·vue
凡人的AI工具箱1 小时前
15分钟学 Go 第 60 天 :综合项目展示 - 构建微服务电商平台(完整示例25000字)
开发语言·后端·微服务·架构·golang