k8s中部署Jenkins
系统环境:
-
• kubernetes 版本:1.23.3
-
• jenkins 版本:2.172
-
• jenkins 部署示例文件 Github 地址:https://github.com/my-dlq/blog-example/tree/master/jenkins-deploy
一、设置存储目录
在 Kubenetes 环境下所起的应用都是一个个 Docker 镜像,为了保证应用重启的情况下数据安全,所以需要将 Jenkins 持久化到存储中。这里用的是 NFS 网路存储,方便在 Kubernetes 环境下应用启动节点转义数据一致。当然也可以选择存储到本地,但是为了保证应用数据一致,需要设置 Jenkins 固定到某一 Kubernetes 节点。
1、安装 NFS 服务端
详情请看 CentOS7 搭建 nfs 服务器 ,这里不过多叙述。
2、挂载 NFS 并设置存储文件夹
如果不能直接操作 NFS 服务端创建文件夹,需要知道 NFS 服务器地址,然后将其挂在到本地目录,进入其中创建 Jenkins 目录空间。
(1)、挂载 NFS
$ mount -o vers=4.1 192.168.2.11:/nfs/ /nfs
(2)、在 NFS 共享存储文件夹下创建存储 Jenkins 数据的文件夹
$ mkdir -p /nfs/data/jenkins
二、创建 PV & PVC
创建 PV 绑定 NFS 创建的 Jenkins 目录,然后创建 PVC 绑定这个 PV,将此 PVC 用于后面创建 Jenkins 服务时挂载的存储。
1、准备 PV & PVC 部署文件
一定要确保 PV 的空间大于 PVC,否则无法关联
jenkins-pv-pvc.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: jenkins
labels:
app: jenkins
spec:
capacity:
storage: Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
mountOptions: #NFS挂在选项
- hard
- nfsvers=4.1
nfs: #NFS设置
path: /nfs/data/jenkins
server: 192.168.2.11
---
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: jenkins
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: Gi #生产环境空间一定要设置比较大点
selector:
matchLabels:
app: jenkins
2、创建 PV & PVC
提前将 namespace 修改成你自己的 namespace
-
• -n:指定 namespace
$ kubectl apply -f jenkins-pv-pvc.yaml -n public
三、创建 ServiceAccount & ClusterRoleBinding
此 kubernetes 集群用的是 RBAC 安全插件,必须创建权限给一个 ServiceAccount,然后将此 ServiceAccount 绑定到 Jenkins 服务,这样赋予 Jenkins 服务一定权限执行一些操作,为了方便,这里将 cluster-admin 绑定到 ServiceAccount 以保证 Jenkins 能拥有一定的权限。
注意:请提前修改 yaml 中的 namespace
(1)、jenkins-rbac.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: jenkins-admin #ServiceAccount名
namespace: mydlqcloud #指定namespace,一定要修改成你自己的namespace
labels:
name: jenkins
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: jenkins-admin
labels:
name: jenkins
subjects:
- kind: ServiceAccount
name: jenkins-admin
namespace: mydlqcloud
roleRef:
kind: ClusterRole
name: cluster-admin
apiGroup: rbac.authorization.k8s.io
(2)、创建 RBAC 命令
$ kubectl create -f jenkins-rbac.yaml
四、创建 Service & Deployment
这里开始部署 Jenkins 服务,创建 Service 与 Deployment,其中 Service 暴露两个接口 80880 与 50000。而 Deployment 里面要注意的是要设置上面创建的 ServiceAccount ,并且设置容器安全策略为“runAsUser: 0”以 Root 权限运行容器,而且暴露8080、50000两个端口。
1、创建 Service & Deployment 部署文件
jenkins-deployment.yaml
apiVersion: v1
kind: Service
metadata:
name: jenkins
labels:
app: jenkins
spec:
type: NodePort
ports:
- name: http
port: #服务端口
targetPort:
nodePort: #NodePort方式暴露 Jenkins 端口
- name: jnlp
port: #代理端口
targetPort:
nodePort:
selector:
app: jenkins
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: jenkins
labels:
app: jenkins
spec:
selector:
matchLabels:
app: jenkins
replicas:
template:
metadata:
labels:
app: jenkins
spec:
serviceAccountName: jenkins-admin
containers:
- name: jenkins
image: registry.cn-shanghai.aliyuncs.com/mydlq/jenkins:2.172
securityContext:
runAsUser: #设置以ROOT用户运行容器
privileged: true #拥有特权
ports:
- name: http
containerPort:
- name: jnlp
containerPort:
resources:
limits:
memory: Gi
cpu: "1000m"
requests:
memory: Gi
cpu: "500m"
env:
- name: LIMITS_MEMORY
valueFrom:
resourceFieldRef:
resource: limits.memory
divisor: Mi
- name: "JAVA_OPTS" #设置变量,指定时区和 jenkins slave 执行者设置
value: "
-Xmx$(LIMITS_MEMORY)m
-XshowSettings:vm
-Dhudson.slaves.NodeProvisioner.initialDelay=0
-Dhudson.slaves.NodeProvisioner.MARGIN=50
-Dhudson.slaves.NodeProvisioner.MARGIN0=0.85
-Duser.timezone=Asia/Shanghai
"
- name: "JENKINS_OPTS"
value: "--prefix=/jenkins" #设置路径前缀加上 Jenkins
volumeMounts: #设置要挂在的目录
- name: data
mountPath: /var/jenkins_home
volumes:
- name: data
persistentVolumeClaim:
claimName: jenkins #设置PVC
参数说明:
-
• JAVA_OPTS: JVM 参数设置
-
• JENKINS_OPTS: Jenkins 参数设置
-
• 设置执行任务时候不等待:
默认情况下,Jenkins生成代理是保守的。例如,如果队列中有两个构建,它不会立即生成两个执行器。它将生成一个执行器,并等待某个时间释放第一个执行器,然后再决定生成第二个执行器。Jenkins确保它生成的每个执行器都得到了最大限度的利用。如果你想覆盖这个行为,并生成一个执行器为每个构建队列立即不等待,所以在Jenkins启动时候添加这些参数:
-
• -Dhudson.slaves.NodeProvisioner.initialDelay=0
-
• -Dhudson.slaves.NodeProvisioner.MARGIN=50
-
• -Dhudson.slaves.NodeProvisioner.MARGIN0=0.85
2、部署 Jenkins
执行 Kuberctl 命令将 Jenkins 部署到 Kubernetes 集群。
注意:将“-n”后面的 namespace 换成你自己的 namespace
-
• -n:指定应用启动的 namespace
$ kubectl create -f jenkins-deployment.yaml -n mydlqcloud
五、获取 Jenkins 生成的 Token
在安装 Jenkins 时候,它默认生成一段随机字符串,用于安装验证。这里访问它的安装日志,获取它生成的 Token 字符串。
(1)、查看 Jenkins Pod 启动日志
注意:这里“-n”指的是要 namespace,后面跟的 namespace 请替换成你jenkins 启动的 namespace
$ kubectl log $(kubectl get pods -n mydlqcloud | awk '{print $1}' | grep jenkins) -n mydlqcloud
(2)、查看日志中生成的 Token 字符串
查看日志,默认给的token为:
*************************************************************
*************************************************************
*************************************************************
Jenkins initial setup is required. An admin user has been created and a password generated.
Please use the following password to proceed to installation:
3b3e0dda9d6746358ade987775f924ef
This may also be found at: /var/jenkins_home/secrets/initialAdminPassword
*************************************************************
*************************************************************
*************************************************************
六、启动 Jenkins 进行安装
输入集群地址和 Jenkins Service 提供的 NodePort 端口,访问 Jenkins 进行安装步骤,可以按下一步步执行:
1、进入Jenkins
输入集群地址和上面设置的Nodeport方式的端口 32001,然后输入上面获取的 Token 字符串。例如,本人的集群IP为 192.168.2.11 ,所以就可以访问 http://192.168.2.11:32001/jenkins ,进入后可以看到下面的界面。
2、安装插件
选择自定义插件来进行安装
安装一些常用的插件,这里可以选择一下,推荐安装下面插件。
确定后可以看到正在安装插件界面
3、设置用户名、密码
然后进入 Jenkins 界面
七、其他
1、插件因网络问题安装失败
如果 Jenkins 安装插件失败,则可以按一下设置
-
• (1)、进入地址 http://192.168.2.11:32001/jenkins/pluginManager/advanced
-
• (2)、将最下面的 Update Site 的 URL 地址替换成:http://mirror.esuni.jp/jenkins/updates/update-center.json
-
• (3)、点“submit”按钮,然后点右下角角 “check now”
-
• (4)、然后输入地址 http://192.168.2.11:32001/jenkins/restart 重启 jenkins 后再重新安装插件
PS:上面地址替换成你们的集群地址及端口。
基于jenkins+k8s(container)实现CI/CD
CI/CD是什么
CI(持续集成)
持续集成是一种软件开发实践,通过自动化工具对代码进行编译、测试和打包,减少人工干预,提高构建效率。它的核心理念是将代码频繁地集成到共享存储库中,并通过自动化构建和测试流程来验证代码的正确性。这样做可以为开发人员提供即时的反馈,帮助他们快速定位并修复问题,从而加速软件开发周期并提高软件质量。
CD(持续交付)
持续交付是一种软件开发实践,通过自动化工具建立一套自动化的流水线,将应用程序部署到不同的环境中,例如开发环境、测试环境和生产环境等。这种流水线能够自动化地执行构建、测试、部署和发布等步骤,使得软件可以在不同环境中快速、可靠地交付给最终用户。持续交付的目标是确保软件可以随时随地以可靠的方式交付给用户,从而缩短交付周期、降低发布风险。
CI/CD的关系
CI和CD是相互关联的两个概念,持续集成是持续交付的基础,只有实现了持续集成,才能够实现持续交付。持续集成提高了代码的质量和可维护性,为持续交付提供了更好的基础;而持续交付则能够更快地将代码交付给用户,从而促进持续集成的实施。这两者相辅相成,共同推动着软件开发过程的持续改进和交付效率的提升。
CI/CD项目简介
利用 Jenkins、SonarQube、Harbor、Container、Kubernetes技术,搭建一个完整的 CI/CD 管道,模拟实际生产环境项目开发部署流程,实现持续集成、持续交付和持续部署。通过自动化构建、测试、代码质量检查和容器化部署,将开发人员从繁琐的手动操作中解放出来,提高团队的开发效率、软件质量和安全性,实现持续更新迭代和持续部署交付。
CI/CD流程图
jenkins-jenkins for k8s.drawio.png
流程说明
开发测试阶段
-
开发人员需求确定后,从master分支拉取最新代码,在dev分支完成开发后,将dev分支合并到test分支并推送至gitlab仓库。
-
Gitlab监听到test分支代码更新,请求jenkins的webhook地址,触发持续构建和持续部署流程,准备将代码部署至测试环境。
-
Jenkins从Gitlab中拉取项目源码,编译并打成jar包,然后调用SonarQube完成代码扫描。
-
扫描完成调用docker或者container打包成容器镜像,并推送至Harbor镜像仓库。
-
jenkins修改yaml文件,将资源部署至测试环境。
-
Jenkins完成测试环境CICD流程后,将结果邮件通知给开发运维和测试人员。
-
测试人员访问测试环境,功能验证无误后反馈给开发主管,至此开发测试阶段完成。
生产发布阶段
-
开发主管将test分支代码合并至master分支,并推送至gitlab仓库。
-
gitlab监听到master分支代码更改,请求jenkins的webhook地址,开始触发生产环境cicd流程,使用k8s滚动更新项目版本。
-
jenkins完成cicd流程后自动发送邮件通知,用户访问新版本服务。
项目代码仓库地址
gitee:https://gitee.com/cuiliang0302/sprint_boot_demo
github:https://github.com/cuiliang0302/sprint-boot-demo
Jenkins动态slave介绍
为什么需要动态slave
-
配置管理困难:不同项目可能使用不同的编程语言、框架或库,这导致了每个Slave的配置环境各不相同。因此,需要动态Slave能够根据不同的项目需求,灵活配置不同的运行环境,从而简化配置管理和维护工作。
-
资源分配不均衡:在使用静态Slave时,可能会出现某些Slave处于空闲状态,而其他Slave却处于繁忙状态,导致资源分配不均衡。动态Slave可以根据当前任务的需求自动调配资源,使得任务能够在空闲的Slave上尽快执行,从而提高资源利用率和任务执行效率。
-
资源浪费:静态Slave在没有任务执行时仍然占用着资源,这导致了资源的浪费。而动态Slave能够根据实际需要自动扩容或缩减,当没有任务执行时会释放资源,从而避免了资源的浪费。
动态slave工作流程
正因为上面的这些种种痛点,我们渴望一种更高效更可靠的方式来完成这个 CI/CD 流程,而 Docker虚拟化容器技术能很好的解决这个痛点,又特别是在 Kubernetes 集群环境下面能够更好来解决上面的问题,下图是基于 Kubernetes 搭建 Jenkins 集群的简单示意图:
从图上可以看到 Jenkins Master 和 Jenkins Slave 以 Pod 形式运行在 Kubernetes 集群的 Node 上,Master 运行在其中一个节点,并且将其配置数据存储到一个 Volume 上去,Slave 运行在各个节点上,并且它不是一直处于运行状态,它会按照需求动态的创建并自动删除。这种方式的工作流程大致为:当 Jenkins Master 接受到 Build 请求时,会根据配置的 Label 动态创建一个运行在 Pod 中的 Jenkins Slave 并注册到 Master 上,当运行完 Job 后,这个 Slave 会被注销并且这个 Pod 也会自动删除,恢复到最初状态。
服务部署
本项目所有服务均运行在k8s集群上,使用nfs共享存储,具体部署配置过程可参考下文,此处不再赘述。
k8s部署
参考文档:https://www.cuiliangblog.cn/detail/section/15287639
nfs共享存储部署
参考文档:https://www.cuiliangblog.cn/detail/section/116191364
container部署
参考文档:https://www.cuiliangblog.cn/detail/section/99861101
harbor部署
参考文档:https://www.cuiliangblog.cn/detail/section/99861101
gitlab部署
参考文档:https://www.cuiliangblog.cn/detail/section/131418586
jenkins部署
参考文档:https://www.cuiliangblog.cn/detail/section/131416735
SonarQube部署
参考文档:https://www.cuiliangblog.cn/detail/section/165547985
项目与权限配置
Harbor配置
创建项目Harbor的项目分为公开和私有的: 公开项目:所有用户都可以访问,通常存放公共的镜像,默认有一个library公开项目。私有项目:只有授权用户才可以访问,通常存放项目本身的镜像。我们可以为微服务项目创建一个新的项目
创建用户创建一个普通用户cuiliang。
配置项目用户权限在spring_boot_demo项目中添加普通用户cuiliang,并设置角色为开发者。
权限说明
角色 | 权限 |
---|---|
访客 | 对项目有只读权限 |
开发人员 | 对项目有读写权限 |
维护人员 | 对项目有读写权限、创建webhook权限 |
项目管理员 | 出上述外,还有用户管理等权限 |
上传下载镜像测试可参考文章https://www.cuiliangblog.cn/detail/section/15189547,此处不再赘述。
gitlab项目权限配置
具体gitlab权限配置参考文档:https://www.cuiliangblog.cn/detail/section/131513569
创建开发组develop,用户cuiliang,项目springboot demo创建组管理员用户登录,创建群组,组名称为develop,组权限为私有
创建项目创建sprint boot demo项目,并指定develop,项目类型为私有
创建用户创建一个普通用户cuiliang
用户添加到组中将cuiliang添加到群组develop中,cuiliang角色为Developer
配置分支权限
用户权限验证使用任意一台机器模拟开发人员拉取代码,完成开发后推送至代码仓库。拉取仓库代码
[root@tiaoban opt]# git clone https://gitee.com/cuiliang0302/sprint_boot_demo.git
正克隆到 'sprint_boot_demo'...
remote: Enumerating objects: 69, done.
remote: Counting objects: 100% (69/69), done.
remote: Compressing objects: 100% (54/54), done.
remote: Total 69 (delta 15), reused 0 (delta 0), pack-reused 0
接收对象中: 100% (69/69), 73.15 KiB | 1.49 MiB/s, 完成.
处理 delta 中: 100% (15/15), 完成.
[root@tiaoban opt]# cd sprint_boot_demo/
[root@tiaoban sprint_boot_demo]# ls
email.html Jenkinsfile LICENSE mvnw mvnw.cmd pom.xml readme.md sonar-project.properties src test
推送至gitlab仓库
# 修改远程仓库地址
[root@tiaoban sprint_boot_demo]# git remote set-url origin http://gitlab.local.com/develop/sprint_boot_demo.git
[root@tiaoban sprint_boot_demo]# git remote -v
origin http://gitlab.local.com/develop/sprint_boot_demo.git (fetch)
origin http://gitlab.local.com/develop/sprint_boot_demo.git (push)
# 推送代码至gitlab
[root@tiaoban sprint_boot_demo]# git push --set-upstream origin --all
Username for 'http://gitlab.local.com': cuiliang
Password for 'http://[email protected]':
枚举对象中: 55, 完成.
对象计数中: 100% (55/55), 完成.
使用 4 个线程进行压缩
压缩对象中: 100% (34/34), 完成.
写入对象中: 100% (55/55), 71.51 KiB | 71.51 MiB/s, 完成.
总共 55(差异 10),复用 52(差异 9),包复用 0
To http://gitlab.local.com/develop/sprint-boot-demo.git
* [new branch] main -> main
分支 'main' 设置为跟踪 'origin/main'。
查看验证
jenkins配置
插件安装与配置
GitLab插件安装与配置:
https://www.cuiliangblog.cn/detail/section/127410630
SonarQube Scanner插件安装与配置:
https://www.cuiliangblog.cn/detail/section/165534414
Kubernetes插件安装与配置:
https://www.cuiliangblog.cn/detail/section/127230452
Email Extension邮件推送插件安装与配置:
https://www.cuiliangblog.cn/detail/section/133029974
Version Number版本号插件安装与配置:
https://plugins.jenkins.io/versionnumber/
Content Replace文件内容替换插件安装与配置:
https://plugins.jenkins.io/content-replace/
jenkins slave镜像制作
安装完Kubernetes插件后,默认的slave镜像仅包含一些基础功能和软件包,如果需要构建镜像,执行kubectl命令,则需要引入其他container或者自定义slave镜像。
关于镜像构建问题,如果k8s容器运行时为docker,可以直接使用docker in docker方案,启动一个docker:dind容器,通过Docker pipeline插件执行镜像构建与推送操作,具体内容可参考https://www.cuiliangblog.cn/detail/section/166573065。
如果k8s容器运行时为container,则使用nerdctl+buildkitd方案,启动一个buildkit容器,通过nerdctl命令执行镜像构建与推送操作,具体内容可参考https://www.cuiliangblog.cn/detail/section/167380911。本次实验以container环境为例,通过nerdctl+buildkitd方案演示如何构建并推送镜像。
构建jenkins-slave镜像
[root@tiaoban jenkins]# cat Dockerfile
FROM jenkins/inbound-agent:latest-jdk17
USER root
COPY kubectl /usr/bin/kubectl
COPY nerdctl /usr/bin/nerdctl
COPY buildctl /usr/bin/buildctl
[root@tiaoban jenkins]#
[root@tiaoban jenkins]# docker build -t harbor.local.com/cicd/jenkins-slave:v1.0 .
测试jenkins-slave镜像构建容器与操作k8s
以下操作在k8s集群master机器,容器运行时为container节点执行测试
# 启动buildkit镜像构建服务
# 挂载/run/containerd/containerd.sock方便container调用buildkitd
# 挂载/var/lib/buildkit,以便于将构建过程中下载的镜像持久化存储,方便下次构建时使用缓存
# 挂载/run/buildkit/目录方便nerctl调用buildkitd
[root@master3 ~]# nerdctl run --name buildkit -d --privileged=true \
-v /run/buildkit/:/run/buildkit/ \
-v /var/lib/buildkit:/var/lib/buildkit \
-v /run/containerd/containerd.sock:/run/containerd/containerd.sock \
moby/buildkit:v0.13.2
[root@master3 ~]# nerdctl ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a8de5299dd84 moby/buildkit:v0.13.2 "buildkitd" 4 seconds ago Up buildkit
# 启动jenkins-slave容器
# 挂载/run/containerd/containerd.sock方便netdctl操作container
# 挂载/run/buildkit/目录方便nerctl调用buildkitd构建镜像
# 挂载/root/.kube/目录方便kubectl工具操作k8s
[root@master3 ~]# nerdctl run --name jenkins-slave -it --privileged=true \
-v /run/buildkit/:/run/buildkit/ \
-v /root/.kube/:/root/.kube/ \
-v /run/containerd/containerd.sock:/run/containerd/containerd.sock \
harbor.local.com/cicd/jenkins-slave:v1.0 bash
# 测试container管理
root@28dcd3a667c9:/home/jenkins# nerdctl ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
28dcd3a667c9 harbor.local.com/cicd/jenkins-slave:v1.0 "/usr/local/bin/jenk…" 11 seconds ago Up jenkins-slave
a8de5299dd84 harbor.local.com/cicd/buildkit:v0.13.2 "buildkitd" 11 minutes ago Up buildkit
# 测试k8s管理
root@28dcd3a667c9:/home/jenkins# kubectl get node
NAME STATUS ROLES AGE VERSION
master1 Ready control-plane 241d v1.27.6
master2 Ready control-plane 241d v1.27.6
master3 Ready control-plane 241d v1.27.6
work1 Ready <none> 241d v1.27.6
work2 Ready <none> 241d v1.27.6
work3 Ready <none> 241d v1.27.6
# 测试镜像构建
root@28dcd3a667c9:/home/jenkins# echo 'FROM busybox' >> Dockerfile
root@28dcd3a667c9:/home/jenkins# echo 'CMD ["echo","hello","container"]' >> Dockerfile
root@28dcd3a667c9:/home/jenkins# cat Dockerfile
FROM busybox
CMD ["echo","hello","container"]
root@28dcd3a667c9:/home/jenkins# nerdctl build -t test:v1 .
root@28dcd3a667c9:/home/jenkins# nerdctl images
REPOSITORY TAG IMAGE ID CREATED PLATFORM SIZE BLOB SIZE
test v1 4943762c7956 7 seconds ago linux/amd64 4.1 MiB 2.1 MiB
harbor.local.com/cicd/buildkit v0.13.2 c3cb08891c15 15 minutes ago linux/amd64 190.3 MiB 87.2 MiB
harbor.local.com/cicd/jenkins-slave v1.0 1d5c5b1572bc 6 minutes ago linux/amd64 384.7 MiB 169.8 MiB
job任务创建与配置
配置webhook构建触发器,当分支代码提交时触发构建,具体配置如下:
流水线选择SCM从代码仓库中获取jenkinsfile,脚本路径填写Jenkinsfile-k8s.groov
效果演示
开发测试阶段
模拟开发人员完成功能开发后提交代码至test分支
[root@tiaoban sprint_boot_demo]# git checkout -b test origin/test
分支 'test' 设置为跟踪 'origin/test'。
切换到一个新分支 'test'
[root@tiaoban sprint_boot_demo]# git branch -a
master
* test
remotes/origin/HEAD -> origin/master
remotes/origin/master
remotes/origin/test
# 修改SpringBoot首页内容为Version:v2
[root@tiaoban sprint_boot_demo]# cat src/main/java/com/example/springbootdemo/HelloWorldController.java
package com.example.springbootdemo;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class HelloWorldController {
@RequestMapping("/")
@ResponseBody
public String hello() {
return "<h1>Hello SpringBoot</h1><p>Version:v2 Env:test</p>";
}
@RequestMapping("/health")
@ResponseBody
public String healthy() {
return "ok";
}
}
[root@tiaoban sprint_boot_demo]# git add .
[root@tiaoban sprint_boot_demo]# git commit -m "test环境更新版本至v2"
[test 68fb576] test环境更新版本至v2
1 file changed, 1 insertion(+), 1 deletion(-)
[root@tiaoban sprint_boot_demo]# git push
枚举对象中: 17, 完成.
对象计数中: 100% (17/17), 完成.
使用 4 个线程进行压缩
压缩对象中: 100% (6/6), 完成.
写入对象中: 100% (9/9), 707 字节 | 707.00 KiB/s, 完成.
总共 9(差异 2),复用 0(差异 0),包复用 0
remote:
remote: To create a merge request for test, visit:
remote: http://gitlab.local.com/develop/sprint_boot_demo/-/merge_requests/new?merge_request%5Bsource_branch%5D=test
remote:
To http://gitlab.local.com/develop/sprint_boot_demo.git
86a166a..68fb576 test -> test
此时查看cicd名称空间下的pod信息,发现已经创建一个名为springbootdemo-275-rf832-h6jkq-630x8的pod,包含3个container,分别是jnlp、maven、buildkitd。
[root@tiaoban ~]# kubectl get pod -n cicd
NAME READY STATUS RESTARTS AGE
gitlab-5997c5cdcd-2rvgz 1/1 Running 14 (100m ago) 15d
jenkins-6df7d6479b-v25rt 1/1 Running 9 (100m ago) 5d13h
sonarqube-postgresql-0 1/1 Running 14 (100m ago) 15d
sonarqube-sonarqube-0 1/1 Running 14 (100m ago) 15d
springbootdemo-275-rf832-h6jkq-630x8 3/3 Running 0 65s
查看jenkins任务信息,已顺利完成了集成部署工作。
并且收到了jenkins自动发出的邮件,内容如下:
查看SonarQube代码扫描信息,未发现异常代码。
查看k8s,已成功创建相关资源。
[root@tiaoban sprint_boot_demo]# kubectl get all -n test
NAME READY STATUS RESTARTS AGE
pod/demo-5d44f794d9-s7jw2 1/1 Running 0 7m38s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/demo ClusterIP 10.111.167.204 <none> 8888/TCP 4d3h
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/demo 1/1 1 1 4d3h
NAME DESIRED CURRENT READY AGE
replicaset.apps/demo-5d44f794d9 1 1 1 7m38s
此时模拟测试人员,访问测试环境域名
至此,开发测试阶段演示完成。
生产发布阶段
接下来演示master分支代码提交后,触发生产环境版本发布流程。
[root@tiaoban sprint_boot_demo]# git branch -a
master
* test
remotes/origin/HEAD -> origin/master
remotes/origin/master
remotes/origin/test
[root@tiaoban sprint_boot_demo]# git checkout master
切换到分支 'master'
您的分支与上游分支 'origin/master' 一致。
[root@tiaoban sprint_boot_demo]# vim src/main/java/com/example/springbootdemo/HelloWorldController.java
[root@tiaoban sprint_boot_demo]# cat src/main/java/com/example/springbootdemo/HelloWorldController.java
package com.example.springbootdemo;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class HelloWorldController {
@RequestMapping("/")
@ResponseBody
public String hello() {
return "<h1>Hello SpringBoot</h1><p>Version:v2 Env:prod</p>";
}
@RequestMapping("/health")
@ResponseBody
public String healthy() {
return "ok";
}
}
[root@tiaoban sprint_boot_demo]# git add .
[root@tiaoban sprint_boot_demo]# git commit -m "生产环境更新版本至v2"
[master 889fc5c] 生产环境更新版本至v2
1 file changed, 1 insertion(+), 1 deletion(-)
[root@tiaoban sprint_boot_demo]# git push
枚举对象中: 17, 完成.
对象计数中: 100% (17/17), 完成.
使用 4 个线程进行压缩
压缩对象中: 100% (6/6), 完成.
写入对象中: 100% (9/9), 719 字节 | 719.00 KiB/s, 完成.
总共 9(差异 2),复用 0(差异 0),包复用 0
To http://gitlab.local.com/develop/sprint_boot_demo.git
600a1b6..889fc5c master -> master
待收到邮件通知后,查看k8s资源,已经在prod名称空间下创建相关资源
[root@tiaoban sprint_boot_demo]# kubectl get all -n prod
NAME READY STATUS RESTARTS AGE
pod/demo-7c57975bd8-7nmnx 1/1 Running 0 41s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/demo ClusterIP 10.97.0.219 <none> 8888/TCP 41s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/demo 1/1 1 1 41s
NAME DESIRED CURRENT READY AGE
replicaset.apps/demo-7c57975bd8 1 1 1 41s
此时访问生产环境域名,服务可以正常访问。
此时查看Harbor仓库镜像信息,其中p开头的为生产环境镜像,t开头的为测试环境镜像。
jenkinsfile
完整的jenkinsfile如下所示,由于每个项目使用的开发语言和版本各不相同,因此建议将jenkinsfile存储在代码仓库随项目一同管理,使用yaml格式可以最大程度的定制slave容器。
pipeline {
agent {
kubernetes {
// 定义要在 Kubernetes 中运行的 Pod 模板
yaml '''
apiVersion: v1
kind: Pod
metadata:
labels:
app: jenkins-slave
spec:
containers:
- name: jnlp
image: harbor.local.com/cicd/jenkins-slave:v1.0
resources:
limits:
memory: "512Mi"
cpu: "500m"
securityContext:
privileged: true
volumeMounts:
- name: buildkit
mountPath: "/run/buildkit/"
- name: containerd
mountPath: "/run/containerd/containerd.sock"
- name: kube-config
mountPath: "/root/.kube/"
readOnly: true
- name: maven
image: harbor.local.com/cicd/maven:3.9.3
resources:
limits:
memory: "512Mi"
cpu: "500m"
command:
- 'sleep'
args:
- '9999'
volumeMounts:
- name: maven-data
mountPath: "/root/.m2"
- name: buildkitd
image: harbor.local.com/cicd/buildkit:v0.13.2
resources:
limits:
memory: "256Mi"
cpu: "500m"
securityContext:
privileged: true
volumeMounts:
- name: buildkit
mountPath: "/run/buildkit/"
- name: buildkit-data
mountPath: "/var/lib/buildkit/"
- name: containerd
mountPath: "/run/containerd/containerd.sock"
volumes:
- name: maven-data
persistentVolumeClaim:
claimName: jenkins-maven
- name: buildkit
hostPath:
path: /run/buildkit/
- name: buildkit-data
hostPath:
path: /var/lib/buildkit/
- name: containerd
hostPath:
path: /run/containerd/containerd.sock
- name: kube-config
secret:
secretName: kube-config
'''
retries 2
}
}
environment {
// 全局变量
HARBOR_CRED = "harbor-cuiliang-password"
IMAGE_NAME = ""
IMAGE_APP = "demo"
branchName = ""
}
stages {
stage('拉取代码') {
environment {
// gitlab仓库信息
GITLAB_CRED = "gitlab-cuiliang-password"
GITLAB_URL = "http://gitlab.cicd.svc/develop/sprint_boot_demo.git"
}
steps {
echo '开始拉取代码'
checkout scmGit(branches: [[name: '*/*']], extensions: [], userRemoteConfigs: [[credentialsId: "${GITLAB_CRED}", url: "${GITLAB_URL}"]])
// 获取当前拉取的分支名称
script {
def branch = env.GIT_BRANCH ?: 'master'
branchName = branch.split('/')[-1]
}
echo '拉取代码完成'
}
}
stage('编译打包') {
steps {
container('maven') {
// 指定使用maven container进行打包
echo '开始编译打包'
sh 'mvn clean package'
echo '编译打包完成'
}
}
}
stage('代码审查') {
environment {
// SonarQube信息
SONARQUBE_SCANNER = "SonarQubeScanner"
SONARQUBE_SERVER = "SonarQubeServer"
}
steps {
echo '开始代码审查'
script {
def scannerHome = tool "${SONARQUBE_SCANNER}"
withSonarQubeEnv("${SONARQUBE_SERVER}") {
sh "${scannerHome}/bin/sonar-scanner"
}
}
echo '代码审查完成'
}
}
stage('构建镜像') {
environment {
// harbor仓库信息
HARBOR_URL = "harbor.local.com"
HARBOR_PROJECT = "spring_boot_demo"
// 镜像标签
IMAGE_TAG = ''
// 镜像名称
IMAGE_NAME = ''
}
steps {
echo '开始构建镜像'
script {
if (branchName == 'master') {
IMAGE_TAG = VersionNumber versionPrefix: 'p', versionNumberString: '${BUILD_DATE_FORMATTED, "yyMMdd"}.${BUILDS_TODAY}'
} else if (branchName == 'test') {
IMAGE_TAG = VersionNumber versionPrefix: 't', versionNumberString: '${BUILD_DATE_FORMATTED, "yyMMdd"}.${BUILDS_TODAY}'
} else {
error("Unsupported branch: ${params.BRANCH}")
}
IMAGE_NAME = "${HARBOR_URL}/${HARBOR_PROJECT}/${IMAGE_APP}:${IMAGE_TAG}"
sh "nerdctl build --insecure-registry -t ${IMAGE_NAME} . "
}
echo '构建镜像完成'
echo '开始推送镜像'
// 获取harbor账号密码
withCredentials([usernamePassword(credentialsId: "${HARBOR_CRED}", passwordVariable: 'HARBOR_PASSWORD', usernameVariable: 'HARBOR_USERNAME')]) {
// 登录Harbor仓库
sh """nerdctl login --insecure-registry ${HARBOR_URL} -u ${HARBOR_USERNAME} -p ${HARBOR_PASSWORD}
nerdctl push --insecure-registry ${IMAGE_NAME}"""
}
echo '推送镜像完成'
echo '开始删除镜像'
script {
sh "nerdctl rmi -f ${IMAGE_NAME}"
}
echo '删除镜像完成'
}
}
stage('项目部署') {
environment {
// 资源清单名称
YAML_NAME = "k8s.yaml"
}
steps {
echo '开始修改资源清单'
script {
if (branchName == 'master' ) {
NAME_SPACE = 'prod'
DOMAIN_NAME = 'demo.local.com'
} else if (branchName == 'test') {
NAME_SPACE = 'test'
DOMAIN_NAME = 'demo.test.com'
} else {
error("Unsupported branch: ${params.BRANCH}")
}
}
// 使用Content Replace插件进行k8s资源清单内容替换
contentReplace(configs: [fileContentReplaceConfig(configs: [fileContentReplaceItemConfig(replace: "${IMAGE_NAME}", search: 'IMAGE_NAME'),
fileContentReplaceItemConfig(replace: "${NAME_SPACE}", search: 'NAME_SPACE'),
fileContentReplaceItemConfig(replace: "${DOMAIN_NAME}", search: 'DOMAIN_NAME')],
fileEncoding: 'UTF-8',
filePath: "${YAML_NAME}",
lineSeparator: 'Unix')])
echo '修改资源清单完成'
sh "cat ${YAML_NAME}"
echo '开始部署资源清单'
sh "kubectl apply -f ${YAML_NAME}"
echo '部署资源清单完成'
}
}
}
post {
always {
echo '开始发送邮件通知'
emailext(subject: '构建通知:${PROJECT_NAME} - Build # ${BUILD_NUMBER} - ${BUILD_STATUS}!',
body: '${FILE,path="email.html"}',
to: '[email protected]')
echo '邮件通知发送完成'
}
}
}
标签:CI,container,name,demo,boot,Jenkins,jenkins,k8s,root
From: https://blog.csdn.net/weixin_45623111/article/details/139226764