Jenkins的CI/CD在k8s中的实践

Jenkins的CI/CD在k8s中的实践 1 背景介绍
之前我们都是在物理机器或者虚拟机上部署jenkins,但是这种传统的 Jenkins master/slave 一主多从的结构会存在一些痛点:

  • 主 master 节点存在单点故障的问题
  • 每个 slave 的配置环境都不一样,来完成不同编程语言的编译打包等操作,但是这些差异化的配置导致管理起来非常不便,维护起来吃力。
  • 资源分配不均衡,有的 slave 要运行的 job 出现排队等候,而有的 slave 处于空闲状态
  • 资源浪费,每台slave运行完job后, 就处于空闲状态了,这样不释放会占用服务器的磁盘资源
正由于上面种种痛点,我们需要一种更直接更高效的方式来完成这个 CI/CD 的过程,希望当我有job需要跑的时候,就给我拉起来一个slave,在这个slave里,把我的job给跑完成后,这个slave的生命周期也就over了。那么这就催生 jenkins 在 k8s 中的构建。
Jenkins的CI/CD在k8s中的实践
文章图片

从上图可知:jenkins master 和 slave都是以container 的形式运行在 kubernetes 集群的 pod上的,master 运行在一个k8s的node上,并且通过数据卷的形式把 jenkins-master 的配置数据存储下来,slave运行在各个节点上,并且不是一直处于running状态,它会按照需求的动态创建及自动删除。
上图的大致工作流程为:当 jenkins master 接受到 build 请求时,会根据配置的 label 动态创建一个运行在 container 中的 jenkins slave 并注册到 master 上,当运行完成 job 后,这个slave 会被注销并且container 也会自动删除,恢复到最初状态。
这种架构带来的好处:
  • 服务高可用: 当Jenkins master 出现故障时,kubernetes 会自动创建一个新的 jenkins master 容器,并且 volume 分配给新创建的容器,保证数据的不丢失,从而达到集群服务高可用。
  • 动态伸缩: 合理使用资源,每次运行 job 时,会自动创建一个 jenkins slave, job完成后,slave 自动注销并删除容器,资源自动释放,而且kuberneres 会根据每个资源的使用情况,动态分配slave 到空闲的节点上创建,降低出现因某些节点资源利用率高,还出现排队等待的情况。
  • 扩展性好:当kubernetes集群的资源严重不足而导致job排队等待时,可以很容器添加一个k8s的work node节点到集群中。
2 部署jenkins
Tool Version 备注
Kubernetes v18.20
Jenkins Jenkins 2.332.3
Jenkins-slave jenkins/inbound-agent:4.11-1-jdk11 官方镜像
jenkins/slave 0.1 基于官方镜像自定义增加了docker、kubectl 工具
jenkins/slave 0.2 基于官方镜像自定义增加了docker、ku bectl、mvn工具
其中 jenkins/slave:0.1 镜像的构建过程
FROM jenkins/inbound-agent:4.11-1-jdk11LABEL AUTHOR SHUTANGCOPY kubectl /usr/local/bin/kubectlUSER rootRUN apt-get update \ && apt upgrade -y \ && apt install -y curl vim wget gnupg apt-transport-https lsb-release ca-certificates \ && wget -O /usr/share/keyrings/docker.asc https://download.docker.com/linux/debian/gpg \ && echo "deb [signed-by=/usr/share/keyrings/docker.asc] https://download.docker.com/linux/debian $(lsb_release -sc) stable" > /etc/apt/sources.list.d/docker.list \ && apt update -y \ #&& apt-cache madison docker-ce \ && apt-get install -y docker-ce \ && usermod -a -G docker jenkins #&& sed -i '/^root/a\jenkinsALL=(ALL:ALL) NOPASSWD:ALL' /etc/sudoersWORKDIR /home/jenkinsUSER jenkinsENTRYPOINT ["jenkins-slave"]

我把master 节点部署到k8s集群中
kubectl apply -f kube-ci.yaml kubectl apply -f jenkins.yaml# 我们采用nodeport的方式把jenkins service 服务的8080端口映射了node上的20000端口,此端口即为jenkins master的页面管理端口,而与jenkins slave通信的端口50000我们采用默认的 ClusterIp 服务类型。

