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

avatar
作者
猴君
阅读量:4

本文大纲概览:

  • 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) 的流程图: image.png

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

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

CD 有两个意思 1. 一是 持续交付(Continuous Delivery) 2. 另一个是持续部署(Continuous Deployment)

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

何为持续交付?

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

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

持续交付流程图

image.png

何为持续部署?

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

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

注意: 无论是持续集成、持续交付还是持续部署,如果要想实现,都离不开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 hubimage.pngimage.png执行docker pull 镜像 发现下载失败,都是超时。。。。

      • 版本过低插件安装不了: ,后来终于有两个镜像可以下载了 就是这个镜像:jenkins/jenkins:lts-jdk11jenkins/jenkins:latest镜像,但是却提示jenkins版本过低导致一些插件安装不了,报错如下:image.png因为这个搞了半天,其实最主要原因可能就是欺负我的虚拟机没开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 下边的引导页面戳这里image.png,但是因为遇到了一些问题主要还是网络问题(还是欺负我虚拟机没vpn 😂😂 再欺负我真要买Strong vpn了啊 哈哈),所以我不用此方式,如果网络好(能fq)可以使用,(安装指南戳这里)
    • 最终:我选择使用war包安装jenkins ,因为经过实践我发现此方式比较好用不用担心被q的问题,选择war安装的话点击下边这个LTS Release:war-stableimage.png然后找到我要安装的版本(注意在安装此版本之前,我安装过较低版本 (LTS版本的2.361)但是我发现有些插件不能用,必须高版本才行所以这里选择较高的一个版本 2.452,ok接下来开干

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

下载war、启动并初始化 jenkins

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

下载war包

image.png 之后使用命令下载war包:

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

image.png

编写启动脚本 来启动jenkins

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

!/bin/bash

args=$1

注意修改jenkinswar包的目录

jenkinswarpath="/usr/local/soft_hzz/jenkins"

jenkins开放端口

jenkinshttpport="8079"

java安装路径

javahome="/usr/lib/jvm/java-11-openjdk-11.0.22.0.7-1.el79.x86_64"

日志文件路径

jenkinslogpath="/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 ${javahome}/bin/java -jar ${jenkinswarpath}/jenkins.war --httpPort=${jenkinshttpport} > ${jenkinslog_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 ``` 接下来启动: image.png

初始化jenkins

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

设置初始密码

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

安装jenkins推荐的插件

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

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

创建管理账号

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

完成后查看安装的插件

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

一些插件介绍:

| 插件名 | 作用 | | --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | 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

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

添加git、maven、jdk

image.pngimage.png 添加后点击应用并保存。

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

新建freestyle类型的任务

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

image.png

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

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

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

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

```

注意在生成前最好是先删除一下旧的

删除公钥和私钥

rm -f ~/.ssh/idrsa ~/.ssh/idrsa.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 ```

image.png 将公钥粘到github: image.png

在将公钥配到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中去: image.png

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

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

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

```bash

!/bin/bash

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

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

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

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

确保 docker-compose 目录存在 不存在则创建

COMPOSEDIR="/usr/local/softhzz/xzll-im/jar-file/jenkinswaybuilddockercompose/" if [ ! -d "$COMPOSEDIR" ]; then log ">>>>>>>>>>>> 目录不存在,创建目录 $COMPOSEDIR >>>>>>>>>>>>>" mkdir -p "$COMPOSEDIR" || { log "Failed to create directory $COMPOSEDIR"; exit 1; } fi

进入 docker-compose 目录

log ">>>>>>>>>>>> 进入到 docker-compose 目录中 >>>>>>>>>>>>>" cd "$COMPOSEDIR" || { log "Failed to enter directory $COMPOSEDIR"; 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 jenkinsbuild20240709

开始打包

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 JARFILE in "${!JARFILES[@]}"; do TARGETDIR="${JARFILES[$JARFILE]}" log ">>>>>>>>>>>> 复制 $JARFILE 到 $TARGETDIR >>>>>>>>>>>>>" cp "$JARFILE" "$TARGETDIR" || { log "Failed to copy $JARFILE 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开始构建并启动: image.png 部署结果:

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

以上就是使用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 如下: image.png

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

内网穿透配置:

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

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

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

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

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

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

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

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

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

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

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

image.png

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

配置jenkins

在jenkins 配置github server信息:

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

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

新增access token凭证

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

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

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

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

随便改点东西,push上去:

image.png

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

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

观察并验证启动结果

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

看一下控制台的日志: image.pngimage.png

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

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

方式三、 pipleine构建

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

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

pipeline简介与使用方式

pipeline是什么?

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

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

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

如何创建pipeline?

创建并定义pipeline有两种方式(这里我们简单描述下这两种方式 ,下边会进行实战演示),如下: 1. 直接在 Jenkins Web 界面输入脚本。 3. 编写 Jenkinsfile 文件并将文件存放到项目根目录。

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

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

分别是: 1. Declarative Pipeline(声明式流水线) 1. 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{} 块内,比如: 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内容 如下: // 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类型的任务: image.png

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

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

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

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

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

声明式脚本内容是我提前写好的(其实就是将方式一的脚本 改一下(但增加了参数选择,从而在构建时可以选择分支),改成遵循声明式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 exitonerror(message) {
if (currentBuild.result == 'FAILURE') { error(message) // 构建失败时退出并记录错误信息 } } ```

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

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

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

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

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

之后我们点击配置: image.pngimage.png

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

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

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

image.png

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

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

pipeline + push

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

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

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

至此,本文就结束了。

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

广告一刻

为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!