DevOps(4)--Jenkins Pipeline

Jenkins Pipeline

要实现在 Jenkins 中的构建工作,可以有多种方式,我们这里采用比较常用的 Pipeline 这种方式。Pipeline,简单来说,就是一套运行在 Jenkins 上的工作流框架,将原来独立运行于单个或者多个节点的任务连接起来,实现单个任务难以完成的复杂流程编排和可视化的工作。

Jenkins Pipeline 有几个核心概念:

  • Node:节点,一个 Node 就是一个 Jenkins 节点,Master 或者 Agent,是执行 Step 的具体运行环境,比如我们之前动态运行的 Jenkins Slave 就是一个 Node 节点

  • Stage:阶段,一个 Pipeline 可以划分为若干个 Stage,每个 Stage 代表一组操作,比如:Build、Test、Deploy,Stage 是一个逻辑分组的概念,可以跨多个 Node

  • Step:步骤,Step 是最基本的操作单元,可以是打印一句话,也可以是构建一个 Docker 镜像,由各类 Jenkins 插件提供,比如命令:sh 'make',就相当于我们平时 shell 终端中执行 make 命令一样。

那么我们如何创建 Jenkins Pipline 呢?

  • Pipeline 脚本是由 Groovy 语言实现的,但是我们没必要单独去学习 Groovy,当然你会的话最好

  • Pipeline 支持两种语法:Declarative(声明式)和 Scripted Pipeline(脚本式)语法

  • Pipeline 也有两种创建方法:可以直接在 Jenkins 的 Web UI 界面中输入脚本;也可以通过创建一个 Jenkinsfile 脚本文件放入项目源码库中

我们这里来给大家快速创建一个简单的 Pipeline,直接在 Jenkins 的 Web UI 界面中输入脚本运行。

  • 新建 Job:在 Web UI 中点击 New Item -> 输入名称:pipeline-demo -> 选择下面的 Pipeline -> 点击 OK

  • 配置:在最下方的 Pipeline 区域输入如下 Script 脚本,然后点击保存。

    node {
    stage('Clone') {
        echo "1.Clone Stage"
    }
    stage('Test') {
        echo "2.Test Stage"
    }
    stage('Build') {
        echo "3.Build Stage"
    }
    stage('Deploy') {
        echo "4. Deploy Stage"
    }
    }
    
  • 构建:点击左侧区域的 Build Now,可以看到 Job 开始构建了

隔一会儿,构建完成,可以点击左侧区域的 ·Console Output·,我们就可以看到如下输出信息:

console output 我们可以看到上面我们 Pipeline 脚本中的4条输出语句都打印出来了,证明是符合我们的预期的。

如果大家对 Pipeline 语法不是特别熟悉的,可以前往输入脚本的下面的链接 Pipeline Syntax 中进行查看,这里有很多关于 Pipeline 语法的介绍,也可以自动帮我们生成一些脚本。

官方文档地址:https://www.jenkins.io/doc/book/pipeline/

1. 在 Slave 中构建任务

上面我们创建了一个简单的 Pipeline 任务,但是我们可以看到这个任务并没有在 Jenkins 的 Slave 中运行,那么如何让我们的任务跑在 Slave 中呢?一定要添加对应的label才行,我们重新编辑上面创建的 Pipeline 脚本,给 node 添加一个 label 属性,如下:

node('ms-jenkins') {
  stage('Clone') {
    echo "1.Clone Stage"
  }
  stage('Test') {
    echo "2.Test Stage"
  }
  stage('Build') {
    echo "3.Build Stage"
  }
  stage('Deploy') {
    echo "4. Deploy Stage"
  }
}

我们这里给 node 添加了一个 ms-jenkins 这样的一个label,然后我们保存,构建之前查看下 kubernetes 集群中的 Pod:

[root@master jekines]# kubectl get pods -n devops
NAME                       READY   STATUS    RESTARTS      AGE
jenkins-675cc9b744-69c8q   2/2     Running   1 (28m ago)   92m

然后重新触发立刻构建:

[root@master jekines]# kubectl get pods -n devops
NAME                       READY   STATUS    RESTARTS      AGE
jenkins-675cc9b744-69c8q   2/2     Running   1 (28m ago)   92m
jenkins-slave-jvpq1        1/1     Running   0          23s

我们发现多了一个名叫jenkins-slave-jvpq1的 Pod 正在运行,隔一会儿这个 Pod 就不再了。

这也证明我们的 Job 构建完成了,同样回到 Jenkins 的 Web UI 界面中查看 Console Output,可以看到如下的信息:

我们回到 Job 的主界面,也可以看到大家可能比较熟悉的 Stage View 界面:

2. 部署 Kubernetes 应用

上面我们已经知道了如何在 Jenkins Slave 中构建任务了,那么如何来部署一个原生的 Kubernetes 应用呢? 要部署 Kubernetes 应用,我们就得对我们部署应用的流程要非常熟悉才行,流程一般是这样的:

  • 编写代码

  • 测试

  • 编写 Dockerfile

  • 构建打包 Docker 镜像

  • 推送 Docker 镜像到仓库

  • 编写 Kubernetes YAML 文件

  • 更改 YAML 文件中 Docker 镜像 TAG

  • 利用 kubectl 工具部署应用

现在我们就需要把上面这些流程放入 Jenkins 中来自动帮我们完成(当然编码除外),从测试到更新 YAML 文件属于 CI 流程,后面部署属于 CD 的流程。如果按照我们上面的示例,我们现在要来编写一个 Pipeline 的脚本,应该怎么编写呢?

node('ms-jenkins') {
    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"
    }
}

现在我们创建一个流水线的作业,直接使用上面的脚本来构建,同样可以得到正确的结果:

这里我们来将一个简单 golang 程序,部署到 kubernetes 环境中。

package main

import (
 "github.com/gin-gonic/gin"
 "log"
 "net/http"
)

func main() {
 e := gin.Default()

 e.GET("/", func(c *gin.Context) {
  c.JSON(http.StatusOK, gin.H{
   "msg": "Hello k8s",
  })
 })

 e.GET("/health", func(c *gin.Context) {
  c.JSON(http.StatusOK, gin.H{
   "health": true,
  })
 })

 if err := e.Run(":8080"); err != nil {
  log.Fatalln(err)
 }
}

我们将代码推送到我们自己的 GitLab 仓库上去,地址:http://gitlab.test.com/devops/k8s-go-demo,这样让 Jenkins 和 Gitlab 连接进行 CI/CD。

如果按照之前的示例,我们应该这样来编写 Pipeline 脚本:

  • 第一步,clone 代码

  • 第二步,进行测试,如果测试通过了才继续下面的任务

  • 第三步,构建 Docker 镜像了

  • 第四步,镜像打包完成,就应该推送到镜像仓库中

  • 第五步,镜像推送完成,更改 YAML 文件中的镜像 TAG 为这次镜像的 TAG

  • 第六步,使用 kubectl 命令行工具进行部署了