Jenkins的CI/CD在k8s中的实践
文章图片

Jenkins的CI/CD在k8s中的实践
文章图片

Jenkins的CI/CD在k8s中的实践
文章图片

Jenkins的CI/CD在k8s中的实践
文章图片

由于我这里的机器是使用的gcp,是可以直接连接外网的,可以直接安装建议的插件
Jenkins的CI/CD在k8s中的实践
文章图片

Jenkins的CI/CD在k8s中的实践
文章图片

Jenkins的CI/CD在k8s中的实践
文章图片

Jenkins的CI/CD在k8s中的实践
文章图片

Jenkins的CI/CD在k8s中的实践
文章图片

Jenkins的CI/CD在k8s中的实践
文章图片

对下面这张图片先有个印象
Jenkins的CI/CD在k8s中的实践
文章图片

3 安装kubernetes插件和配置相关参数
3.1 安装插件kubernetes Jenkins的CI/CD在k8s中的实践
文章图片

Jenkins的CI/CD在k8s中的实践
文章图片

重启jenkins对应的pod
shutang@luna2:~$ kubectl get pod -n kube-cicd NAMEREADYSTATUSRESTARTSAGE jenkins-f875d95c-sx6z81/1Running025m shutang@es2:~$ kubectl delete pod jenkins-f875d95c-sx6z8 -n kube-cicd pod "jenkins-f875d95c-sx6z8" deleted# 稍等一会1分钟左右 shutang@luna2:~$ kubectl get pod -n kube-cicd NAMEREADYSTATUSRESTARTSAGE jenkins-f875d95c-fbk561/1Running078s shutang@luna2:~$shutang@luna2:~$ kubectl get all -n kube-cicd NAMEREADYSTATUSRESTARTSAGE pod/jenkins-f875d95c-fbk561/1Running071sNAMETYPECLUSTER-IPEXTERNAL-IPPORT(S)AGE service/jenkinsNodePort10.103.47.2218080:20000/TCP26m service/jenkins-agentClusterIP10.101.152.7050000/TCP26mNAMEREADYUP-TO-DATEAVAILABLEAGE deployment.apps/jenkins1/11126mNAMEDESIREDCURRENTREADYAGE replicaset.apps/jenkins-f875d95c11126m

Jenkins的CI/CD在k8s中的实践
文章图片

Jenkins的CI/CD在k8s中的实践
文章图片

其中 Kubernetes URL 填写 kubernetes svc 的servicename
shutang@luna2:~$ kubectl get svc NAMETYPECLUSTER-IPEXTERNAL-IPPORT(S)AGE kubernetesClusterIP10.96.0.1443/TCP46d

其中 Kubernetes Namespace 填写 我们 jenkins master 部署的命名空间 kube-cicd
然后点击链接测试,当出现kubernetes 版本时就正常可以了。
Jenkins的CI/CD在k8s中的实践
文章图片

注意 namespace,我这里填 kube-cicd,然后点击 Test Connection, 如果出现了 Connected to kubernetes v1.18.20,就证明Jenkins 和 Kubernetes 系统正常通信了,然后下方的 Jenkins url地址:jenkins.kube-cicd.svc.cluster.local:8080,这里的格式为:ServiceName.namespace.svc.cluster.local:8080, 根据上面创建的 jenkins 的服务名填写。
另外需要注意,如果这里 Test Connection 失败的话,很有可能是权限问题,这里就需要把我们创建的 jenkins 的 serviceAccount 对应的 secret 添加到这里的 Credentials 里面.
3.2 配置pod模版 配置 Pod Template,其实就是配置 Jenkins Slave 运行的 Pod 模板,命名空间我们同样是用 kube-cicd,Labels 这里也非常重要,对于后面执行 Job 的时候需要用到该值,然后我们这里使用的是 jenkins/slave:0.1 这个镜像,这个镜像是在官方的 jenkins/inbound-agent:4.11-1-jdk11 镜像基础上定制的,加入了docker、 kubectl 等一些实用的工具。
Jenkins的CI/CD在k8s中的实践
文章图片

