在 Tekton 中如何实现审批功能

2023年 1月 4日 37.9k 0

1. CICD 平台的基本功能

常见的 CICD 引擎并不适合直接提供给业务方使用。主要原因在于用户学习成本高、缺乏必要的鉴权、维护升级难度大。我们通常会基于流程引擎,针对业务进行适配提高易用性,针对场景进行封装收敛复杂度,那么一个 CICD 平台需要具备哪些基本的功能呢?

  • 流程编排。基本而又核心的功能,借助开源的编排引擎即可。
  • 流程原子。流程原子组装得到流水线,越丰富的流程原子,越能够满足业务方的需求。
  • 流程控制。主要包括条件执行、暂停、继续、审批等,允许控制流水线的行为。
  • 自动触发。通过 API、Webhook 等方式自动触发流水线,会给使用方带来很大便利。
  • 权限控制。作为一个面向用户的平台,权限控制必不可少。

Tekton 作为云原生下的 CICD 引擎,用来构建面向 Kubernetes 基础设施的 CICD 平台,非常适用。本篇主要想和大家分享的是 Tekton 流程控制,特别是审批的功能。

2. Tekton 中的流程控制

2.1 runAfter

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
- name: test-app
  taskRef:
    name: make-test
  resources:
    inputs:
      - name: workspace
        resource: my-repo
- name: build-app
  taskRef:
    name: kaniko-build
  runAfter:
    - test-app
  resources:
    inputs:
      - name: workspace
        resource: my-repo

通过 runAfter 关键字可以控制任务的执行顺序,上面的示例中 build-app 会在 test-app 执行完成之后执行。使用 runAfter 可以实现对流程的编排。

2.2 conditions

这里首先创建一个 Condition 对象,检查代码仓库中是否存在指定文件。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
apiVersion: tekton.dev/v1alpha1
kind: Condition
metadata:
  name: file-exists
spec:
  params:
    - name: "path"
  resources:
    - name: workspace
      type: git
  check:
    image: alpine
    script: 'test -f $(resources.workspace.path)/$(params.path)'

在创建 Pipeline 时,只需要在 Task 中引用这个 Condition,提供必要的参数即可。下面这个例子中,仅当代码仓库中存在 README.md 文件时,my-task 任务才会执行。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
apiVersion: tekton.dev/v1beta1
kind: Pipeline
metadata:
  name: conditional-pipeline
spec:
  resources:
    - name: source-repo
      type: git
  params:
    - name: "path"
      default: "README.md"
  tasks:
    - name: if-condition-then-run
      conditions:
        - conditionRef: "file-exists"
          params:
            - name: "path"
              value: "$(params.path)"
          resources:
            - name: workspace
              resource: source-repo
      taskRef:
        name: my-task

2.3 PipelineRunCancelled

当 PipelineRun Spec 中的状态处于 PipelineRunCancelled 时,Reconciler 会提前取消全部 Task 并更新状态。参考代码: https://github.com/tektoncd/pipeline/blob/c8dc797cf5a6f11f90cb742d014470a444fcdc60/pkg/reconciler/pipelinerun/pipelinerun.go#L147

  • 查看正在运行的 pipelinerun
1
2
3
4
kubectl get pipelineruns.tekton.dev

NAME                                     SUCCEEDED   REASON               STARTTIME   COMPLETIONTIME
cancel-pipelinerun-r-67qsr               Unknown     Running              51m
  • 修改 pipelineruns 的 status 为 PipelineRunCancelled
1
kubectl patch PipelineRun cancel-pipelinerun-r-67qsr --type=merge -p '{"spec":{"status":"PipelineRunCancelled"}}'
  • 查看取消的 pipelinerun
1
2
3
4
kubectl get pipelineruns.tekton.dev

NAME                                     SUCCEEDED   REASON                 STARTTIME   COMPLETIONTIME
cancel-pipelinerun-r-67qsr               False       PipelineRunCancelled   52m         3s

2.4 PipelineRunPending