2.1 gitlab

[root@master harbor]# kubectl get ingress -n gitlab
NAME     CLASS   HOSTS             ADDRESS   PORTS   AGE
gitlab   nginx   gitlab.test.com             80      4d21h

将上述代码上传到仓库

2.2 harbor

[root@master harbor]# kubectl get ingress -n harbor
NAME                      CLASS   HOSTS                   ADDRESS   PORTS     AGE
myharbor-ingress          nginx   testharbor.com                    80, 443   13s
myharbor-ingress-notary   nginx   notary.testharbor.com             80, 443   13s

将上述项目打包上传到harbor仓库中,项目镜像从harbor仓库中拉取。

创建用户:

用户名/密码: devops/DevOps123

给devops项目分配成员:

开发人员:对于指定项目拥有读写权限

以新用户重新进行登录:

制作镜像:

Dockerfile

#源镜像
FROM golang:1.18 as build
#作者
MAINTAINER devops "msdevops@test.com"
## 在docker的根目录下创建相应的使用目录
RUN mkdir -p /www/webapp
## 设置工作目录
WORKDIR /www/webapp
## 把当前(宿主机上)目录下的文件都复制到docker上刚创建的目录下
COPY . /www/webapp
#go构建可执行文件
RUN GOPROXY="https://goproxy.io"  go build -o main main.go
#暴露端口
EXPOSE 8080

RUN chmod +x main
ENTRYPOINT ["./main"]

当前的k8s集群无docker环境,新开一个虚拟机安装docker,制作镜像

[root@localhost k8s-go-demo]# git clone http://192.168.200.101:30513/devops/k8s-go-demo.git
Cloning into 'k8s-go-demo'...
remote: Enumerating objects: 11, done.
remote: Counting objects: 100% (11/11), done.
remote: Compressing objects: 100% (9/9), done.
remote: Total 11 (delta 2), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (11/11), done.

[root@localhost k8s-go-demo]# ll
total 28
-rw-r--r--. 1 root root  428 Oct 27 13:24 Dockerfile
-rw-r--r--. 1 root root 1053 Oct 27 13:23 go.mod
-rw-r--r--. 1 root root 6962 Oct 27 13:23 go.sum
-rw-r--r--. 1 root root  372 Oct 27 13:23 main.go
-rw-r--r--. 1 root root 6208 Oct 27 13:23 README.md
[root@localhost k8s-go-demo]# docker build -t k8s-go-demo:v1.0 .
Sending build context to Docker daemon  84.48kB
Step 1/9 : FROM golang:1.18 as build
 ---> 385d98c8996f
Step 2/9 : MAINTAINER devops "msdevops@test.com"
 ---> Using cache
 ---> dacf2b3b5bb2
Step 3/9 : RUN mkdir -p /www/webapp
 ---> Using cache
 ---> 34db9c0fc3ef
Step 4/9 : WORKDIR /www/webapp
 ---> Using cache
 ---> 9687306052d6
Step 5/9 : COPY . /www/webapp
 ---> 93a917bea6cf
Step 6/9 : RUN GOPROXY="https://goproxy.io"  go build -o main main.go
 ---> Running in 41e4995a61f4
go: downloading github.com/gin-gonic/gin v1.8.1
go: downloading github.com/gin-contrib/sse v0.1.0
go: downloading github.com/mattn/go-isatty v0.0.14
go: downloading golang.org/x/net v0.0.0-20210226172049-e18ecbb05110
go: downloading github.com/go-playground/validator/v10 v10.10.0
go: downloading github.com/pelletier/go-toml/v2 v2.0.1
go: downloading github.com/ugorji/go/codec v1.2.7
go: downloading google.golang.org/protobuf v1.28.0
go: downloading gopkg.in/yaml.v2 v2.4.0
go: downloading golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069
go: downloading github.com/go-playground/universal-translator v0.18.0
go: downloading github.com/leodido/go-urn v1.2.1
go: downloading golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97
go: downloading golang.org/x/text v0.3.6
go: downloading github.com/go-playground/locales v0.14.0
Removing intermediate container 41e4995a61f4
 ---> 35a6250584b8
Step 7/9 : EXPOSE 8080
 ---> Running in 9e1559735b91
Removing intermediate container 9e1559735b91
 ---> e6abfc5d76a6
Step 8/9 : RUN chmod +x main
 ---> Running in 58cdf237701c
Removing intermediate container 58cdf237701c
 ---> d810bc4970b6
Step 9/9 : ENTRYPOINT ["./main"]
 ---> Running in ed450c66e55c
Removing intermediate container ed450c66e55c
 ---> 70f9abf89f54
Successfully built 70f9abf89f54
Successfully tagged k8s-go-demo:v1.0

最好是配置一下hosts

[root@localhost ~]# vim /etc/hosts
192.168.200.101 testharbor.com
192.168.200.101 gitlab.test.com

打tag:

[root@localhost k8s-go-demo]# docker tag k8s-go-demo:v1.0 testharbor.com/devops/k8s-go-demo:v1.0

harbor地址添加到docker中:

[root@localhost k8s-go-demo]# vim /etc/docker/daemon.json 
{
  "registry-mirrors": ["https://hub-mirror.c.163.com/"],
  "insecure-registries": ["testharbor.com"]
}
[root@localhost k8s-go-demo]# systemctl daemon-reload     
[root@localhost k8s-go-demo]# systemctl restart docker 

登录harbor:

[root@localhost k8s-go-demo]# docker login -u devops -p DevOps123 testharbor.com
WARNING! Using --password via the CLI is insecure. Use --password-stdin.
WARNING! Your password will be stored unencrypted in /root/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store

Login Succeeded

推送:

[root@localhost k8s-go-demo]# docker push testharbor.com/devops/k8s-go-demo:v1.0
The push refers to repository [testharbor.com/devops/k8s-go-demo]
e413d9914a02: Pushed 
41837602cc97: Pushed 
da0358c4f9e8: Pushed 
9505e8901b34: Pushed 
fc0b1497525e: Pushed 
3f2cb4afd8f2: Pushed 
3a8d602e51a3: Pushed 
d1dec9917839: Pushed 
d38adf39e1dd: Pushed 
4ed121b04368: Pushed 
d9d07d703dd5: Pushed 
v1.0: digest: sha256:4a2487f19625b6e93f1d17556f3fd13a98796159564bab351c7e84bb9681412b size: 2635

2.3 CI/CD

jenkins账号密码 admin/admin

熟悉了gitlab和harbor的流程后,我们来通过Jenkins Pipeline构建整个CI/D流程。

新建流水线任务:

同时记录http://jenkins.test.com/job/devops-demo/build?token=devops123 这个地址,后面要用

