基于jenkins持续集成k8s
CI/CD流程介绍
在本次集成 CI/CD 工具的过程中,我们先对比一下传统环境与 Kubernetes 环境下的流程差异:
传统环境:
- CI 流程:
开发人员提交代码到 GitLab 仓库 → Jenkins 触发构建 → 进行漏洞扫描、编译代码 → 将构建产物推送至 Nexus 仓库 → 部署至测试环境。 - CD 流程:
Jenkins 从 Nexus 仓库拉取构建产物 → 部署至生产环境。
Kubernetes 环境:
- CI 流程:
开发人员提交代码到 GitLab 仓库 → Jenkins 触发构建 → 进行漏洞扫描、编译代码 → 构建 Docker 镜像并推送至 Harbor 仓库 → 部署至 Kubernetes 测试环境。 - CD 流程:
Jenkins 从 Harbor 仓库拉取对应的镜像 → 部署应用至 Kubernetes 生产环境。
Slave介绍
静态 Slave(静态 Agent)
静态SLave:需要固定的节点,配置其对应环境,手动注册到Master,然后执行任务,任务完成节点处于空闲等待状态;
优势:
- 能够分担主节点上的压力,加快构建速度(所有任务都由Master执行,造成构建速度缓慢,且任务多会出现排队现象
- 能够将特定的任务在特定的主机上运行(比如:不同的任务需要不同的编译环境)
痛点:
- Master 发生单点故障时,整个Jenkins都没办法使用;
- 每个Slave的环境不一样,用于完成不同项目的编译打包工作,但这些不同环境的配置管理及维护都特别困难;
- 有的Slave构建任务频繁,可能出现排队等待,而有的Slave又处于空闲状态,所以会出现资源分配严重不均衡;
- 因为每个Slave都需要一台虚拟机,当Slave空间时,等于就是空跑,资源浪费明显;
动态 Slave(动态 Agent)
动态Slave:由Master动态创建Slave的Pod,自动注册到Master,然后执行任务,任务结束Pod自动销毁;
优势:
- 定义:动态 slave 是按需创建和销毁的构建节点。这些节点根据构建任务的需求动态启动,任务完成后可以自动销毁。
- 配置:通常通过云插件(如 Amazon EC2、Kubernetes 插件)来实现。这些插件允许 Jenkins 根据需求动态创建和销毁虚拟机或容器化的 agent。
- 资源分配:资源仅在需要时被占用,不使用时可以释放,从而提高资源利用率并降低成本。
- 适用场景:适用于构建任务不太频繁或者资源需求波动较大的环境。特别是在云环境中,动态 slave 可以帮助实现更加灵活和成本有效的构建流程。
总的来说,静态 slave 更适合那些需要长时间运行和持续可用的环境,而动态 slave 则更适合那些对资源使用有弹性需求的情况。动态 slave 的使用越来越普遍
动态slave工作流程:
从图上可以看到 Jenkins Master 和 Jenkins Slave 以 Pod 形式运行在 Kubernetes 集群的 Node 上,Master 运行在其中一个节点,并且将其配置数据存储到一个 Volume 上去,Slave 运行在各个节点上,并且它不是一直处于运行状态,它会按照需求动态的创建并自动删除。
这种方式的工作流程大致为:当 Jenkins Master 接受到 Build 请求时,会根据配置的 Label 动态创建一个运行在 Pod 中的 Jenkins Slave 并注册到 Master 上,当运行完 Job 后,这个 Slave 会被注销并且这个 Pod 也会自动删除,恢复到最初状态。
准备工作
服务部署可参考
- Gitlab: https://www.cnblogs.com/Unstoppable9527/p/18415683
- jenkins: https://www.cnblogs.com/Unstoppable9527/p/18415666
- Maven: https://www.cnblogs.com/Unstoppable9527/p/18415699
- Node: https://www.cnblogs.com/Unstoppable9527/p/18415704
- SonarQube:https://www.cuiliangblog.cn/detail/section/165547985
- Harbor:https://www.cuiliangblog.cn/detail/section/15189547
配置cloud节点
镜像构建服务部署
开启agent节点通道
系统管理-全局安全配置-代理
TCP port for inbound agents
需要手动开启指定端口,用于k8s连接jenkins
配置k8s认证
k8s证书准备
两种方式
- 使用 rbac授权,token的方式连接k8s
- 使用admin证书。这里我使用第二种
sz ~/.kube/config #将证书上传到集群外
配置cloud
系统管理-clouds-new cloud
- kubernetes地址:为k8s api server地址,通过调用apiserver操作k8s。可通过以下来查看:
[root@master01 jenkins]# kubectl cluster-info
Kubernetes control plane is running at https://10.0.0.200:16443
CoreDNS is running at https://10.0.0.200:16443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.
- 凭据:kubernetes plugin可以通过key或凭据的方式与k8s进行认证,方便起见,我们采用凭据的方式,使用我们此前创建的
<font style="color:rgb(192, 52, 29);background-color:rgb(251, 229, 225);">secret file</font>
凭据,此时我们需要。 - kubernetes命令空间:使用默认的default
- 点击连接测试,可以看到k8s已经连接成功
- Jenkins 地址 : jenkins的访问地址
- Jenkins 通道 :用于与 Kubernetes 集群建立 CI/CD 连接
配置jnlp镜像
这里我们使用的是内网环境需要手动配置jnlp镜像
添加镜像secret
kubectl create secret docker-registry harbor-admin --namespace=default --docker-server=harbor.jiajia.com --docker-username=admin --docker-password=123456
[root@master01 jenkins]# kubectl get secret | grep harbor
harbor-admin kubernetes.io/dockerconfigjson 1 33m
- 名称:pod名称,在k8s中实际名称为jenkins-slave-随机值。
- 标签列表:此处标签即标识Jenkins agent的,如流水线中agent定义调度在哪个slave上运行。当然此处我们也可不配置,kubernetes plugin将会默认使用
<font style="color:rgb(192, 52, 29);background-color:rgb(251, 229, 225);">jenkins/jnlp-slave:alpine</font>
镜像创建。但是kubernetes-plugin官方已<font style="color:rgb(192, 52, 29);background-color:rgb(251, 229, 225);">停止维护</font>
此镜像,而统一使用<font style="color:rgb(192, 52, 29);background-color:rgb(251, 229, 225);">jenkins/inbound-agent</font>
。因此我们需要进行重新设置。 名称:pod中容器的名称,注意此处必须设置为jnlp,才能对镜像重写使用<font style="color:rgb(192, 52, 29);background-color:rgb(251, 229, 225);">jenkins/inbound-agent</font>
,否则将会出现以下问题:k8s同时拉取<font style="color:rgb(192, 52, 29);background-color:rgb(251, 229, 225);">jenkins/inbound-agent</font>
和<font style="color:rgb(192, 52, 29);background-color:rgb(251, 229, 225);">jenkins/jnlp-slave:alpine</font>
两个镜像,第一个为重写后的实际使用镜像,第二个为默认镜像,导致jenkins-slave无法正常运行,不断重复构建。 - Docker镜像:当名称设置为jnlp后,
<font style="color:rgb(192, 52, 29);background-color:rgb(251, 229, 225);">harbor.jiajia.com/dev/inbound-agent:latest</font>
即为重写后的镜像,否则默认使用<font style="color:rgb(192, 52, 29);background-color:rgb(251, 229, 225);">jenkins/jnlp-slave:alpine</font>
。 - 工作目录:Jenkins slave的默认工作目录,构建时将会在此目录下创建workspace。
- 运行的命令和命令参数: 其中
- 运行的命令必须要留空,否则会重写镜像的默认entrypoint,导致agent 无法连接到master,下面我们会进行演示说明。
- 资源限制:默认的容器是没有资源限制的,我们在此添加了cpu和memory限制,大家可根据实际情况进行修改。
测试节点
我们这里创建一个流水线测试一下是否可用
pipeline {
// 使用k8s拉起slave
agent {
kubernetes {
cloud 'k8s'
inheritFrom 'jenkins-slave'
namespace 'default'
}
}
stages {
stage('输出pods名称') {
steps {
sh 'hostname'
}
}
stage('等待时间') {
steps {
sh 'sleep 3'
}
}
}
}
可以看到已经构建成功
制作pod模板镜像
jnlp镜像是用来连接JenkinsMaster以及共享Master的WORKSPACE,但该镜像并没有maven、docker、kubectl等常用命令,为此我们需要定制几个镜像,后期通过Pipeline将不同的任务交由同一个Pod的不同的容器来执行。
maven镜像
用于代码构建编译打包,会把Ruoyi相关依赖包打到基础镜像内,避免分层构建失败
下载修改好的源
#下载修改好的源
wget https://linux.oldxu.net/settings_docker.xml
编写Dockerfile
[root@master01 maven]# git clone https://gitee.com/y_project/RuoYi-Cloud.git
[root@master01 maven]# cd RuoYi-Cloud
[root@master01 maven]# git checkout v3.6.3
[root@master01 maven]# cd ../
[root@master01 maven]# cat Dockerfile
FROM maven:3.8.6-openjdk-8
ADD ./RuoYi-Cloud /opt/RuoYi-Cloud
ADD ./settings_docker.xml /usr/share/maven/conf/settings.xml
RUN /bin/cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
cd /opt/RuoYi-Cloud && mvn clean install -DskipTests && \
rm -rf /opt/RuoYi-Cloud
构建
docker build -t harbor.jiajia.com/dev/jenkins-maven:3.8.6 .
docker push harbor.jiajia.com/dev/jenkins-maven:3.8.6
sonar镜像
拉取镜像
[root@node02 ~]# docker pull emeraldsquad/sonar-scanner:2.3.0
[root@node02 ~]# docker tag emeraldsquad/sonar-scanner:2.3.0 harbor.jiajia.com/dev/sonar-scanner:2.3.0
[root@node02 ~]# docker push harbor.jiajia.com/dev/sonar-scanner:2.3.0
nodejs镜像
编写Dockerfile
[root@master01 nodejs]# cat Dockerfile
# 使用基础镜像
FROM centos:7
# 安装 gzip 和 wget 工具使用 RPM 包
COPY gzip-1.5-11.el7_9.x86_64.rpm /tmp/
COPY wget-1.14-18.el7_6.1.x86_64.rpm /tmp/
RUN yum localinstall -y /tmp/gzip-1.5-11.el7_9.x86_64.rpm /tmp/wget-1.14-18.el7_6.1.x86_64.rpm && \
yum clean all && \
rm -f /tmp/gzip-1.5-11.el7_9.x86_64.rpm /tmp/wget-1.14-18.el7_6.1.x86_64.rpm
# 创建必要的目录
RUN mkdir -p /data/setup/ /data/prog/
# 下载并解压 Node.js,如果文件和目录不存在
RUN cd /data/setup/ && \
if [ ! -f /data/setup/node-v12.18.3-linux-x64.tar.xz ]; then \
wget http://down.yu1991.com/ruoyi/node-v12.18.3-linux-x64.tar.xz; \
fi && \
if [ ! -d /data/prog/node-v12.18.3-linux-x64 ]; then \
tar -xf /data/setup/node-v12.18.3-linux-x64.tar.xz -C /data/prog/; \
fi && \
chown -R root:root /data/prog/node-v12.18.3-linux-x64 && \
ln -snf /data/prog/node-v12.18.3-linux-x64 /data/prog/node
# 创建 npm 和 node 的软链接
RUN ln -s /data/prog/node/bin/npm /usr/bin/npm && \
ln -s /data/prog/node/bin/node /usr/bin/node
# 设置工作目录
WORKDIR /data/prog
构建
docker build -t harbor.jiajia.com/dev/jenkins-nodejs:12.18.3 .
docker push harbor.jiajia.com/dev/jenkins-nodejs:12.18.3
docker镜像
拉取镜像
docker pull docker:20.10
docker tag docker:20.10 harbor.jiajia.com/dev/docker:20.10
docker push harbor.jiajia.com/dev/docker:20.10
kubectl镜像
添加源
cat << EOF > ./kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/
enabled=1
gpgcheck=0
EOF
编写Dockerfile
FROM centos:7
# 1、调整时区
RUN /bin/cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
echo 'Asia/Shanghai' >/etc/timezone && \
rm -f /etc/yum.repos.d/*
# 2、添加yum源
ADD ./kubernetes.repo /etc/yum.repos.d/kubernetes.repo
# 3、安装Kubectl
RUN yum makecache && \
yum install kubectl-1.23.17 -y && \
yum clean all
构建
docker build -t harbor.jiajia.com/dev/jenkins-kubectl:1.23.17 .
docker push harbor.jiajia.com/dev/jenkins-kubectl:1.23.17
测试模板镜像
我们新建一个流水线测试一下模板
pipeline {
agent {
kubernetes {
// 自动创建 Slave Pod 执行以下动作
cloud 'k8s'
inheritFrom 'jenkins-slave'
namespace 'default'
yaml '''
apiVersion: v1
kind: Pod
spec:
containers:
- name: maven
image: harbor.jiajia.com/dev/jenkins-maven:3.8.6
imagePullPolicy: IfNotPresent
command: ["cat"]
tty: true
volumeMounts:
- name: data
mountPath: /root/.m2
- name: nodejs
image: harbor.jiajia.com/dev/jenkins-nodejs:12.18.3
imagePullPolicy: IfNotPresent
command: ["cat"]
tty: true
- name: sonar
image: harbor.jiajia.com/dev/sonar-scanner:2.3.0
imagePullPolicy: IfNotPresent
command: ["cat"]
tty: true
- name: docker
image: docker:20.10
imagePullPolicy: IfNotPresent
command: ["cat"]
tty: true
volumeMounts:
- name: dockersocket
mountPath: /run/docker.sock
- name: kubectl
image: harbor.jiajia.com/dev/jenkins-kubectl:1.23.17
imagePullPolicy: IfNotPresent
tty: true
command: ["cat"]
volumes:
- name: data
nfs:
server: 10.0.0.11 #存储maven仓库依赖
path: /nfs/data/maven
- name: dockersocket
hostPath:
path: /run/docker.sock
'''
}
}
stages {
stage('maven测试') {
steps {
container('maven') {
sh 'mvn -version'
}
}
}
stage('nodejs测试') {
steps {
container('nodejs') {
sh 'node -v'
}
}
}
stage('docker测试') {
steps {
container('docker') {
sh 'docker ps'
}
}
}
stage('kubectl测试') {
steps {
container('kubectl') {
sh 'kubectl version'
}
}
}
}
}
CI阶段流程
获取代码
在 Kubernetes 上拉取代码时,可以参考以下方案:
- Slave Pod 如何访问 GitLab:
- 通过 GitLab 的 Service 地址直接访问项目。
- 使用 CoreDNS 配置自定义域名解析,以便更方便地访问 GitLab。
- GitLab 项目是私有的,如何处理:
- 在 Jenkins 上配置好与 GitLab 对应的认证信息,以确保能够顺利访问私有项目。
- 编写stage
配置域名解析
kubectl edit configmaps -n kube-system coredns
添加如下部分
hosts {
10.10.10.22 gitlab.jiajia.com
fallthrough
}
配置后进入容器测试是否可以正确解析
配置gitlab认证
获取gitlab的token,点击create会自动生成
jenkins添加凭据
凭据ID必须是全局唯一,这里为了后期方便使用,我命名为gitlab_token,凭据ID一旦创建不可更改
配置stage
stage("获取代码") {
steps {
container('maven') {
echo "拉取代码"
checkout scmGit(
branches: [[name: '*/master']],
extensions: [],
userRemoteConfigs: [[credentialsId: 'git-root',
url: "${GitLab_Url}${PROJECT_NAME}.git"]])
}
}
}
漏洞扫描
通过 Pod 模板中的 Sonar 容器访问 SonarQube 服务端,可以参考以下步骤:
- DNS 解析:
- 配置 DNS 解析,使得 Slave Pod 能够正确访问 SonarQube 服务端。
- 这里SonarQube部署在集群外可以直接通过ip访问
- Jenkins 集成 SonarQube:
- 在 Jenkins 中集成 SonarQube,确保 Jenkins 能够与 SonarQube 通信。
- 编写 Jenkins Pipeline Stage:
- 在 Jenkins Pipeline 中编写一个 Stage,用于调用 Sonar-Scanner 执行代码扫描,并将结果上传到 SonarQube 服务端。
配置sonarqube
安装sonarqube,这里已经提前安装好了,默认安装成功后访问 浏览器 http://部署的服务器IP:9090
,默认账号 admin
密码admin
配置token
token: 2253419796b4417f2200453f99351ca2e65bbfe4
配置jenkins
创建凭据
安装SonarQube插件
SonarQube
配置jenkins对接sonar
系统管理-系统配置-添加add sonarqube
编写Stage
注:withSonarQubeEnv
负责提供 SonarQube 环境变量
stage('代码扫描') {
steps {
withSonarQubeEnv('sonarqube'){
container('sonar'){
sh 'ls -l'
sh 'sonar-scanner \
-Dsonar.projectKey=${PROJECT_NAME} \
-Dsonar.java.binaries=src \
-Dsonar.sources=.'
}
}
}
}
代码编译
编写stage
stage('编译代码'){
steps {
container('maven'){
sh 'pwd && ls -l'
sh 'mvn package -Dmaven.test.skip=true'
}
}
}
构建镜像
在构建镜像的过程中,可以按照以下优化步骤进行:
- 配置域名解析:
- 配置 DNS 解析,使得 Slave Pod 能够顺利访问 Harbor 仓库。
- 配置凭据:
- 在 Jenkins 中配置好访问 Harbor 的凭据,确保推送镜像时能够正确进行认证。
- 构建 Docker 镜像:
- 使用
docker build
命令,根据 Dockerfile 构建 Docker 镜像。
- 使用
- 推送镜像到 Harbor:
- 将构建好的 Docker 镜像推送到 Harbor 仓库中。
配置域名解析
上可以参考获取代码部分
配置harbor凭据
编写stage
dockfile文件存放在镜像仓库
stage('生成镜像Tag') {
steps {
container('maven') {
script {
//本次git提交的commid (git log -n1 --pretty=format:'%h')
env.COMMITID = sh(returnStdout: true, script: "git log -n1 --pretty=format:'%h'").trim()
//构建的时间 (date +%Y%m%d_%H%M%S)
env.BuildTime = sh(returnStdout: true, script: "date +%Y%m%d_%H%M%S").trim()
//完整的镜像Tag (c106654_20221115_133911)
env.ImageTag = COMMITID + "_" + BuildTime
}
sh 'echo "镜像的Tag: ${ImageTag}"'
}
}
}
stage('镜像构建') {
steps {
container('docker') {
withCredentials([usernamePassword(credentialsId: "HARBOR_ID", passwordVariable: 'HARBOR_PASSWORD', usernameVariable: 'HARBOR_USER')]) {
//登陆harbor
sh 'echo "${HARBOR_PASSWORD}" | docker login ${HarBor_Url} -u "${HARBOR_USER}" --password-stdin'
//构建镜像
sh 'docker build -t ${HarBor_Url}/${Pro}/${PROJECT_NAME}:${ImageTag} .'
//推送镜像
sh 'docker push ${HarBor_Url}/${Pro}/${PROJECT_NAME}:${ImageTag}'
//删除镜像
sh 'docker rmi ${HarBor_Url}/${Pro}/${PROJECT_NAME}:${ImageTag}'
}
}
}
}
服务部署
编写stage
stage("服务发布") {
steps {
container('kubectl') {
sh 'kubectl version'
withCredentials([file(credentialsId: 'k8s-admin', variable: 'KUBECONFIG')]) {
sh 'mkdir -p ~/.kube && cp ${KUBECONFIG} ~/.kube/config'
sh '''
sed -i "s#{namespace}#default#g" deploy.yaml
sed -i "s#{image}#${HarBor_Url}/${Pro}/${PROJECT_NAME}:${ImageTag}#g" deploy.yaml
kubectl apply -f deploy.yaml
'''
}
}
}
}
完整流水线
pipeline {
agent {
kubernetes {
cloud 'k8s'
inheritFrom 'jenkins-slave'
namespace 'default'
yaml '''
apiVersion: v1
kind: Pod
spec:
containers:
- name: maven
image: harbor.jiajia.com/dev/jenkins-maven:3.8.7
imagePullPolicy: Always
command: ["cat"]
tty: true
volumeMounts:
- name: data
mountPath: /root/.m2
- name: nodejs
image: harbor.jiajia.com/dev/jenkins-nodejs:12.18.3
imagePullPolicy: IfNotPresent
command: ["cat"]
tty: true
- name: sonar
image: harbor.jiajia.com/dev/sonar-scanner:2.3.0
imagePullPolicy: IfNotPresent
command: ["cat"]
tty: true
- name: docker
image: docker:20.10
imagePullPolicy: IfNotPresent
command: ["cat"]
tty: true
volumeMounts:
- name: dockersocket
mountPath: /run/docker.sock
- name: kubectl
image: harbor.jiajia.com/dev/jenkins-kubectl:1.23.17
imagePullPolicy: IfNotPresent
tty: true
command: ["cat"]
volumes:
- name: data
nfs:
server: 10.0.0.11
path: /nfs/data/maven
- name: dockersocket
hostPath:
path: /run/docker.sock
'''
}
}
environment {
HarBor_Url="harbor.jiajia.com"
Pro="ruoyi"
GitLab_Url="http://gitlab.jiajia.top/root/"
}
parameters {
choice choices: ['ruoyi-auth', 'ruoyi-system', 'ruoyi-gateway', 'ruoyi-ui'],
description: '请选择项目构建',
name: 'PROJECT_NAME'
}
stages {
stage("获取代码") {
steps {
container('maven') {
echo "拉取代码"
checkout scmGit(
branches: [[name: '*/master']],
extensions: [],
userRemoteConfigs: [[credentialsId: 'git-root',
url: "${GitLab_Url}${PROJECT_NAME}.git"]])
}
}
}
stage('获取构建信息') {
steps {
script {
currentBuild.displayName = "${BUILD_NUMBER}-${PROJECT_NAME}"
currentBuild.description = "提交者: ${BUILD_USER} <br> 提交者ID:${BUILD_USER_ID} <br> 提交时间:${BUILD_TIMESTAMP} <br> 构建分支:${PROJECT_NAME}"
}
}
}
stage('代码扫描') {
steps {
withSonarQubeEnv('sonarqube'){
container('sonar'){
sh 'ls -l'
sh 'sonar-scanner -Dsonar.projectKey=${PROJECT_NAME} -Dsonar.java.binaries=src -Dsonar.sources=.'
}
}
}
}
stage('代码编译') {
steps {
container('maven') {
sh 'pwd'
}
}
}
stage('生成镜像Tag') {
steps {
container('maven') {
script {
env.COMMITID = sh(returnStdout: true, script: "git log -n1 --pretty=format:'%h'").trim()
env.BuildTime = sh(returnStdout: true, script: "date +%Y%m%d_%H%M%S").trim()
env.ImageTag = COMMITID + "_" + BuildTime
}
sh 'echo "镜像的Tag: ${ImageTag}"'
}
}
}
stage('镜像构建') {
steps {
container('docker') {
withCredentials([usernamePassword(credentialsId: "HARBOR_ID", passwordVariable: 'HARBOR_PASSWORD', usernameVariable: 'HARBOR_USER')]) {
sh 'echo "${HARBOR_PASSWORD}" | docker login ${HarBor_Url} -u "${HARBOR_USER}" --password-stdin'
sh 'docker build -t ${HarBor_Url}/${Pro}/${PROJECT_NAME}:${ImageTag} .'
sh 'docker push ${HarBor_Url}/${Pro}/${PROJECT_NAME}:${ImageTag}'
sh 'docker rmi ${HarBor_Url}/${Pro}/${PROJECT_NAME}:${ImageTag}'
}
}
}
}
stage("服务发布") {
steps {
container('kubectl') {
sh 'kubectl version'
withCredentials([file(credentialsId: 'k8s-admin', variable: 'KUBECONFIG')]) {
sh 'mkdir -p ~/.kube && cp ${KUBECONFIG} ~/.kube/config'
sh '''
sed -i "s#{namespace}#default#g" deploy.yaml
sed -i "s#{image}#${HarBor_Url}/${Pro}/${PROJECT_NAME}:${ImageTag}#g" deploy.yaml
kubectl apply -f deploy.yaml
'''
}
}
}
}
}
post {
always {
dingtalk (
robot: "dev",
type: 'MARKDOWN',
title: "${PROJECT_NAME} 构建${currentBuild.result}",
text: [
"## [${PROJECT_NAME} 构建${currentBuild.result}提醒](${BUILD_URL}console)",
"---",
"- 项目名称:${PROJECT_NAME} ",
"- 构建编号:${BUILD_NUMBER} ",
"- 构建分支:master ",
"- 构建人:${BUILD_USER} ",
"- 构建URL:${BUILD_URL} ",
"- 构建结果:${currentBuild.result} ",
"- 构建环境:${env.PLATFORM} ",
"- 构建时间:${BUILD_TIMESTAMP} ",
"- 构建持续时间:${currentBuild.duration/1000}秒"
]
)
}
}
}
CD阶段流程
Jenkins 从 Harbor 仓库拉取对应的镜像 → 部署应用至 Kubernetes 生产环境。
对于获取镜像仓库标签我们可以配置 Jenkins 动态关联参数,以便从 Harbor 仓库获取镜像的最新标签,并自动关联到部署流程中,实现灵活的镜像版本管理
动态关联参数
内容提要:
- 安装 Active Choices Plugin-in 之前版本叫 Active Choices Plugin
- UI 应用 Active Choices Reactive Parameters
- Pipeline 脚本应用
安装 Active Choices Plugin
在用 Active Choices Reactive Parameters 之前,确保 Jenkins 上安装 Active Choices 插件。
安装成功后,打开 Job Configure 页面,勾上 This project is parameterized
, 点开 Add Parameter
,会看到多出下面三个选项。
配置harbor项目名称
选择Active Choices parameters-主动选择参数
return ["pi6000", "prome","ruoyi"]
配置harbor镜像名称
选择Active Choices Reactive Parameter 主动选择反应参数
import groovy.json.JsonSlurper
// 定义 curl 命令来获取 JSON 数据
def command = "curl -s -u admin:123456 -H 'Content-Type: application/json' -X GET https://192.168.1.20/api/v2.0/projects/${HarBor_Pro}/repositories --insecure"
// 执行命令并获取输出
def process = command.execute()
def output = process.text.trim()
// 打印输出以调试
println("Command Output: ${output}")
// 解析 JSON 数据
def jsonSlurper = new JsonSlurper()
def parsedJson = jsonSlurper.parseText(output)
// 提取仓库名称的最后部分
def repositoryNames = parsedJson.collect {
// 获取仓库名称
def fullName = it.name
// 提取最后部分
def shortName = fullName.replaceAll(/.*\/([^\/]*)$/, '$1')
return shortName
}
// 返回仓库名称列表
return repositoryNames
记得填写Referenced parameters获取HarBor_Pro参数的变化
获取镜像标签
选择 Active Choices Reactive Parameter 主动选择反应参数
def get_tag = [ "bash", "-c", "curl -s -uadmin:123456 -H 'Content-Type: application/json' -X GET https://192.168.1.20/v2/${HarBor_Pro}/${Image_Name}/tags/list --insecure | sed -r 's#(\\{.*\\[)(.*)(\\]\\})#\\2#g' | xargs -d ',' -n1 | xargs -n1 | sort -t '_' -k2 -k3 -nr | head -5"]
return get_tag.execute().text.tokenize("\n")
记得填写Referenced parameters获取HarBor_Pro参数的变化
HarBor_Pro,Image_Name
整体效果
测试镜像名称
测试一下完整镜像名称
pipeline {
agent {
kubernetes {
cloud 'k8s'
inheritFrom 'jenkins-slave'
namespace 'default'
yaml '''
apiVersion: v1
kind: Pod
spec:
imagePullSecrets:
- name: harbor-admin
containers:
- name: kubectl
image: harbor.jiajia.com/dev/jenkins-kubectl:1.23.17
imagePullPolicy: IfNotPresent
command: ["cat"]
tty: true
'''
}
}
environment {
Full_Image = "harbor.jiajia.com/${Harbor_Pro}/${Image_Name}:${Image_Tags}"
}
stages {
stage('输出完整的镜像名称') {
steps {
sh 'echo 镜像名称-tag: ${Full_Image}'
}
}
}
}
发布服务
pipeline {
agent {
kubernetes {
cloud 'k8s'
inheritFrom 'jenkins-slave'
namespace 'default'
yaml '''
apiVersion: v1
kind: Pod
spec:
imagePullSecrets:
- name: harbor-admin
containers:
- name: kubectl
image: harbor.jiajia.com/dev/jenkins-kubectl:1.23.17
imagePullPolicy: IfNotPresent
command: ["cat"]
tty: true
'''
}
}
environment {
Full_Image = "harbor.jiajia.com/${Harbor_Pro}/${Image_Name}:${Image_Tags}"
}
stages {
stage('输出完整的镜像名称') {
steps {
sh 'echo 镜像名称-tag: ${Full_Image}'
}
}
stage('部署应用至K8S') {
steps {
withCredentials([file(credentialsId: 'k8s-admin', variable: 'KUBECONFIG')]) {
container('kubectl'){
sh 'mkdir -p ~/.kube && cp ${KUBECONFIG} ~/.kube/config'
sh 'kubectl set image deployment/${Image_Name} ${Image_Name}=${Full_Image} -n default'
}
}
}
}
}
}
快速回滚
stage('快速回滚') {
steps {
withCredentials([file(credentialsId: 'k8s-admin', variable: 'KUBECONFIG')]) {
container('kubectl'){
script {
timeout(time:1 , unit: 'HOURS'){
def UserInput = input message: '是否回退至上一个版本', parameters: [choice(choices: ['No', 'Yes'], name: 'rollback')]
if (UserInput == "Yes"){
sh 'mkdir -p ~/.kube && cp ${KUBECONFIG} ~/.kube/config'
sh 'kubectl rollout undo deployment ${Image_Name} -n default'
}else {
echo "没有选择回退"
}
}
}
}
}
}
}
发布服务构建
可以看到当前镜像版本已更新
kubectl get pods ruoyi-auth-6b66d64b7-9vbwf -o yaml | grep image
我们的流水线设置了1小时的超时限制。如果在此期间没有执行操作,默认情况下部署将不会回滚。现在,我们选择在超时后自动执行回滚操作。
查看回滚后镜像,可以看到已经快速回退到上一个版本
kubectl get pods ruoyi-auth-6b66d64b7-9vbwf -o yaml | grep image
完整流水线
pipeline {
agent {
kubernetes {
cloud 'k8s'
inheritFrom 'jenkins-slave'
namespace 'default'
yaml '''
apiVersion: v1
kind: Pod
spec:
imagePullSecrets:
- name: harbor-admin
containers:
- name: kubectl
image: harbor.jiajia.com/dev/jenkins-kubectl:1.23.17
imagePullPolicy: IfNotPresent
command: ["cat"]
tty: true
'''
}
}
parameters {
choice(
choices: ['dev', 'prod'],
description: '请选择要部署的环境',
name: 'PLATFORM'
)
}
environment {
Full_Image = "harbor.jiajia.com/${Harbor_Pro}/${Image_Name}:${Image_Tags}"
UserInput = ""
}
stages {
stage('输出完整的镜像名称') {
steps {
sh 'echo 镜像名称-tag: ${Full_Image}'
}
}
stage('部署应用至K8S') {
steps {
withCredentials([file(credentialsId: 'k8s-admin', variable: 'KUBECONFIG')]) {
container('kubectl'){
sh 'mkdir -p ~/.kube && cp ${KUBECONFIG} ~/.kube/config'
sh 'kubectl set image deployment/${Image_Name} ${Image_Name}=${Full_Image} -n default'
}
}
}
}
stage('快速回滚') {
steps {
withCredentials([file(credentialsId: 'k8s-admin', variable: 'KUBECONFIG')]) {
container('kubectl'){
script {
timeout(time:1 , unit: 'HOURS'){
UserInput = input message: '是否回退至上一个版本', parameters: [choice(choices: ['No', 'Yes'], name: 'rollback')]
if (UserInput == "Yes"){
sh 'mkdir -p ~/.kube && cp ${KUBECONFIG} ~/.kube/config'
sh 'kubectl rollout undo deployment ${Image_Name} -n default'
}else {
echo "没有选择回退"
}
}
}
}
}
}
}
}
post {
always {
dingtalk (
robot: "dev",
type: 'MARKDOWN',
title: "${Image_Name} 构建${currentBuild.result}",
text: [
"## [${Image_Name} 构建${currentBuild.result}提醒](${BUILD_URL}console)",
"---",
"- 项目名称:${Image_Name} ",
"- 构建编号:${BUILD_NUMBER} ",
"- 构建人:${BUILD_USER} ",
"- 构建URL:${BUILD_URL} ",
"- 构建结果:${currentBuild.result} ",
"- 构建环境:${env.PLATFORM} ",
"- 构建时间:${BUILD_TIMESTAMP} ",
"- 构建镜像:${Full_Image} ",
"- 是否回滚:${UserInput} ",
"- 构建持续时间:${currentBuild.duration/1000}秒"
]
)
}
}
}
生成动态关联参数流水线
properties([gitLabConnection(gitLabConnection: 'gitlab', jobCredentialId: ''), parameters([activeChoice(choiceType: 'PT_SINGLE_SELECT', description: '请选择项目名称', filterLength: 1, filterable: false, name: 'HarBor_Pro', randomName: 'choice-parameter-3118753064687', script: groovyScript(fallbackScript: [classpath: [], oldScript: '', sandbox: false, script: ''], script: [classpath: [], oldScript: '', sandbox: false, script: 'return ["pi6000", "prome","ruoyi"]'])), reactiveChoice(choiceType: 'PT_SINGLE_SELECT', description: '请选择仓库名称', filterLength: 1, filterable: false, name: 'Image_Name', randomName: 'choice-parameter-3118772227412', referencedParameters: 'HarBor_Pro', script: groovyScript(fallbackScript: [classpath: [], oldScript: '', sandbox: false, script: ''], script: [classpath: [], oldScript: '', sandbox: false, script: '''import groovy.json.JsonSlurper
// 定义 curl 命令来获取 JSON 数据
def command = "curl -s -u admin:123456 -H \'Content-Type: application/json\' -X GET https://192.168.1.20/api/v2.0/projects/${HarBor_Pro}/repositories --insecure"
// 执行命令并获取输出
def process = command.execute()
def output = process.text.trim()
// 打印输出以调试
println("Command Output: ${output}")
// 解析 JSON 数据
def jsonSlurper = new JsonSlurper()
def parsedJson = jsonSlurper.parseText(output)
// 提取仓库名称的最后部分
def repositoryNames = parsedJson.collect {
// 获取仓库名称
def fullName = it.name
// 提取最后部分
def shortName = fullName.replaceAll(/.*\\/([^\\/]*)$/, \'$1\')
return shortName
}
// 返回仓库名称列表
return repositoryNames'''])), reactiveChoice(choiceType: 'PT_SINGLE_SELECT', description: '请选择镜像标签', filterLength: 1, filterable: false, name: 'Image_Tags', randomName: 'choice-parameter-3118774362701', referencedParameters: 'HarBor_Pro,Image_Name', script: groovyScript(fallbackScript: [classpath: [], oldScript: '', sandbox: false, script: ''], script: [classpath: [], oldScript: '', sandbox: false, script: '''def get_tag = [ "bash", "-c", "curl -s -uadmin:123456 -H \'Content-Type: application/json\' -X GET https://192.168.1.20/v2/${HarBor_Pro}/${Image_Name}/tags/list --insecure | sed -r \'s#(\\\\{.*\\\\[)(.*)(\\\\]\\\\})#\\\\2#g\' | xargs -d \',\' -n1 | xargs -n1 | sort -t \'_\' -k2 -k3 -nr | head -5"]
return get_tag.execute().text.tokenize("\\n")''']))])])
生成完之后放在流水线最上面即可
完整流水线
properties([gitLabConnection(gitLabConnection: 'gitlab', jobCredentialId: ''), parameters([activeChoice(choiceType: 'PT_SINGLE_SELECT', description: '请选择项目名称', filterLength: 1, filterable: false, name: 'HarBor_Pro', randomName: 'choice-parameter-3118753064687', script: groovyScript(fallbackScript: [classpath: [], oldScript: '', sandbox: false, script: ''], script: [classpath: [], oldScript: '', sandbox: false, script: 'return ["pi6000", "prome","ruoyi"]'])), reactiveChoice(choiceType: 'PT_SINGLE_SELECT', description: '请选择仓库名称', filterLength: 1, filterable: false, name: 'Image_Name', randomName: 'choice-parameter-3118772227412', referencedParameters: 'HarBor_Pro', script: groovyScript(fallbackScript: [classpath: [], oldScript: '', sandbox: false, script: ''], script: [classpath: [], oldScript: '', sandbox: false, script: '''import groovy.json.JsonSlurper
// 定义 curl 命令来获取 JSON 数据
def command = "curl -s -u admin:123456 -H \'Content-Type: application/json\' -X GET https://192.168.1.20/api/v2.0/projects/${HarBor_Pro}/repositories --insecure"
// 执行命令并获取输出
def process = command.execute()
def output = process.text.trim()
// 打印输出以调试
println("Command Output: ${output}")
// 解析 JSON 数据
def jsonSlurper = new JsonSlurper()
def parsedJson = jsonSlurper.parseText(output)
// 提取仓库名称的最后部分
def repositoryNames = parsedJson.collect {
// 获取仓库名称
def fullName = it.name
// 提取最后部分
def shortName = fullName.replaceAll(/.*\\/([^\\/]*)$/, \'$1\')
return shortName
}
// 返回仓库名称列表
return repositoryNames'''])), reactiveChoice(choiceType: 'PT_SINGLE_SELECT', description: '请选择镜像标签', filterLength: 1, filterable: false, name: 'Image_Tags', randomName: 'choice-parameter-3118774362701', referencedParameters: 'HarBor_Pro,Image_Name', script: groovyScript(fallbackScript: [classpath: [], oldScript: '', sandbox: false, script: ''], script: [classpath: [], oldScript: '', sandbox: false, script: '''def get_tag = [ "bash", "-c", "curl -s -uadmin:123456 -H \'Content-Type: application/json\' -X GET https://192.168.1.20/v2/${HarBor_Pro}/${Image_Name}/tags/list --insecure | sed -r \'s#(\\\\{.*\\\\[)(.*)(\\\\]\\\\})#\\\\2#g\' | xargs -d \',\' -n1 | xargs -n1 | sort -t \'_\' -k2 -k3 -nr | head -5"]
return get_tag.execute().text.tokenize("\\n")''']))])])
pipeline {
agent {
kubernetes {
cloud 'k8s'
inheritFrom 'jenkins-slave'
namespace 'default'
yaml '''
apiVersion: v1
kind: Pod
spec:
imagePullSecrets:
- name: harbor-admin
containers:
- name: kubectl
image: harbor.jiajia.com/dev/jenkins-kubectl:1.23.17
imagePullPolicy: IfNotPresent
command: ["cat"]
tty: true
'''
}
}
parameters {
choice(
choices: ['dev', 'prod'],
description: '请选择要部署的环境',
name: 'PLATFORM'
)
}
environment {
Full_Image = "harbor.jiajia.com/${Harbor_Pro}/${Image_Name}:${Image_Tags}"
UserInput = ""
}
stages {
stage('输出完整的镜像名称') {
steps {
sh 'echo 镜像名称-tag: ${Full_Image}'
}
}
stage('部署应用至K8S') {
steps {
withCredentials([file(credentialsId: 'k8s-admin', variable: 'KUBECONFIG')]) {
container('kubectl'){
sh 'mkdir -p ~/.kube && cp ${KUBECONFIG} ~/.kube/config'
sh 'kubectl set image deployment/${Image_Name} ${Image_Name}=${Full_Image} -n default'
}
}
}
}
stage('快速回滚') {
steps {
withCredentials([file(credentialsId: 'k8s-admin', variable: 'KUBECONFIG')]) {
container('kubectl'){
script {
timeout(time:1 , unit: 'HOURS'){
UserInput = input message: '是否回退至上一个版本', parameters: [choice(choices: ['No', 'Yes'], name: 'rollback')]
if (UserInput == "Yes"){
sh 'mkdir -p ~/.kube && cp ${KUBECONFIG} ~/.kube/config'
sh 'kubectl rollout undo deployment ${Image_Name} -n default'
}else {
echo "没有选择回退"
}
}
}
}
}
}
}
}
post {
always {
dingtalk (
robot: "dev",
type: 'MARKDOWN',
title: "${Image_Name} 构建${currentBuild.result}",
text: [
"## [${Image_Name} 构建${currentBuild.result}提醒](${BUILD_URL}console)",
"---",
"- 项目名称:${Image_Name} ",
"- 构建编号:${BUILD_NUMBER} ",
"- 构建人:${BUILD_USER} ",
"- 构建URL:${BUILD_URL} ",
"- 构建结果:${currentBuild.result} ",
"- 构建环境:${env.PLATFORM} ",
"- 构建时间:${BUILD_TIMESTAMP} ",
"- 构建镜像:${Full_Image} ",
"- 是否回滚:${UserInput} ",
"- 构建持续时间:${currentBuild.duration/1000}秒"
]
)
}
}
}
基于Lark Notice优化构建通知
简介
<font style="color:rgb(31, 35, 40);">lark-notice-plugin</font>
是一个用于 <font style="color:rgb(31, 35, 40);">Jenkins</font>
的 <font style="color:rgb(31, 35, 40);">构建通知机器人</font>
通知插件,可以将 <font style="color:rgb(31, 35, 40);">Jenkins</font>
构建过程以及结果通知推送到 <font style="color:rgb(31, 35, 40);">Lark</font>
、<font style="color:rgb(31, 35, 40);">飞书</font>
、<font style="color:rgb(31, 35, 40);">钉钉</font>
协作平台。 可配置多个的通知时机,包括 <font style="color:rgb(31, 35, 40);">构建启动时</font>
、<font style="color:rgb(31, 35, 40);">构建中断</font>
、<font style="color:rgb(31, 35, 40);">构建失败</font>
、<font style="color:rgb(31, 35, 40);">构建成功时</font>
、<font style="color:rgb(31, 35, 40);">构建不稳定</font>
等。 支持多种不同类型的消息,包括 <font style="color:rgb(31, 35, 40);">文本消息</font>
、<font style="color:rgb(31, 35, 40);">图片消息</font>
, <font style="color:rgb(31, 35, 40);">群名片消息</font>
、<font style="color:rgb(31, 35, 40);">富文本消息</font>
、<font style="color:rgb(31, 35, 40);">卡片消息</font>
; 同时该插件还提供了<font style="color:rgb(31, 35, 40);">自定义模板</font>
和<font style="color:rgb(31, 35, 40);">变量</font>
的功能,使您能够根据自己的需求来定制通知消息的内容和格式。
下载插件
目前不支持在线安装
Jenkins版本要求:2.414.3+
#离线安装
cd /var/lib/jenkins/plugins
wget https://gitee.com/xm721806280/lark-notice-plugin/releases/download/v2.0.0/lark-notice.hpi
systemctl restart jenkins
控制台验证
机器人配置
打开 Manage Jenkins
页面,找到 Lark Notice
配置项,如下图所示:
默认全部勾选
点击新增机器人
测试是否正常
增加stage
stage('发送卡片消息') {
steps {
echo '发送卡片消息...'
}
post {
success {
dingTalk (
robot: '6081ef0eaec970f0ef3e5f2799f46a8b0e3c343744ec90e990f212b07910f03c',
type: 'CARD',
title: '📢 Jenkins 构建通知',
text: [
"## <font color='green'>📢 Jenkins 构建通知</font>",
"---",
"📋 **任务名称**:${Image_Name} ",
"🔢 **任务编号**:[${BUILD_DISPLAY_NAME}](${BUILD_URL}) ",
"🌟 **构建状态**: <font color='green'>${currentBuild.currentResult}</font> ",
"🕐 **构建用时**: ${currentBuild.duration/1000}秒 ",
"😘 **构建镜像**: ${Full_Image} ",
"😎 **构建环境**: ${env.PLATFORM} ",
"😝 **是否回滚**: ${UserInput} ",
"👤 **执 行 者**: ${env.BUILD_USER} ",
'![图片](https://p5.toutiaoimg.com/origin/pgc-image/4f42eb331c604c5c8a7acd5c833e0208?from=pc) '
],
atAll: true,
buttons: [
[
title: "更改记录",
url: "${BUILD_URL}changes"
],
[
title: "控制台",
type: "danger",
url: "${BUILD_URL}console"
]
]
)
}
}
}
本文来自博客园,作者:&UnstopPable,转载请注明原文链接:https://www.cnblogs.com/Unstoppable9527/p/18418741