除了上面的 PipelineRunCancelled 状态,pipelinerun 还有一个状态,PipelineRunPending。PipelineRunPending 实现的效果是,创建 PipelineRun 但不立即运行

  • 创建一条 PipelineRunPending 状态的流水线
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
---
apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
  name: pending-pipelinerun
spec:
  params:
  - name: pl-param-x
    value: "100"
  - name: pl-param-y
    value: "500"
  pipelineRef:
    name: pending-pipeline
  status: "PipelineRunPending"
  • 查看流水线状态
1
2
3
4
kubectl get pipelineruns.tekton.dev

NAME                                     SUCCEEDED   REASON                 STARTTIME   COMPLETIONTIME
pending-pipelinerun                      Unknown     PipelineRunPending

这条流水线没有执行时间,因为它一直处于等待状态。

  • 移除 PipelineRunPending 状态
1
kubectl patch PipelineRun pending-pipelinerun --type=merge -p '{"spec":{"status":""}}'

这条流水线开始执行。

  • 查看流水线状态
1
2
3
4
kubectl get pipelineruns.tekton.dev

NAME                                     SUCCEEDED   REASON                 STARTTIME   COMPLETIONTIME
pending-pipelinerun                      Unknown     Running                4s
  • 无法将正在运行的流水线修改为 PipelineRunPending 状态

在 Tekton v0.24.1 中无法修改状态为 PipelineRunPending,如果运行将可以实现暂停的效果。

1
2
3
4
kubectl get pipelineruns.tekton.dev

NAME                                     SUCCEEDED   REASON               STARTTIME   COMPLETIONTIME
cancel-pipelinerun                       Unknown     Running              9s
1
2
3
kubectl patch PipelineRun cancel-pipelinerun --type=merge -p '{"spec":{"status":"PipelineRunPending"}}'

Error from server (BadRequest): admission webhook "validation.webhook.pipeline.tekton.dev" denied the request: validation failed: invalid value: PipelineRun cannot be Pending after it is started: spec.status

validation 限制了这次修改操作。

3. 如何实现审批功能

上面提到了 Tekton 中的几个流程控制方法,但是社区并没有提供、也不准备提供审批的功能。因此,在对 Tekton 进行二次开发时,需要 CICD 平台自行实现审批和权限的控制。下面是两种实现方案,以供参考:

3.1 方案一,使用 Trigger