这里需要从我们的gitlab仓库进行构建,但是gitlab.test.com不识别,需要在 CoreDNS 中添加自定义域名解析:

[root@master harbor]# kubectl edit cm coredns -n kube-system
# Please edit the object below. Lines beginning with a '#' will be ignored,
# and an empty file will abort the edit. If an error occurs while saving this file will be
# reopened with the relevant failures.
#
apiVersion: v1
data:
  Corefile: |
    .:53 {
        errors
        health {
           lameduck 5s
        }
        ready
        hosts {
          192.168.200.101 gitlab.test.com
          192.168.200.101 jenkins.test.com
          192.168.200.101 testharbor.com
          192.168.200.101 master
          192.168.200.101 node1
          192.168.200.101 node2
          fallthrough
        }
        kubernetes cluster.local in-addr.arpa ip6.arpa {
           pods insecure
           fallthrough in-addr.arpa ip6.arpa
           ttl 30
        }
        prometheus :9153
        forward . /etc/resolv.conf {
           max_concurrent 1000
        }
        cache 30
        loop
        reload
        loadbalance
    }
kind: ConfigMap
metadata:
  creationTimestamp: "2022-09-23T03:57:42Z"
  name: coredns
  namespace: kube-system
"/tmp/kubectl-edit-3730817610.yaml" 39L, 994C written
configmap/coredns edited

添加认证:

配置gitlab的webhook:

出现上面的问题,使用root账号登录,在admin的设置Network设置

同时在Jenkins中,配置安全策略:

在脚本命令行执行以下脚本,关闭跨域保护:

import jenkins.model.Jenkins

def jenkins = Jenkins.instance

jenkins.setCrumbIssuer(null)

或者:

hudson.security.csrf.GlobalCrumbIssuerConfiguration.DISABLE_CSRF_PROTECTION=true

测试:

3. Jenkinsfile

Jenkinsfile 有两种写法:

  • 脚本式(Scripted Pipeline):将流水线定义在 node{} 中,内容为可执行的 Groovy 脚本。

  • 声明式(Declarative Pipeline):将流水线定义在 pipeline{} 中,内容为声明式的语句。

前面我们示例中使用的是脚本式,现在普遍是推荐使用声明式,所以我们来学习声明式的语法。

3.1 示例

pipeline {
    agent {                     // 声明使用的节点
        label 'master'
    }
    environment {               // 定义环境变量
        GIT_REPO = 'https://github.com/xx'
    }
    options {
        timestamps()
        timeout(time: 10, unit: 'MINUTES')
    }
    stages {
        stage('拉取代码') {      // 开始一个阶段
            environment {       // 定义该阶段的环境变量
                BRANCH = 'master'
            }
            steps {             // 该阶段需要执行一些步骤
                sh """
                    git clone $GIT_REPO
                    git checkout $BRANCH"
                """
            }
        }
        stage('构建镜像') {
            steps {
                sh '''
                    IMAGE=${image_hub}/${image_project}/${image_name}:${image_tag}
                    docker build . -t $IMAGE
                    docker push $IMAGE
                    docker image rm $IMAGE
                '''
            }
        }
    }
    post {
        always {            // 任务结束时总是执行以下操作
            deleteDir()     // 递归地删除当前目录。这里是作用于当前 agent 的 ${env.WORKSPACE} 目录
        }
    }
}
  • 用 // 声明单行注释。

  • 每个 {} 的内容不能为空。

3.2 变量

script {
    ID = "1"            // 创建变量
    NAME = "man" + ID   // 拼接字符串
    echo NAME           // 直接打印 Groovy 的变量
    echo "$ID"          // 字符串定界符为双引号时,支持用 $ 插入变量或表达式的值
    echo '$ID'          // 字符串定界符为单引号时,不支持用 $ 取值
    sh "echo $ID"       // 执行 sh 语句,字符串定界符为双引号时,会先在 Groovy 解释器中获取 $ 变量的值,再将字符串作为 Shell 命令执行
    sh 'echo $ID'       // 执行 sh 语句,字符串定界符为单引号时,会直接作为 Shell 命令执行,因此 $ 会读取 Shell 变量
}
  • Groovy 语言支持在字符串中用 插入变量的值,用 ${} 插入表达式的值。

    • Jenkins 在执行 Jenkinsfile 之前,会先渲染以双引号作为定界符的字符串,如果其中存在 $ 符号,则尝试对 Groovy 解释器中的变量进行取值。

      • 如果通过 $ 读取的变量不存在,则会抛出 Groovy 的语法异常:groovy.lang.MissingPropertyException: No such property

      • 如果通过 ${env.A} 方式读取的变量不存在,则会返回 null ,不会报错。

  • 如果想让 Groovy 将字符串中的 $ 当作普通字符处理,则需要使用单引号作为定界符,或者使用转义字符 $

  • Jenkinsfile script{} 中创建的 Groovy 变量名区分大小写,但 parameters、environment 变量名不区分大小写,不过它们加入 shell 环境变量时会区分大小写。

3.2.1 环境变量

在 environment{} 中可以定义环境变量,它们会保存为 Groovy 变量和 Shell 环境变量。如下:

stage('测试') {
    environment {
        ID = 1
    }
    steps {
        sh "echo $ID"
        sh 'echo $ID'
    }
}
  • 如果给环境变量赋值为空,则会删除该变量。

  • 定义在 pipeline.environment{} 中的环境变量会作用于该 pipeline 全局,而定义在 stage.environment{} 中的只作用于该阶段。

  • 还可以在 Jenkins 系统配置页面,定义作用于所有 Job 的环境变量。或者通过 Folder Properties 插件,在文件夹中定义环境变量。

在 environment{} 中可以导入 Jenkins 的凭据作为环境变量:

environment {
    ACCOUNT1 = credentials('account1')
}

假设该凭据是 Username With Password 类型,值为 admin:123456 ,则 Jenkins 会在 Shell 中加入三个环境变量:

ACCOUNT1=admin:123456
ACCOUNT1_USR=admin
ACCOUNT1_PSW=123456
  • 读取其它类型的凭据时,建议打印出 Shell 的所有环境变量,从而发现 Jenkins 加入的环境变量的名字。

  • 为了保密,如果直接将上述变量打印到 stdout 上,Jenkins 会将它们的值显示成 ****

