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

流程说明

开发测试阶段

  1. 开发人员需求确定后,从master分支拉取最新代码,在dev分支完成开发后,将dev分支合并到test分支并推送至gitlab仓库。
  2. Gitlab监听到test分支代码更新,请求jenkins的webhook地址,触发持续构建和持续部署流程,准备将代码部署至测试环境。
  3. Jenkins从Gitlab中拉取项目源码,编译并打成jar包,然后调用SonarQube完成代码扫描。
  4. 扫描完成调用docker或者container打包成容器镜像,并推送至Harbor镜像仓库。
  5. jenkins修改yaml文件,将资源部署至测试环境。
  6. Jenkins完成测试环境CICD流程后,将结果邮件通知给开发运维和测试人员。
  7. 测试人员访问测试环境,功能验证无误后反馈给开发主管,至此开发测试阶段完成。

生产发布阶段

  1. 开发主管将test分支代码合并至master分支,并推送至gitlab仓库。
  2. gitlab监听到master分支代码更改,请求jenkins的webhook地址,开始触发生产环境cicd流程,使用k8s滚动更新项目版本。
  3. jenkins完成cicd流程后自动发送邮件通知,用户访问新版本服务。

项目代码仓库地址

gitee:https://gitee.com/cuiliang0302/sprint_boot_demo

github:https://github.com/cuiliang0302/sprint-boot-demo

Jenkins动态slave介绍

为什么需要动态slave

  1. 配置管理困难: 不同项目可能使用不同的编程语言、框架或库,这导致了每个Slave的配置环境各不相同。因此,需要动态Slave能够根据不同的项目需求,灵活配置不同的运行环境,从而简化配置管理和维护工作。
  2. 资源分配不均衡: 在使用静态Slave时,可能会出现某些Slave处于空闲状态,而其他Slave却处于繁忙状态,导致资源分配不均衡。动态Slave可以根据当前任务的需求自动调配资源,使得任务能够在空闲的Slave上尽快执行,从而提高资源利用率和任务执行效率。
  3. 资源浪费: 静态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://cuiliang@gitlab.local.com': 
枚举对象中: 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: 'cuiliang0302@qq.com')
            echo '邮件通知发送完成'
        }
    }
}
我的博客只写前端博文,点击我去看更多喜欢的前端博文,欢迎大家一起讨论学习!【https://blog.csdn.net/qq_29101285?spm=1011.2266.3001.5343】
Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