【Jenkins的CI/CD在k8s中的实践】Jenkins的CI/CD在k8s中的实践
文章图片

另外需要挂载宿主机两个目录到jenkins slave所在的pod中
  1. /var/run/docker.sock: 该文件是用于pod中的容器能够共享宿主机的docker
  2. /home/shutang/.kube: 这个目录挂载到容器的/root/.kube 目录下面这是为了让我们能够在pod的容器中能够使用kubectl工具来访问我们的kubernetes集群,方便后面我们在slave pod 部署kubernetes应用.
Jenkins的CI/CD在k8s中的实践
文章图片

Jenkins的CI/CD在k8s中的实践
文章图片

在创建第二个挂载卷的时候,由于这个 jenkins-slave 是部署在 k8s 集群的 node 节点上的,首先需要到 node 节点上查看一下,发现宿主机的 /home/shutang/.kube (我用的普通用户 shutang 部署的 k8s 集群)并没有任何文件或文件夹,不像master节点上相对应的目录下是有内容的。这时候就需要把 master 主机上 /home/shutang/.kube/config 文件拷贝到node 节点上。
下面在填写一个service account
Jenkins的CI/CD在k8s中的实践
文章图片

4 在jenkins里运行一个自由的jenkins job
Kubernetes 插件参数配置完成后,我们就来添加一个 job 任务,看看是否能够在 slave pod 中执行, 任务执行完成后看看 pod 是否会被销毁。
4.1 在 Jenkins 首页创建一个 freestyle project Jenkins的CI/CD在k8s中的实践
文章图片

Jenkins的CI/CD在k8s中的实践
文章图片

这里Label Expression 要与我们在创建Pod 模版的时候的Label一样
注意: 要保证 work node 节点上的 /var/run/docker.sock 的权限为 777
Jenkins的CI/CD在k8s中的实践
文章图片

点击 Build Now
Jenkins的CI/CD在k8s中的实践
文章图片

Jenkins的CI/CD在k8s中的实践
文章图片

5 在 jenkins 里运行一个 pipeline 流水线类型的 Jenkins job
Jenkins的CI/CD在k8s中的实践
文章图片

Jenkins的CI/CD在k8s中的实践
文章图片

Jenkins的CI/CD在k8s中的实践
文章图片

是不是也证明我们当前的任务在跑在上面动态生成的这个 Pod 中,也符合我们的预期。我们回到 Job 的主界面,也可以看到大家可能比较熟悉的 Stage View 界面:
Jenkins的CI/CD在k8s中的实践
文章图片

6 自动化构建 golang 应用
上面我们已经知道了如何在 jenkins slave中构建任务了,那么如何来部署一个原生的kubernetes 应用呢?要部署 kubernetes 应用
的流程非常熟悉才可以,我们之前的流程是如何的呢:
  • 本地编写代码
  • 本地测试
  • 本地编写 Dockerfile
  • 推送代码和 Dockerfile文件到公司仓库
  • 从git代码仓库拉去代码文件,构建打包Docker镜像
  • 推送镜像到私有的镜像仓库
  • 编写 Kubernetes YAML 文件
  • 更改 YAML 文件中的 Docker 镜像的 TAG
  • 利用 kubectl 工具部署应用
我们之前在 kubernetes 环境中部署一个原生应用的流程基本上就是上面这些过程,现在我们就是需要把上面这些流程放入 jenkins 中来自动帮我们完成(编码除外),从测试到更新 yaml 文件属于 CI (Continuous Integration 持续集成)流程,后面部署属于 CD (Continuous Delivery 持续交付) 的流程。如果按照上面的流程,我们现在来编写一个Pipeline 的脚本,应该如何编写呢?
Jenkinsfile (Declarative Pipeline)
pipeline { agent any stages { stage('Clone') { steps { echo "1.Clone Stage" } } stage('Test') { steps { echo "2.Test Stage" } } stage('Build') { steps { echo "3.Build Docker Image Stage" } } stage('Push') { steps { echo "4.Push Docker Image Stage" } } stage('YAML') { steps { echo "5. Change YAML File Stage" } } stage('Deploy') { steps { echo "6. Deploy Stage" sh 'kubectl apply -f xxx.yaml' } }}}