3.2.2 构建参数

  • 可以给 Job 声明构建参数,它们会保存为 Groovy 变量和 Shell 环境变量。

    • 用户在启动 Job 时,必须传入构建参数,除非它们有默认值。

  • Pipeline Job 可以在 pipeline.parameters{} 中以代码的形式定义构建参数,而其它类型的 Job 只能在 Jenkins Web 页面中定义构建参数。如下:

    pipeline {
        agent any
        parameters {
            booleanParam(name: 'A', defaultValue: true, description: '')   // 布尔参数
            choice(name: 'E', choices: ['A', 'B', 'C'], description: '')   // 单选参数,输入时会显示成下拉框
            string(name: 'B', defaultValue: 'Hello', description: '')      // 字符串参数,在 Web 页面上输入时不能换行
            text(name: 'C', defaultValue: 'Hello\nWorld', description: '') // 文本参数,输入时可以换行
            password(name: 'D', defaultValue: '123456', description: '')   // 密文参数,输入时会显示成密文
            file(name: 'f1', description: '')                              // 文件参数,输入时会显示文件上传按钮
        }
        stages {
            stage('Test') {
                steps {
                    echo "$A"   // 也可通过 $params.A 的格式读取构建参数,避免与环境变量重名
                }
            }
        }
    }
    
    • 如果定义了 parameters{} ,则会移除在 Jenkins Web 页面中定义的、在上游 Job 中定义的构建参数。

    • 每次修改了 parameters{} 之后,要执行一次 Job 才会在 Jenkins Web 页面上生效。

    • password 类型的参数虽然在输入时显示成密文,但打印到终端上时会显示成明文,不如 Jenkins 的 credentials 安全。

    • file 类型的参数上传的文件会存储到 ${workspace}/${job_name}/f1 路径处,而用 $f1 可获得上传的文件名。

      • pipeline job 的文件参数功能无效,不能上传文件。可采用以下两种替代方案:

        • 创建一个普通类型的 job ,供用户上传文件,保存到主机的 /tmp 目录下。然后让其它 job 从这里拷贝文件。

        • 在 Jenkins 之外搭建一个文件服务器,供用户上传文件。然后让其它 job 从这里下载文件。这样上传文件时麻烦些,但方便跨主机拷贝文件。

  • 在 shell 命令中调用构建参数时,可能被注入攻击。

    • 比如脚本执行 ls $file ,而用户输入构建参数file=a;rm -rf *就会注入攻击。

    • 如果让用户输入 booleanParam、choice 类型的构建参数,则在 Web 页面上只能选择有限的值。

      • 即使用户通过 HTTP API 输入构建参数,Jenkins 也会自动检查参数的值是否合法。如果不合法,则采用该参数的默认值。

    • 如果让用户输入 string、text 类型的构建参数,则应该过滤之后再调用。如下:

    parameters {
        string(name: 'file')
    }
    environment {
        file = file.replaceAll('[^\\w /:,.*_-]', '_').trim()    // 将构建参数赋值给一个环境变量,替换特殊字符
    }
    

3.2.3 内置变量

可以通过 params 字典读取 Pipeline 的构建参数。如下:

params.A
params.B

可以通过 env 字典读取 Pipeline 的环境变量。除了用户添加的环境变量,还有 Jenkins 内置的环境变量,比如:

env.JENKINS_HOME    # Jenkins 部署的主目录
env.NODE_NAME       # 节点名
env.WORKSPACE       # 在当前节点上的工作目录

env.JOB_NAME        # 任务名
env.JOB_URL         # 任务链接
env.BUILD_NUMBER    # 构建编号
env.BUILD_URL       # 构建链接

env.BRANCH_NAME     # 分支名
env.CHANGE_AUTHOR   # 版本的提交者
env.CHANGE_URL      # 版本的链接

这些变量的值都是 String 类型。

这些变量可以按以下格式读取:

script {
    echo env.NODE_NAME              // 在 Groovy 代码中,通过 env 字典读取
    echo "${env.NODE_NAME}"         // 在字符串中,通过 $ 取值

    sh "echo ${env.NODE_NAME}"
    sh 'echo $NODE_NAME'            // env 字典的内容会导入 Shell 的环境变量,因此可以在 shell 中直接读取
}

可以通过 currentBuild 字典获取当前的构建信息。如下:

currentBuild.buildCauses       # Build 的执行原因,返回一个字典,包括 userId、userName 等
currentBuild.displayName       # Build 的名称,格式为 #number
currentBuild.fullDisplayName   # Build 的全名,格式为 JOB_NAME #number
currentBuild.description       # Build 的描述,默认为 null
currentBuild.duration          # Build 的持续时长,单位 ms
currentBuild.result            # Build 的结果。如果构建尚未结束,则返回值为 null
currentBuild.currentResult     # Build 的当前状态。开始执行时为 SUCCESS ,受每个 stage 影响,不会变为 null

只有 displayName、description 变量支持修改。修改其它变量时会报错:RejectedAccessException: No such field

script {
    jenkins_user = "${currentBuild.buildCauses}".findAll('userName:([^,\\]]+)')[0].replaceAll('userName:', '')
    currentBuild.displayName = "#${env.BUILD_NUMBER}    $jenkins_user"
    currentBuild.description = "BRANCH=${env.BRANCH}"
}

3.3 agent{}

用于控制在哪个 Jenkins 代理上执行流水线。

可用范围:

  • 在 pipeline{} 中必须定义 agent{} ,作为所有 stage{} 的默认代理。

  • 在单个 stage{} 中可选定义 agent{} ,只作用于该阶段。

agent 常见的几种定义格式:

agent none          // 不设置全局的 agent ,此时要在每个 stage{} 中单独定义 agent{}
agent any           // 让 Jenkins 选择任一代理
agent {
    label 'master'  // 选择指定名字的代理
}
agent {
    node {          // 选择指定名字的代理,并指定工作目录
        label 'master'
        customWorkspace '/opt/jenkins_home/workspace/test1'
    }
}

3.4 docker

可以创建临时的 docker 容器作为 agent :

agent {
    docker {
        // label 'master'
        // customWorkspace "/opt/jenkins_home/workspace/test1"
        image 'centos:7'
        // args  '-v /tmp:/tmp'
    }
}

这会在指定节点上创建一个 docker 容器,执行 pipeline ,然后自动删除该容器。

该容器默认的启动命令如下:

docker run -t -d \
    -u 0:0 \                                    # 默认会设置容器内用户为 root
    -w /opt/jenkins/workspace/test_pipeline \   # 默认会自动挂载 workspace 目录,并将其设置为容器的工作目录
    -v /opt/jenkins/workspace/test_pipeline:/opt/jenkins/workspace/test_pipeline:rw,z \
    -e ******** -e ******** -e ******** \       # 默认会传入 Jenkins 的环境变量
    centos:7 cat

也可以在 script{} 中运行容器:

script{
    // 运行一个容器,在 Groovy 中保存为 container1 对象
    docker.image('mysql:5.7').withRun('-p 3306:3306') { container1 ->
        // 等待服务启动
        sh """
            while ! curl 127.0.0.1:3306
            do
                echo 'Wait until service is up...'
                sleep 1
            done
        """

        // 支持嵌套,从而同时运行多个容器
        docker.image('centos:7').inside("--link ${container1.id}:db") {
            sh """
                sh test.sh
            """
        }
    }
}
  • withRun() 方法执行完之后会自动删除容器。

  • inside() 方法启动容器时,会加上像 agent.docker 的默认配置。