如上图,可以将用户的一条流水线拆解为两条流水线,pipeline-1/2 和 pipeline-2/2。两条流水线之间引入一个 trigger。

  • 当流水线 pipeline-1/2 执行完成时,通知审批者。
  • 审批者审批通过后,触发 pipeline-2/2 执行。
  • pipeline-2/2 执行结束,完成整条流水线。
  • Tekton 社区提供了一个 triggers 组件,用来自动化触发流水线。如下图:

  • 审批之后,推送一个触发事件 Event
  • EventController 收到这个事件之后,从 TriggerBinding 提取出事件内的参数 Parameters
  • TriggerTemplate 利用传递过来的参数 Parameters,创建流水线 pipeline-2/2 。
  • 3.2 方案二,开发一个审批 Task

    开发 Task 是 Tekton 的主要扩展方式,同时开发 Task 只需要掌握基本的 Shell 和 Yaml 知识即可。这里提供另外一个思路就是开发一个审批 Task。如上图,在一条流水线中,插入一个用于审批控制的 Task-Approve。

  • 在使用审批原子时,需要同步创建一个 ConfigMap,用于保存审批的状态 Status=init
  • 当流水线执行完成 Task-beforeApprove 任务时,启动 Task-Approve 任务,修改状态 Status=notifying。Task-Approve 任务一直处于等待状态。
  • 发送通知给 Approver,修改状态 Status=notified
  • 审批者审批流水线,允许执行,修改状态 Status=success
  • Task-Approve 检测到 Status=success,立即结束等待状态,完成当前 Task
  • 流水线继续执行审批后的任务 Task-afterApprove,直至结束
  • 下面是一个示例:首先创建一个 ConfigMap 用于保存审批状态。

    1
    2
    3
    4
    5
    6
    
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: approve-cm
    data:
      status: init
    

    编写一个审批的 Task,默认等待 24 小时审批,否则超时。如果将状态修改为 success 则审批通过,如果将状态修改为 refused 则表示拒绝。

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    
    apiVersion: tekton.dev/v1beta1
    kind: Task
    metadata:
      name: approve-task
    spec:
      workspaces:
      - name: data
      params:
      - name: timeout
        description: The max seconds to approve
        type: string
        default: "86400"
      steps:
      - name: sleep-a-while
        image: bash:latest
        script: |
          #!/usr/bin/env bash
    
          end=$((SECONDS+$(params.timeout)))
          while [ $SECONDS -lt $end ]; do
            name=$(cat "$(workspaces.data.path)"/status)
            if [ "$name" = "success" ]
            then
              echo "approved!"
              exit 0
            elif [ "$name" = "refused" ]
            then
              echo "refused!"
              exit 1
            fi
            sleep 2
            echo "waiting"
          done
          echo "too long not to approve"
          exit 1      
    

    然后,创建一个测试用例

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    
    apiVersion: tekton.dev/v1beta1
    kind: Task
    metadata:
      name: something
      annotations:
        description: |
                A simple task that do something
    spec:
      steps:
      - name: do-something
        image: bash:latest
        script: |
          #!/usr/bin/env bash
          uname -a      
    ---
    apiVersion: tekton.dev/v1beta1
    kind: Pipeline
    metadata:
      name: approve-pipeline
    spec:
      workspaces:
      - name: workspace
      tasks:
      - name: wait-for-approve
        workspaces:
        - name: data
          workspace: workspace
        taskRef:
          name: approve-task
      - name: do-something
        taskRef:
          name: something
        runAfter:
          - wait-for-approve
    ---
    apiVersion: tekton.dev/v1beta1
    kind: PipelineRun
    metadata:
      name: approve-pipelinerun
    spec:
      workspaces:
      - name: workspace
        configmap:
          name: approve-cm
      pipelineRef:
        name: approve-pipeline
    
    • 创建之后查看流水线

    日志中会一直输出 waiting。

    • 审批通过
    1
    
    kubectl patch ConfigMap approve-cm --type=merge -p '{"data":{"status":"success"}}'
    
    • 查看流水线状态

    4. 总结

    在进行 Tekton 二次开发时,审批是很难绕开的功能,但社区并没有提供相关的特性。本文首先介绍了 Tekton 中流程控制方法,然后提供了两种实现审批功能的方案。下面对方案进行简单的对比和总结:

    4.1 使用 Trigger 审批

    优点

    • 灵活,审批之后的执行,完全由开发者控制,自由度更大。同时也可以使用后台任务替换 Trigger,使用 Tekton Client 创建流水线。
    • 可靠,即使重启也不会影响审批。

    缺点

    • 拆分之后可能不止两条流水线。
    • 需要跨流水线传递参数、产物,增加了维护的成本。
    • 架构复杂度增加,引入了新的组件、后台处理逻辑

    4.2 开发一个审批 Task

    优点

    • 使用简单。一条 Pipeline 只有一个 DAG,容易理解。
    • 更加符合 Tekton 的扩展方式。

    缺点

    • 审批 Task 因为节点故障失败时,无法恢复
    • 占用集群资源,审批 Task 常驻集群等待。
    • ConfigMap 状态更新不及时,会有一个延时(默认在分钟级),大约值为 kubelet 的同步周期加上 ConfigMap 在 kubelet 中缓存的 TTL 时间。可以参考文档 如何修改 Kubelet 的启动参数 修改。

    5. 参考

    • https://kubernetes.io/zh/docs/tasks/configure-pod-container/configure-pod-configmap/

    相关文章

    KubeSphere 部署向量数据库 Milvus 实战指南
    探索 Kubernetes 持久化存储之 Longhorn 初窥门径
    征服 Docker 镜像访问限制!KubeSphere v3.4.1 成功部署全攻略
    那些年在 Terraform 上吃到的糖和踩过的坑
    无需 Kubernetes 测试 Kubernetes 网络实现
    Kubernetes v1.31 中的移除和主要变更

    发布评论