常用参数说明:
  • Pipeline 是声明式流水线的一种特定语法,定义了包含执行整个流水线的所有内容和指令。
  • agent 是声明式流水线的一种特定语法,指示 Jenkins 为整个流水线分配一个执行器(在节点上)和工作区。
  • stage 是一个描述流水线阶段的语法块,在脚本化流水线语法中,stage(阶段)块 是可选的。
  • steps 是声明式流水线的一种特定语法,它描述了在这个 stage 中要运行的步骤。
  • node 是脚本化流水线的一种特定语法,它指示 Jenkins 在任何可用的代理/节点上执行流水线,这实际等同于声明式流水线特定语法的agent。
上述的声明式流水线等同于以下的脚本式流水线:
node {stage('Clone') { echo "1.Clone Stage" } stage('Test') { echo "2.Test Stage" } stage('Build') { echo "3.Build Docker Image Stage" } stage('Push') { echo "4.Push Docker Image Stage" } stage('YAML') { echo "5. Change YAML File Stage" } stage('Deploy') { echo "6. Deploy Stage" sh 'kubectl apply -f xxx.yaml' }}

这里我们来将一个简单的 golang 程序,部署到 kubernetes 环境中,代码链接:golang
如果按照之前的示例,我们是不是应该像下面这样来编写Pipeline脚本:
  • 第一步: clone代码
  • 第二步:进行测试,如果测试通过了才进行下面的任务
  • 第三步:由于 Dockerfile 基本上都是放入源码中进行管理的,所以我们就是直接构建 Docker 镜像了
  • 第四步:镜像打包完成后,就应该推送到镜像仓库
  • 第五步:镜像推送完成,需要修改yaml文件的镜像的tag
  • 第六步:使用 kubectl 开始部署
第一步:clone代码
stage('Clone') { echo "1.Clone Stage" git url: "https://github.paypal.com/shutang/goproject.git" }

第二步:测试
这部忽略
第三步:构建镜像
stage('Build') { echo "3.Build Docker Image Stage" sh "docker build -t cntsp/jenkins-demo:${build_tag} ." }

我们平时构建的时候是不是都是直接使用 docker build 命令进行构建就行了,那么这个地方呢?我们 4.1 中的创建一个自由风格的job所依赖的 Slave Pod 里的镜像就是采用的 Docker in Docker的方式,也就是说我们也可以直接在Slave 中使用 docker build 命令,所以我们这里直接采用sh执行 docker build 命令,但是镜像的tag呢?我们如果使用镜像 tag,则每次都是 latest 的 tag,这对于以后的排查或者回滚这类的工作会带来很大麻烦,我们这里采用和git commit 的记录序列号作为镜像的tag,这里有一个好处就是镜像的tag 可以和git 提交记录对应起来,也方便日后对应查看。但是由于这个tag不只是我们这一个stage需要使用,下一个推送镜像也需要使用,所以我们这里把这个tag编写成一个公共的参数,把它放在Clone这个stage中,这样一来我们前两个stage就变成下面这样了:
stage('Clone') { echo "1.Clone Stage" git url: "https://github.com/shutang/goproject.git" script { build_tag = sh(returnStdout: true, script: 'git rev-parse --short HEAD').trim() } }stage('Build') { echo "3.Build Docker Image Stage" sh "docker build -t cntsp/jenkins-demo:${build_tag} ." }

第四步:推送镜像
镜像构建完成后,我们需要把构建的镜像推送到镜像仓库中去,我们这里使用dockerhub作为镜像仓库,需要提前创建一个dockerhub 账号,记住用户名和密码。正常来说,当我们首次在机器上推送docker镜像到dockerhub的时候,我们需要先使用docker login命令进行登录,然后输入用户名和密码后,我们就可以正常的docker push了。那这样的话,关于Push Stage段,如下:
stage('Push') { echo "4.Push Docker Image Stage" sh "docker login -u cntsp -p xxxxx" sh "docker push cntsp/jenkins-demo:${build_tag}" }