3.5 stages{}

pipeline{} 流水线的主要内容写在 stages{} 中,其中可以定义一个或多个 stage{} ,表示执行的各个阶段。

  • Jenkins 会按先后顺序执行各个 stage{} ,并在 Web 页面上显示执行进度。

  • 每个 stage{} 的名称不能重复。

  • 每个 stage{} 中有且必须定义一个以下类型的语句块:

stages{}
steps{}
matrix{}
parallel{}

例子:

stages {
    stage('测试 1') {
        steps {...}
    }
    stage('测试 2') {
        stages('嵌套阶段') {
            stage('单元测试 1') {
                steps {...}
            }
            stage('单元测试 2') {
                steps {...}
            }
        }
    }
}

3.6 steps{}

在 steps{} 中可以使用多种 DSL 语句。

官方文档地址:https://www.jenkins.io/doc/pipeline/steps/

3.6.1 archiveArtifacts

用于将指定路径的文件归档。

  • 归档文件会被保存到 master 节点的 $JENKINS_HOME/jobs/$JOB_NAME/builds/$BUILD_ID/archive/ 目录下。

  • 可以在 Jenkins 的 job 页面查看、下载归档文件。

    archiveArtifacts artifacts: 'dist.zip'
    
  • 目标文件的路径可以使用通配符:

    target/*.jar
    **/*.log
    

3.6.2 bat

用于在 Windows 系统上执行 CMD 命令。

3.6.3 build

用于执行一个 Job 。

  • 在流水线上,被执行的 job 位于当前 job 的下游。

    build (
        job: 'job1',
        parameters: [
            string(name: 'AGENT', value: 'master'),  // 这里的 string 是指输入值的类型,可输入给大部分类型的 parameters
        ]
        // wait: true,        // 是否等待下游 job 执行完毕,才继续执行当前 job
        // propagate: true,   // 是否让下游 job 的构建结果影响当前 job 。需要启用 wait 才生效
        // quietPeriod: 5,    // 设置静默期,默认为 5 秒
    )
    
  • 如果给下游 job 传入未定义的 parameters ,则后者并不会接收。

3.6.4 checkout

用于拉取代码仓库。

例:拉取 Git 仓库

checkout([
    $class: 'GitSCM',
    branches: [[name: "$BRANCH"]],    // 切换到指定的分支,也可以填 tag 或 commit ID 。不过该插件最终会切换到具体的 commit ID
    extensions: [
      [$class: 'CleanBeforeCheckout'],  // 清理项目文件,默认启用。相当于 git clean -dfx 加 git reset --hard
      // [$class: 'RelativeTargetDirectory', relativeTargetDir: '.'], // 本地仓库的保存目录,默认为 .
      // [$class: 'CloneOption', shallow: true, depth: 1],            // 浅克隆,只下载最近 1 个版本的文件
      // [$class: 'SubmoduleOption', recursiveSubmodules: true, parentCredentials: true, shallow: true, depth: 1], // 递归克隆 submodule ,采用父 Git 项目的凭据,并采用浅克隆
    ],
    userRemoteConfigs: [[
      credentialsId: 'account_for_git', // 登录 git 服务器的凭据,为 Username With Password 类型
      url: "$repository_url"            // git 远程仓库的地址
    ]]
])

也可直接执行 git 命令:

withCredentials([gitUsernamePassword(credentialsId:'account_for_git')]){  // 这会自动绑定 git 账号密码到环境变量 GIT_USERNAME、GIT_PASSWORD
    sh """
        git clone $repository_url
    """
}

与直接使用 git 命令相比,使用 checkout 语句会将 git commit、diff 等信息收集到 Jenkins 中并显示。

例:拉取 SVN 仓库

checkout([
    $class: 'SubversionSCM',
    locations: [[
        remote: "$repository_url"
        credentialsId: 'account_for_svn',
        local: '.',                               // 本地仓库的保存目录,默认是创建一个与 SVN 最后一段路径同名的子目录
        // depthOption: 'infinity',               // 拉取的目录深度,默认是无限深
    ]],
    quietOperation: true,                         // 不显示拉取代码的过程
    workspaceUpdater: [$class: 'UpdateUpdater']   // 使本地目录更新到最新版本
])

3.6.5 dir

用于暂时切换工作目录,执行一些指令之后又回到 WORKSPACE 。

steps {
  dir("/tmp") {
      sh "pwd"
  }
  sh "pwd"
}

3.6.6 echo

用于显示字符串。

steps {
    echo 'Hello'
}
  • echo 语句只能显示 String 类型的值,而使用 println 可以显示任意类型的值。

  • 打印字符串时,要加上双引号或单引号 作为定界符(除非是纯数字组成的字符串),否则会被当作 Groovy 的变量名。

    • 例如:echo ID 会被当作 echo "$ID" 执行

    • 使用三引号 “””‘’’ 包住时,可以输入换行的字符串。

3.6.7 emailext

用于发送邮件。

需要先在 Jenkins 系统配置中配置 SMTP 服务器。

emailext (
    subject: "[${currentBuild.fullDisplayName}]的构建结果为${currentBuild.currentResult}",
    from: "123456@email.com",
    to: '123456@email.com',
    body: """
        任务名:${env.JOB_NAME}
        任务链接:${env.JOB_URL}
        构建编号:${env.BUILD_NUMBER}
        构建链接:${env.BUILD_URL}
        构建耗时:${currentBuild.duration} ms
    """
)

3.6.8 error

用于让 Job 立即终止,变为 Failure 状态,并显示一行提示文本。

error '任务执行出错'

3.6.9 lock

用于获取一个全局锁,可避免并发任务同时执行时冲突。

  • 可用范围:steps{}、options{}

  • 该功能由插件 Lockable Resources 提供。

lock('resource_1') {    // 锁定一个名为 resource-1 的资源。如果该资源不存在则自动创建(任务结束之后会删除)。如果该资源已经被锁定,则一直等待获取
    sleep 10            // 获取锁之后,执行一些语句
    echo 'done'
}                       // 执行完之后,会自动释放锁定的资源

lock 函数的可用参数如下:

lock(resource: 'resource_1',        // 要锁定的资源名
      // label: 'my_resource',      // 通过标签筛选锁定多个资源
      // quantity: 0,               // 至少要锁定的资源数量、默认为 0 ,表示锁定所有
      // variable: 'LOCK',          // 将资源名赋值给一个变量
      // inversePrecedence: false,  // 如果有多个任务在等待获取锁,是否插队到第一个
      // skipIfLocked: false        // 如果需要等待获取锁,是否跳过执行
      ) {
    ...
}

