本文大纲概览:
- 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) 的流程图:
知道了CI , 那么CD又是指啥呢?接着看
CD(持续交付&持续部署) 介绍
CD 有两个意思
- 一是 持续交付(Continuous Delivery)
- 另一个是持续部署(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-jdk11
和jenkins/jenkins:latest
镜像,但是却提示jenkins版本过低导致一些插件安装不了,报错如下:因为这个搞了半天,其实最主要原因可能就是欺负我的虚拟机没开vpn,哎 手头紧,暂时先不开了毕竟有点小贵,所以就决定弃启用docker安装jenkins, 我用war或RPM也行呀,真是的,😄
-
- 说明2:为什么安装jenkins 2.252版本?
- 由于在使用jenkins过程中我需要一些最新插件,而 LTS(
官方长期支持版本
)版本缺少对最新插件的支持,所以这里弃用LTS版本而使用较新的版本来安装部署学习,我使用的是: 2.452 (生产环境还是建议使用LTS版本来安装启动jenkins
)
- 由于在使用jenkins过程中我需要一些最新插件,而 LTS(
- 说明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接下来开干
- 首先第一原因是因为上边的说明1才迫使我使用war安装jenkins,另外因为我是centos7,所以其实我本应该使用
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会推荐你安装一些插件,这里建议都安装上以免后续还得再手动安装,如下: 插件有点多需要等待一会:
-
注意: 我这里安装速度还行 就没替换插件的源 如果较慢的话 可以试试下边几个插件源:
镜像名 镜像地址 清华大学 mirrors.tuna.tsinghua.edu.cn/jenkins/upd... 华为 mirrors.huaweicloud.com/jenkins/upd... xmission mirror.xmission.com/jenkins/upd...
在这里替换插件源(注意 : 替换后一定要重启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自动部署?
整个流程简单梳理为以下步骤:
- 首先是配置github和jenkins (详情见下边)
- 开发者push代码到github仓库
- github通过此项目设置的webhook 告知(回调)jenkins接口,哎,有push事件了啊,你看你该怎么处理。
- 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有两种方式(这里我们简单描述下这两种方式 ,下边会进行实战演示),如下:
- 直接
在 Jenkins Web 界面输入脚本
。 - 编写
Jenkinsfile
文件并将文件存放到项目根目录。 何为Jenkinsfile? 对Jenkins 流水线的定义可以被写在一个文本文件中 (此文件称为Jenkinsfile),该文件可以放在项目的根目录下,从而将 流水线的定义作为应用程序的一部分,像其他代码一样进行版本控制和代码审查。
在哪里配置pipeline?
- 将流水线定义放在名为
Jenkinsfile
的文件中,存储在项目的根目录。使用 Jenkins 的 "Pipeline from SCM" 功能从版本控制系统中读取Jenkinsfile
并执行流水线。 - 直接在 Jenkins 的 Web 界面中编写和配置流水线脚本。在创建或配置一个 Pipeline 项目时,可以在 "Pipeline" 部分选择 "Pipeline script" 并直接输入脚本。
pipeline 的两种书写方式
分别是:
- Declarative Pipeline(声明式流水线)
- 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{} 块内,比如:
arduinopipeline { /* 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的几种构建方式了吗??