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(持续集成)的工作流程
- CI初始化:包括但不限于(CI服务的安装&启动,脚本、任务定义与配置,webhook设置,凭证设置,系统配置,插件等等)
- 开发人员从代码库中获取最新代码(pull)。
- 开发人员在本地进行开发和测试。
- 开发人员将代码提交(commit & push)到代码仓库(github、 gitlib)
- 通过回调方式通知CI服务器,ci服务器自动拉取最新代码并触发构建任务。
- 构建(build)-> 自动化测试(test)-> 反馈结果(result)
- 将自动化测试的结果反馈给开发人员(钉钉邮件短信等方式),如果测试失败,开发人员会及时修复代码并重新提交
- 构建和测试成功后,代码可以部署到进一步的测试环境或生产环境中
集成工具
集成工具有很多种(我所接触的主要还是jenkins和gitlib),包括但不限于以下: * Jenkins (常见):一个开源的自动化服务器,支持复杂的构建、测试和部署流水线。 * GitLab CI/CD (常见):GitLab 内置的 CI/CD 工具,支持从代码提交到生产部署的全流程自动化。 * CircleCI:一个基于云的持续集成和持续交付平台,支持多种编程语言和构建环境。 * Travis CI:一个基于云的持续集成服务,特别适用于 GitHub 项目。 * Spinnaker:一个开源的持续交付平台,支持多云环境的自动化部署
持续集成的好处
- 提高代码质量:通过频繁的自动化测试和代码质量检查,可以早期发现并修复错误。
- 加快交付速度:自动化构建和测试减少了手动操作的时间,可以提高开发速度从而加快交付。
- 提高团队协作效率:开发人员可以更快地得到反馈,后期与测试人员协作更加顺畅。
- 总之概括起来就是一句话: 省去人工操作,push后自动构建、测试(这里指自动化测试)、反馈,从而尽早的在开发环境发现代码问题并修复
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版本过低,导致一些插件安装报错,使得我很恼火。
- 说明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包
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包的目录
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 ``` 接下来启动:
初始化jenkins
好,现在我们通过 172.30.128.65:8079访问jenkins,首次访问会出现下边这个解锁jenkins的页面,按提示找到密码并填进去点击继续就行了:
设置初始密码
安装jenkins推荐的插件
点击继续,之后,jenkins会推荐你安装一些插件,这里建议都安装上以免后续还得再手动安装,如下: 插件有点多需要等待一会:
注意: 我这里安装速度还行 就没替换插件的源 如果较慢的话 可以试试下边几个插件源:
| 镜像名 | 镜像地址 | | -------- | ------------------------------------------------------------------------- | | 清华大学 | https://mirrors.tuna.tsinghua.edu.cn/jenkins/updates/update-center.json | | 华为 | https://mirrors.huaweicloud.com/jenkins/updates/update-center.json | | xmission | http://mirror.xmission.com/jenkins/updates/update-center.json |
在这里替换插件源(注意 : 替换后一定要重启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,步骤如下:
```
注意在生成前最好是先删除一下旧的
删除公钥和私钥
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 ```
在将公钥配到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" }
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开始构建并启动: 部署结果:
看到Finished: SUCCESS说明构建成功了(注意构建成功不等于微服务相关项目启动成功哦) 看一下启动情况(可知都正常启动了): 当然最好看下各个服务的日志,这样才能确保真的启动成功(以im-connect服务为例):
以上就是使用freestyle方式部署github项目的流程。挺简单的,但是大部分场景下不使用此方式,需要手动服务多的话比较费时费力。
方式二、 push代码后自动部署
一般在dev环境(测试环境一般不需要push即部署,因为那样影响测试稳定性)可能需要每次push后都需要让最新代码生效(也就是希望每次push后部署一遍,而如果这个工作靠人去完成的话服务多的话将会很累很烦,所以jenkins提供了个回调接口,也就是在github中配上jenkins的回调地址,当有某些事件时(一般是push事件)那么就回调jenkins 然后jenkins去进行自动部署)下边我们就看下如何 在push后让jenkins自动部署?
整个流程简单梳理为以下步骤:
- 首先是配置github和jenkins (详情见下边)
- 开发者push代码到github仓库
- github通过此项目设置的webhook 告知(回调)jenkins接口,哎,有push事件了啊,你看你该怎么处理。
- jenkins收到事件,进行处理(也就是部署项目)
在github设置webhook
webhook的url其实就是 jenkins提供的回调接口的地址
进入项目,找到setting , 找到webhooks 并点击 add webhook 如下:
注意: 由于我的虚拟机是在局域网,github在外网访问不到我的虚拟机ip, 所以需要做一下 内网穿透,我这里使用内网穿透工具为 :灵曜, 一个好用、配置简单的内网穿透工具, 戳此进官网 http://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的回调,如下:
观察并验证启动结果
看一下各容器的状态以及随便找一个服务看看日志: 可以看出,各容器启动正常,日志也正常都起来了。
ok到这里,就实现了 通过push 触发jenkins自动构建 的目的了。当然按照CI的概念来说,你还需要在部署后自动运行自动化测试脚本,以及将测试的结果通知给开发人员以便进行问题修复(如果有的话)。
方式三、 pipleine构建
首先你要确保这一堆pipeline插件一键安装了,想使用pipeline功能,相关插件是不可少的。
官方中文文档: 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?
- 将流水线定义放在名为 Jenkinsfile 的文件中,存储在项目的根目录。使用 Jenkins 的 “Pipeline from SCM” 功能从版本控制系统中读取 Jenkinsfile 并执行流水线。
- 直接在 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
- 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 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 exitonerror(message) {
if (currentBuild.result == 'FAILURE') { error(message) // 构建失败时退出并记录错误信息 } } ```
配完pipeline后,来试一把,首先我们点击build with parameters 然后选择你要部署的分支,我这里是:jenkinsbuild20240709,之后点击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的几种构建方式了吗??