如果我们只是在 Jenkins 的 Web UI 界面中来完成这个任务的话,我们这里的 Pipeline 是可以这样写的,但是我们倡导使用 Jenkinsfile 的形式放入源码中进行版本管理,这样的话我们直接把dockerhub仓库的用户名和密码暴露给别人这样显然是不好的,现在由于我们的源码仓库是使用的github的公共代码仓库,所有人都可以看到我们的源码,所以我们应该用一种方式来隐藏用户名和密码这种私密信息,幸运的是jenkins为我们提供了解决方法。
在首页点击 Credentials -> Stores scoped to Jenkins 下面的 Jenkins -> Global credentials (unrestricted) -> 左侧的 Add Credentials:添加一个 Username with password 类型的认证信息,如下:
dockerhub 个人仓库账号和密码
Jenkins的CI/CD在k8s中的实践
文章图片

输入个人的 dockerhub 镜像仓库的用户名和密码,ID部分我们输入为dockerhub,注意,这个值非常重要,在后面的Pipeline 的脚本中我们需要使用到这个ID值。有了上面的 dockerhub 的用户名和密码的认证信息,现在我们可以在Pipeline中使用这里的用户名和密码了
stage('Push') { echo "4.Push Docker Image Stage" withCredentials([usernamePassword(credentialsId: 'dockerhub', passwordVariable: 'dockerHubPassword', usernameVariable: 'dockerHubUser')]) { sh "docker login -u ${dockerHubUser} -p ${dockerHubPassword}" sh "docker push cntsp/jenkins-demo:${build_tag}" } }

注意我们这里在 stage 中使用了一个新的函数withCredentials,其中有一个credentialsId值就是我们刚刚创建的 ID 值,然后我们就可以在脚本中直接使用这里两个变量值来直接替换掉之前的登录 dockerhub 的用户名和密码,现在是不是就很安全了,我只是传递进去了两个变量而已,别人并不知道我的真正用户名和密码,只有我们自己的 Jenkins 平台上添加的才知道。
第五步:更改yaml文件
上面我们已经完成了镜像的打包、推送的工作,接下来我们是不是应该更新 Kubernetes 系统中应用的镜像版本了,当然为了方便维护,我们都是用 YAML 文件的形式来编写应用部署规则,比如我们这里的 YAML 文件:(k8s.yaml)
apiVersion: apps/v1 kind: Deployment metadata: name: jenkins-demo spec: selector: matchLabels: app: jenkins-demo template: metadata: labels: app: jenkins-demo spec: containers: - image: cntsp/jenkins-demo: imagePullPolicy: IfNotPresent name: jenkins-demo env: - name: branch value: --- apiVersion: v1 kind: Service metadata: name: jenkins-demo spec: type: NodePort ports: - protocol: TCP name: test port: 8080 targetPort: 8080 nodePort: 30003 selector: app: jenkins-demo

对于 Kubernetes 比较熟悉的同学,对上面这个 YAML 文件一定不会陌生,我们使用一个 Deployment 资源对象来管理 Pod,该 Pod 使用的就是我们上面推送的镜像,唯一不同的地方是 Docker 镜像的 tag 不是我们平常见的具体的 tag,而是一个 的标识,实际上如果我们将这个标识替换成上面的 Docker 镜像的 tag,是不是就是最终我们本次构建需要使用到的镜像?怎么替换呢?其实也很简单,我们使用一个sed命令就可以实现了:
stage('YAML') { echo "5. Change YAML File Stage" sh "sed -i 's//${build_tag}/' k8s.yaml" }

上面的 sed 命令就是将 k8s.yaml 文件中的 标识给替换成变量 build_tag 的值。
第六步:部署
Kubernetes 应用的yaml文件已经更新完成了,之前我们手动的环境下,是不是直接使用kubectl apply 命令就可以直接更新应用了啊?当然我们这里只是写入了Pipeline里面,思路都一样。
stage('Deploy') { echo "6. Deploy Stage" sh "kubectl apply -f k8s.yaml" }