3.6.10 retry

用于当任务执行失败时(不包括语法错误),自动重试。

retry(3) {       // 最多尝试执行 3 次(包括第一次执行)
    sh 'ls /tmp/f1'
}

3.6.11 script

用于执行 Groovy 代码。

可以用赋值号 = 直接创建变量。如下:

steps {
    script {
        A = 1
    }
    echo "$A"
}

在 script{} 中创建的变量会在 Groovy 解释器中一直存在,因此在该 script{} 甚至该 stage{} 结束之后依然可以读取,但并不会被 Jenkins 加入到 shell 的环境变量中。

例:将 shell 命令执行后的 stdout 或返回码赋值给变量

script {
    STDOUT = sh(script: 'echo hello', returnStdout: true).trim()
    EXIT_CODE = sh(script: 'echo hello', returnStatus: true)
    echo "$STDOUT"
    echo "$EXIT_CODE"
}

例:从 shell 中获得数组并遍历它

script {
    FILE_LIST = sh(script: "ls /", returnStdout: true)
    for (f in FILE_LIST.tokenize("\n")){
        sh "echo $f"
    }
}

tokenize() 方法用于将字符串分割成多个字段的数组,并忽略内容为空的字段。

例:捕捉异常

script {
    try {
        sh 'exit 1'
    } catch (err) {     // 将异常捕捉之后,构建状态就会依然为 SUCCESS
        echo "${err}"
    } finally {
        echo "finished"
    }
}

也可以用 post{} 语句块实现异常处理。

例:修改 Job 的描述

script {
    currentBuild.rawBuild.project.description = 'Hello'
}

执行时可能报错:RejectedAccessException: Scripts not permitted to use method xxx
需要到 Jenkins 管理页面,点击 Script Approval ,批准该方法被脚本调用。

3.6.12 sh

用于执行 shell 命令。

steps {
    sh "echo Hello"
    sh 'echo World'
    sh """
        A=1
        echo $A     // 这会读取 Groovy 解释器中的变量 A
    """
    sh '''
        A=1
        echo $A     // 这会读取 shell 中的变量 A
    '''
}

每个 sh 语句会被 Jenkins 保存为一个临时的 x.sh 文件,用 /bin/bash -ex x.sh 的方式执行,且切换到该 Job 的工作目录。

  • 因此各个 sh 语句之间比较独立、解耦。

  • 因此会记录下执行的每条 shell 命令及其输出。例如执行以下 sh 语句:

    sh """
        echo Hello 你好         # 建议不要在 sh 语句中通过 echo 命令添加注释,因为该注释会打印一次,执行命令时又会记录一次,而且命令中包含中文时还会转码
        : 测试开始               # 可通过 : 命令加入注释
        comment=( 测试开始 )     # 可通过数组加入注释,此时数组内的中文不会转码
    """
    

    执行后的 Console Output 为:

    + echo Hello $'\344\275\240\345\245\275'
    Hello 你好
    + : 测试开始
    + comment=( 测试开始 )
    
  • 每次执行 sh 语句需要耗时 300ms ,因此建议将多个 sh 语句合并。

3.6.13 timeout

用于设置超时时间。

  • 超时之后则立即终止 Job ,变为 ABORTED 状态。

timeout(time: 3, unit: 'SECONDS') {     // 时间单位可以是 SECONDS、MINUTES、HOURS
    sh 'ping baidu.com'
}

3.6.14 waitUntil

用于暂停执行任务,直到满足特定的条件。

waitUntil {
    fileExists '/tmp/f1'
}

3.6.15 withCredentials

用于调用 Jenkins 的凭据。

withCredentials([
    usernamePassword(
        credentialsId: 'credential_1',
        usernameVariable: 'USERNAME',   // 将凭据的值存到变量中(如果在终端显示该变量的值,Jenkins 会自动隐藏)
        passwordVariable: 'PASSWORD'
    )]) {
    sh '''                              // 此时 sh 语句需要用单引号,避免票据变量被导入 shell 的环境变量
        docker login -u ${USERNAME} -p ${PASSWORD} ${image_hub}
    '''
}

3.6.16 withEnv

用于给 sh 语句添加环境变量。

withEnv(['A=Hello', 'B=World']) {
      sh 'echo $A $B'
}

执行 pipeline 时,默认会将环境变量、params 字典、env 字典等变量,通过 withEnv 添加到 sh 语句的环境变量中。

3.7 matrix{}

包含一个 axes{} 和 stages{} ,用于将一个 stages{} 在不同场景下并行执行一次,相当于执行多个实例。

  • 可用范围:stage{}

  • 每个并行任务称为一个 Branch 。

    • 只要有一个并行执行的任务失败了,最终结果就是 Failure 。

matrix {
    axes {
        axis {
            name 'PLATFORM'
            values 'linux', 'darwin', 'windows'
        }
        axis {
            name 'PYTHON_VERSION'
            values '3.5', '3.6', '3.7', '3.8'
        }
    }
    stages {
        stage('单元测试') {
            steps {
                echo PLATFORM
                echo PYTHON_VERSION
            }
        }
    }
}

axes{} 用于定义并发任务的矩阵,可以包含多个 axis{} 。

  • 每个 axis{} 用于定义一个矩阵变量。

  • 上例中定义了两个 axis{} ,矩阵变量 PLATFORMPYTHON_VERSION 分别有 3、4 种取值,因此会执行 3*4=12 个并发任务。

3.8 parallel{}

包含多个 stage{} ,用于并行执行多个任务。

  • 可用范围:stage{}

stage('单元测试') {
    parallel {
        stage('单元测试 1') {
            steps {
                echo '测试完成'
            }
        }
        stage('单元测试 2') {
            steps {
                echo '测试完成'
            }
        }
    }
}

3.9 options{}

用于给 Pipeline 添加一些可选配置。

  • 可用范围:pipeline{}、stage{}

options {
    buildDiscarder logRotator(daysToKeepStr: '30',          // 限制 build 记录的保留天数
                              numToKeepStr: '100',          // 限制 build 记录的保留数量
                              artifactDaysToKeepStr: '10',  // 限制 build 归档文件的保留天数。删除一次 build 记录时,也会删除其包含的 archive
                              artifactNumToKeepStr: '10')   // 限制 build 归档文件的保留数量

    disableConcurrentBuilds()           // 不允许同时执行该 job ,会排队执行
    lock('resource-1')                  // 获取全局锁(此时不支持附加语句块)
    parallelsAlwaysFailFast()           // 用 matrix{}、parallel{} 执行并发任务时,如果有某个任务失败,则立即放弃执行其它任务
    quietPeriod(5)                      // 设置静默期,默认为 5 秒
    retry(3)
    timeout(time: 60, unit: 'SECONDS')
    timestamps()                        // 输出内容到终端时,加上时间戳
}

3.10 triggers{}

用于在满足条件时自动触发 Pipeline 。

  • 可用范围:pipeline{}

triggers {
    cron('H */4 * * 1-5')       // 定期触发。其中 H 表示一个随机值,用于分散执行多个同时触发的任务
    pollSCM('H */4 * * 1-5')    // 定期检查 SCM 仓库,如果提交了新版本代码则构建一次
}

3.11 when{}

用于在满足条件时才执行某个 stage 。

  • 可用范围:stage{}

  • 不满足 when{} 的条件时,会跳过执行该 stage ,但并不会导致执行状态变为 Failure 。

when {
    environment name: 'A', value: '1'  // 当环境变量等于指定值时
}
when {
    expression {            // 当 Groovy 表达式为 true 时
        currentBuild.currentResult == 'SUCCESS'
    }
}
when {
    not {                   // 当子条件为 false 时
        environment name: 'A', value: '1'
    }
}
when {
    allOf {                 // 当子条件都为 true 时
        environment name: 'A', value: '1'
        environment name: 'B', value: '2'
    }
    anyOf {                 // 当任一子条件为 true 时
        environment name: 'A', value: '1'
        environment name: 'B', value: '2'
    }
    // when{} 子句中的多个条件默认为 allOf{} 的关系
}

environment 表达式只能处理环境变量,而 expression{} 能处理环境变量、普通变量。

3.12 input{}

用于暂停某个阶段的执行,等待用户输入某些参数

  • 可用范围:stage{}

input {
    message '等待输入...'
    ok '确定'
    submitter 'admin, ops'  // 限制有输入权限的用户
    parameters {            // 等待用户输入以下参数
        string(name: 'NODE', defaultValue: 'master', description: '部署到哪个节点?')
    }
}

3.13 post{}

用于当构建状态满足某些条件时,才执行操作。

  • 可用范围:pipeline{}、stage{}

  • pipeline 出现语法错误时,Jenkins 会直接报错,而不会执行 post 部分。

success       # 状态为成功
failure       # 失败
unstable      # 不稳定
aborted       # 放弃执行

unsuccessful  # 不成功,包括 failure、unstable、aborted
always        # 匹配任何状态
cleanup       # 匹配任何状态,且放在其它所有 post 条件之后执行,通常用于清理
changed       # 与上一次执行的状态不同
regression    # 状态为 failure、unstable 或 aborted ,且上一次执行的状态为 success
fixed         # 状态为 success ,且上一次执行的状态为 failure 或 unstable

例子:

pipeline {
    agent any
    stages {
        stage('Test') {
            steps {
                echo 'testing ...'
            }
        }
    }
    post {
        success {
            echo '执行成功'
        }
        failure {
            echo '执行失败'
        }
        unstable {
            echo '执行状态不稳定'
        }
        aborted {
            echo '放弃执行'
        }
    }
}

4. 编写 Jenkinsfile

重新编写一个slave pod template:

FROM jenkins/inbound-agent
ARG KUBECTL_VERSION=v1.24.0
USER root
RUN apt-get update && \
  apt-get install -y curl && \
  apt-get clean
RUN apt-get install sudo
RUN curl -OL https://storage.googleapis.com/kubernetes-release/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl && \
  chmod +x ./kubectl && \
  mv ./kubectl /usr/local/bin/kubectl && \
  ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && \
  echo $TZ > /etc/timezone
VOLUME [ "/home/jenkins/.m2" ]
USER root
ENTRYPOINT ["/usr/local/bin/jenkins-agent"]

上传到docker hub,备用,改一下pod template中container的版本。

第一步,指明要选择的代理

pipeline {
    agent none // 这里先不指定
}

第二步,开始定义阶段

stages {
        stage('Clone Code') {
            agent {
                label 'ms-jenkins'
            }
            steps{
                echo "克隆代码"
            }
        }
        stage('Image Build') {
            agent {
                label 'ms-jenkins'
            }
            steps{
                echo "dockerfile 构建镜像并打tag"
            }
            
        }
        stage('Push') {
            agent {
                label 'ms-jenkins'
            }
            steps{
                echo "上传镜像到harbor仓库"
            }
            
        }
    }

可以先测试一下

上传仓库

可以在Console Output 看到对应的输出日志

第三步,补全上述步骤,先将上传harbor仓库这几个步骤跑通

这里有一个问题,k8s1.24没有docker了,containerd又不能构建镜像,所以需要做一些额外的操作

  • 首先安装**nerdctl**

    每个节点都需要安装(资料中有提供包)

    [root@node1 devops]# wget https://github.com/containerd/nerdctl/releases/download/v0.20.0/nerdctl-0.20.0-linux-amd64.tar.gz
    [root@node1 devops]# tar -zxvf nerdctl-0.20.0-linux-amd64.tar.gz 
    nerdctl
    containerd-rootless-setuptool.sh
    containerd-rootless.sh
    [root@node1 devops]# ll
    total 36100
    -rwxr-xr-x 1 root root    21562 May 17 04:12 containerd-rootless-setuptool.sh
    -rwxr-xr-x 1 root root     7032 May 17 04:12 containerd-rootless.sh
    -rwxr-xr-x 1 root root 26677248 May 17 04:13 nerdctl
    -rw-r--r-- 1 root root 10253491 Oct 28 14:01 nerdctl-0.20.0-linux-amd64.tar.gz
    [root@node1 devops]# cp nerdctl /usr/bin
    [root@node1 devops]# chmod +x /usr/bin/nerdctl
    
  • 安装buildkt

    每个节点都需要安装(资料中有提供包)

    [root@node1 buildkit]# wget https://github.com/moby/buildkit/releases/download/v0.10.3/buildkit-v0.10.3.linux-amd64.tar.gz
    [root@node1 buildkit]# tar -zxvf buildkit-v0.10.3.linux-amd64.tar.gz 
    bin/
    bin/buildctl
    bin/buildkit-qemu-aarch64
    bin/buildkit-qemu-arm
    bin/buildkit-qemu-i386
    bin/buildkit-qemu-mips64
    bin/buildkit-qemu-mips64el
    bin/buildkit-qemu-ppc64le
    bin/buildkit-qemu-riscv64
    bin/buildkit-qemu-s390x
    bin/buildkit-runc
    bin/buildkitd
    [root@node1 buildkit]# ll
    total 46348
    drwxr-xr-x 2 root root      283 Oct 20  2015 bin
    -rw-r--r-- 1 root root 47457160 Oct 28 14:06 buildkit-v0.10.3.linux-amd64.tar.gz
    [root@node1 buildkit]# cp bin/* /usr/bin
    

    启动:

    [root@node2 devops]# vim /etc/systemd/system/buildkitd.service
    [Unit]
    Description=BuildKit
    Documentation=https://github.com/moby/buildkit
    [Service]
    ExecStart=/usr/bin/buildkitd --oci-worker=false --containerd-worker=true
    [Install]
    WantedBy=multi-user.target
    
    [root@node2 devops]# systemctl  start buildkitd
    [root@node2 devops]# systemctl  enable buildkitd
    Created symlink from /etc/systemd/system/multi-user.target.wants/buildkitd.service to /etc/systemd/system/buildkitd.service.
    [root@node2 devops]# systemctl  status buildkitd
    
  • 重新配置Jenkins slave pod template,将上述这些映射到容器内