到这里整个流程基本就算完成了。
人工确认
理论上来说我们上面的6个步骤其实已经完成了,但是一般在我们的实际项目实践过程中,可能还需要一些人工干预的步骤,这是为什么呢?比如我们提交了一次代码,测试也通过了,镜像也打包上传了,但是这个版本并不一定就是要立刻上线到生产环境的,对吧,我们可能需要将该版本先发布到测试环境、QA 环境、或者预览环境之类的,总之直接就发布到线上环境去还是挺少见的,所以我们需要增加人工确认的环节,一般都是在 CD 的环节才需要人工干预,比如我们这里的最后两步,我们就可以在前面加上确认,比如:
stage('YAML') { echo "5. Change YAML File Stage" def userInput = input( id: 'userInput', message: '?Choose a deploy environment', parameters: [ [ $class: 'ChoiceParameterDefinition', choices: "Dev\nQA\nProd", name: 'Env' ] ] ) echo "This is a deploy step to ${userInput.Env}" sh "sed -i 's//${build_tag}/' k8s.yaml" }

我们这里使用了 input 关键字,里面使用一个 Choice 的列表来让用户进行选择,然后在我们选择了部署环境后,我们当然也可以针对不同的环境再做一些操作,比如可以给不同环境的 YAML 文件部署到不同的 namespace 下面去,增加不同的标签等等操作:
stage('Deploy') { echo "6. Deploy Stage" if (userInput.Env == "Dev") { // deploy dev stuff // 需要修改k8s.yaml文件,这里需要指定部署的namespace和nodeselector以及暴露的端口 } else if (userInput.Env == "QA"){ // deploy qa stuff // 需要修改k8s.yaml文件,这里需要指定部署的namespace和nodeselector以及暴露的端口 } else { // deploy prod stuff // 需要修改k8s.yaml文件,这里需要指定部署的namespace和nodeselector以及暴露的端口 } sh "kubectl apply -f k8s.yaml" }

由于这一步也属于部署的范畴,所以我们可以将最后两步都合并成一步,我们最终的 Pipeline 脚本式流水线如下:
node('slave-jnlp') {stage('Clone') { echo "1.Clone Stage" git url: "https://github.com/shutang/goproject.git" script { build_tag = sh(returnStdout: true, script: 'git rev-parse --short HEAD').trim() } }stage('Test') { echo "2.Test Stage" sh "whoami" sh "pwd" }stage('Build') { echo "3.Build Docker Image Stage" sh "docker build -t cntsp/jenkins-demo:${build_tag} ." }stage('Push') { echo "4.Push Docker Image Stage" withCredentials([usernamePassword(credentialsId: 'dockerhub', passwordVariable: 'dockerHubPassword', usernameVariable: 'dockerHubUser')]) { sh "docker login -u ${dockerHubUser} -p ${dockerHubPassword}" sh "docker push cntsp/jenkins-demo:${build_tag}" } }stage('Deploy') {echo "5. Deploy Stage"environment { DEV_NAMESPACE = "dev" QA_NAMESPACE = "qa" PROD_NAMESPACE = "prod" }def userInput = input( id: 'userInput', message: '?Choose a deploy environment', parameters: [ [ $class: 'ChoiceParameterDefinition', choices: "dev\nqa\nprod", name: 'Env' ] ] )echo "This is a deploy step to ${userInput}" sh "sed -i 's//${build_tag}/' k8s.yaml"if (userInput == "dev") { // 需要指定node来部署开发环境的应用 // deploy dev stuff echo "${env.DEV_NAMESPACE}" sh "sed -i 's//dev/' k8s.yaml" } else if (userInput == "qa"){ // deploy qa stuff sh "sed -i 's//qa/' k8s.yaml" } else { // deploy prod stuff sh "sed -i 's//prod/' k8s.yaml" } sh "kubectl apply -f k8s.yaml" sh "kubectl get pods -n ${userInput}" } }

在第6部分我们讲解了使用 Jenkins Pipeline 来自动化部署一个 kubernetes 应用的方法,在实际的项目中,往往一个代码仓库都会有很多分支的,比如开发、测试、线上这些分支都是分开的,一般情况下开发或者测试的分支我们希望提交代码后就直接进行CI/CD操作,而线上的话最好增加一个人工干预的步骤,这就需要 Jenkins 对代码仓库有多分支的支持,当然这个特性也是被 Jenkins 支持的。
7 Jenkinsfile
同样的,我们可以使用第6部分的方法直接把要构建的脚本配置在 Jenkins Web Ui界面中就可以,但是我们也提到过最佳的方式是将脚本写入一个名为 Jenkinsfile 的文件中,跟随代码库进行统一的管理。
我们这里在之前的 git 库中新建一个 dev 分支,然后稍微修改一下代码。具体可以参考 github
然后新创建一个 Jenkinsfile 文件,将第6部分的构建脚本拷贝进来,但是我们需要对其做一些修改:
node('slave-jnlp') {stage('Clone') { echo "1.Clone Stage" // git url: "https://github.com/shutang/goproject.git" checkout scm script { build_tag = sh(returnStdout: true, script: 'git rev-parse --short HEAD').trim() if (env.BRANCH_NAME != 'master') { build_tag = "${env.BRANCH_NAME}-${build_tag}" } } }stage('Test') { echo "2.Test Stage" echo "${env.BRANCH_NAME}" // sh "printenv" }stage('Build') { echo "3.Build Docker Image Stage" sh "docker build -t cntsp/jenkins-demo:${build_tag} ." }stage('Push') { echo "4.Push Docker Image Stage" withCredentials([usernamePassword(credentialsId: 'dockerhub', passwordVariable: 'dockerHubPassword', usernameVariable: 'dockerHubUser')]) { sh "docker login -u ${dockerHubUser} -p ${dockerHubPassword}" sh "docker push cntsp/jenkins-demo:${build_tag}" } }stage('Deploy') {echo "5. Deploy Stage"env.namespace="dev" /** def userInput = input( id: 'userInput', message: '?Choose a deploy environment', parameters: [ [ $class: 'ChoiceParameterDefinition', choices: "dev\nqa\nprod", name: 'Env' ] ] )echo "This is a deploy step to ${userInput}" **/ if (env.BRANCH_NAME == 'master') { input "您确认要部署线上环境吗?" env.namespace="prod" } sh "sed -i 's//${build_tag}/' k8s.yaml" sh "sed -i 's//${env.namespace}/' k8s.yaml" echo "${env.BRANCH_NAME}" sh "sed -i 's//${env.BRANCH_NAME}/' k8s.yaml" sh "kubectl apply -f k8s.yaml" sh "kubectl get pods -n ${env.namespace}"/** if (userInput == "dev") { // 需要指定node来部署开发环境的应用 // deploy dev stuff echo "${env.DEV_NAMESPACE}"} else if (userInput == "qa"){ // deploy qa stuff sh "sed -i 's//qa/' k8s.yaml" } else { // deploy prod stuff sh "sed -i 's//prod/' k8s.yaml" }**/ } }

在第一步中我们增加了 checkout scm命令,用来检出代码仓库中当前分支的代码,为了避免各个环境的镜像 tag 产生冲突,我们为非master 分支的代码构建的镜像增加了一个分支的前缀,在第五步中如果是master分支的话我们才增加一个分支的前缀,在第五步中如果是 master 分支的话我们才增加一个确认部署的流程,其他分支都自动部署,并且还需要替换 k8s.yaml 文件中的 镜像的tagnamespace 环境变量的值。更改完成后,提交 dev 分支到 github
上面的脚本化流水线如何修改Pipeline式流水线呢?
pipeline { agent { label 'slave-jnlp' }environment { namespace = "dev" }stages { stage('Clone') { steps { echo "1.Clone Stage" git url: "https://github.com/shutang/goproject.git" script { build_tag = sh(returnStdout: true, script: 'git rev-parse --short HEAD').trim() } }}stage('Test') { steps { echo "2.Test Stage" sh "sed -i 's//${build_tag}/' k8s.yaml" sh "sed -i 's//${env.BRANCH_NAME}/' k8s.yaml" } }stage('Build') { steps { echo "3.Build Docker Image Stage" sh "docker build -t cntsp/jenkins-demo:${build_tag} ." }}stage('Push') { steps { echo "4.Push Docker Image Stage" withCredentials([usernamePassword(credentialsId: 'dockerhub', passwordVariable: 'dockerHubPassword', usernameVariable: 'dockerHubUser')]) { sh "docker login -u ${dockerHubUser} -p ${dockerHubPassword}" sh "docker push cntsp/jenkins-demo:${build_tag}" } }}stage('Deploy1') { when { branch 'master' } steps { echo "5. Deploy Stage" timeout(5) { input ( "您确认要部署线上环境吗?" ) }sh "sed -i 's//prod/' k8s.yaml" sh "kubectl apply -f k8s.yaml" sh "kubectl get pods -n dev"echo '执行完成' }}stage('Deploy2') { when { branch 'dev' } steps { echo "This is a deploy step to dev" sh "sed -i 's//dev/' k8s.yaml" sh "kubectl apply -f k8s.yaml" sh "kubectl get pods -n dev"echo '执行完成' }}}}

8 BlueOcean
我们这里使用 BlueOcean 这种方式来完成此处 CI/CD 的工作,BlueOceanJenkins 团队从用户体验角度出发,专为 Jenkins Pipeline 重新设计的一套 UI 界面,仍然兼容以前的 fresstyle类型的 job,BlueOcean 具有以下特性:
  • 连续交付(CD)Pipeline 的复杂可视化,允许快速直观的了解 Pipeline
  • 可以通过 Pipeline 编辑器直观的创建 Pipeline
  • 需要干预或者出现问题时快速定位,BlueOcean 显示了 Pipeline 需要注意的地方,方便异处理和排查定位
  • 用于分支和拉去请求的本地集成可以在 GitHub 或者 Bitbucket 中与其他人进行代码协作时最大限度提高开发人员的生产力。
BlueOcean 可以安装在现有的 Jenkins 环境中,也可以使用 Docker 镜像的方式直接运行,我们这里直接在现有的 Jenkins 环境中安装 BlueOcean 插件:登录 Jenkins Web UI -> 点击左侧的 Manage Jenkins -> Manage Plugins -> Available -> 搜索查找 BlueOcean -> 点击下载安装并重启。
Jenkins的CI/CD在k8s中的实践
文章图片

Jenkins的CI/CD在k8s中的实践
文章图片

Jenkins的CI/CD在k8s中的实践
文章图片

Jenkins的CI/CD在k8s中的实践
文章图片

Jenkins的CI/CD在k8s中的实践
文章图片

Jenkins的CI/CD在k8s中的实践
文章图片

Jenkins的CI/CD在k8s中的实践
文章图片

Jenkins的CI/CD在k8s中的实践
文章图片

Jenkins的CI/CD在k8s中的实践
文章图片

Jenkins的CI/CD在k8s中的实践
文章图片

Jenkins的CI/CD在k8s中的实践
文章图片

Jenkins的CI/CD在k8s中的实践
文章图片

Jenkins的CI/CD在k8s中的实践
文章图片

Jenkins的CI/CD在k8s中的实践
文章图片

Jenkins的CI/CD在k8s中的实践
文章图片

在上面的图中每个阶段我们都可以点击进去查看对应的构建结果,比如我们可以查看Push阶段下面的日志信息:
+ docker push cntsp/jenkins-demo:dev-cfd765eThe push refers to a repository [cntsp/jenkins-demo]fb25856d9567: Preparing ...... fb25856d9567: Pusheddev-cfd765e: digest: sha256:09459ae801365f8b0f1503c14f3d51657f3eb1e763e72d5a45234d6dcb108269 size: 1789

Jenkins的CI/CD在k8s中的实践
文章图片

我们可以看到本地构建的 Docker 镜像的 Tag 为 dev-cfd765e,是符合我们在 Jenkinsfile 中的定义的吧
Jenkins的CI/CD在k8s中的实践
文章图片

参考文档:https://zhuanlan.zhihu.com/p/...

    推荐阅读