pipeline {
    agent none // 这里先不指定
    environment{
        XDG_RUNTIME_DIR = "/run/user/0"
    }
    stages {
            stage('Clone Code') {
                   agent {
                       label 'ms-jenkins'
                   }
                   steps{
                       echo "克隆代码"
                       withCredentials([gitUsernamePassword(credentialsId:'gitlab-auth')]){  // 这会自动绑定 git 账号密码到环境变量 GIT_USERNAME、GIT_PASSWORD
                           sh """
                               git clone -b main http://gitlab.test.com/devops/k8s-go-demo.git
                           """
                       }
                   }
            }
            stage('Image Build') {
                agent {
                    label 'ms-jenkins'
                }
                steps{
                    echo "dockerfile 构建镜像并打tag"
                    sh 'ls -l'
                    sh 'nerdctl  build -f Dockerfile  -t k8s-go-demo:${BUILD_ID} . '
                    sh 'nerdctl  tag  k8s-go-demo:${BUILD_ID} testharbor.com/devops/k8s-go-demo:${BUILD_ID}'
                }

            }
            stage('Push') {
                agent {
                    label 'ms-jenkins'
                }
                steps{
                    echo "上传镜像到harbor仓库"
                    sh "nerdctl login --insecure-registry --username=devops testharbor.com -p DevOps123"
                    sh "nerdctl push --insecure-registry testharbor.com/devops/k8s-go-demo:${BUILD_ID}"
                }

            }
        }
}

构建成功

成功将镜像推送上去。

4.1 编写yaml

接下来要做的就是编写k8s的yaml资源清单,将上传到harbor的镜像部署在k8s集群中。

k8s-go-demo.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: k8s-go-demo-deployment
  labels:
    app: k8s-go-demo
spec:
  selector:
    matchLabels:
      app: k8s-go-demo
  replicas: 3
  minReadySeconds: 5
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 1
  template:
    metadata:
      labels:
        app: k8s-go-demo
    spec:
      containers:
        - image: testharbor.com/devops/k8s-go-demo:{VERSION}
          name: k8s-go-demo
          imagePullPolicy: IfNotPresent
          command: ["./main"]
          ports:
            - containerPort: 8080
              protocol: TCP
---
apiVersion: v1
kind: Service
metadata:
  name: k8s-go-demo-service
  labels:
    app: k8s-go-demo
spec:
  selector:
    app: k8s-go-demo
  ports:
    - name: go-port
      protocol: TCP
      port: 8080
      targetPort: 8080
      nodePort: 31080
  type: NodePort

补全步骤:

第四步:kubectl apply

pipeline {
    stages {
          
            stage('Deploy') {
                agent {
                    label 'ms-jenkins'
                }
                steps{
                    echo "部署新版本"
                    sh 'sed -i "s#{VERSION}#${BUILD_ID}#g" ./k8s-go-demo.yaml'
                    sh 'kubectl apply -f ./k8s-go-demo.yaml -n default'
                }

            }
        }
}

完整:

pipeline {
    agent none // 这里先不指定
    environment{
        XDG_RUNTIME_DIR = "/run/user/0"
    }
    stages {
            stage('Clone Code') {
                   agent {
                       label 'ms-jenkins'
                   }
                   steps{
                       echo "克隆代码"
                       withCredentials([gitUsernamePassword(credentialsId:'gitlab-auth')]){  // 这会自动绑定 git 账号密码到环境变量 GIT_USERNAME、GIT_PASSWORD
                           sh """
                               git clone -b main http://gitlab.test.com/devops/k8s-go-demo.git
                           """
                       }
                   }
            }
            stage('Image Build') {
                agent {
                    label 'ms-jenkins'
                }
                steps{
                    echo "dockerfile 构建镜像并打tag"
                    sh 'ls -l'
                    sh 'nerdctl  build -f Dockerfile  -t k8s-go-demo:${BUILD_ID} . '
                    sh 'nerdctl  tag  k8s-go-demo:${BUILD_ID} testharbor.com/devops/k8s-go-demo:${BUILD_ID}'
                }

            }
            stage('Push') {
                agent {
                    label 'ms-jenkins'
                }
                steps{
                    echo "上传镜像到harbor仓库"
                    sh "nerdctl login --insecure-registry --username=devops testharbor.com -p DevOps123"
                    sh "nerdctl push --insecure-registry testharbor.com/devops/k8s-go-demo:${BUILD_ID}"
                }

            }
            stage('Deploy') {
                agent {
                    label 'ms-jenkins'
                }
                steps{
                    echo "部署新版本"
                    sh 'sed -i "s#{VERSION}#${BUILD_ID}#g" ./k8s-go-demo.yaml'
                    sh 'kubectl apply -f ./k8s-go-demo.yaml -n default'
                }

            }
        }
}

同时由于我们是私有仓库,所以需要在containerd中加入认证,要不然镜像无法拉取。

#所有节点
[root@node1 k8s-go-demo]# vim /etc/containerd/config.toml
[plugins."io.containerd.grpc.v1.cri".registry]
      config_path = ""

      [plugins."io.containerd.grpc.v1.cri".registry.auths]

      [plugins."io.containerd.grpc.v1.cri".registry.configs]
        [plugins."io.containerd.grpc.v1.cri".registry.configs."testharbor.com".tls]
          insecure_skip_verify = true
        [plugins."io.containerd.grpc.v1.cri".registry.configs."testharbor.com".auth]
          username = "devops"
          password = "DevOps123"

      [plugins."io.containerd.grpc.v1.cri".registry.headers]

      [plugins."io.containerd.grpc.v1.cri".registry.mirrors]
        [plugins."io.containerd.grpc.v1.cri".registry.mirrors."testharbor.com"]
          endpoint = ["https://testharbor.com"]
    [plugins."io.containerd.grpc.v1.cri".x509_key_pair_streaming]
      tls_cert_file = ""
      tls_key_file = ""
[root@node1 k8s-go-demo]# systemctl daemon-reload     
[root@node1 k8s-go-demo]# systemctl  restart containerd 

上传gitlab仓库,Jenkins会自动构建,等待结果


DevOps(4)--Jenkins Pipeline
http://47.123.5.226:8090//archives/devops-4---jenkins-pipeline
作者
pony
发布于
2024年04月30日
许可协议