在 Tekton 中如何實(shí)現(xiàn)審批功能
1. CICD 平臺(tái)的基本功能
常見(jiàn)的 CICD 引擎并不適合直接提供給業(yè)務(wù)方使用。主要原因在于用戶(hù)學(xué)習(xí)成本高、缺乏必要的鑒權(quán)、維護(hù)升級(jí)難度大。
我們通常會(huì)基于流程引擎,針對(duì)業(yè)務(wù)進(jìn)行適配提高易用性,針對(duì)場(chǎng)景進(jìn)行封裝收斂復(fù)雜度,那么一個(gè) CICD 平臺(tái)需要具備哪些基本的功能呢?
- 流程編排?;径趾诵牡墓δ埽柚_(kāi)源的編排引擎即可。
- 流程原子。流程原子組裝得到流水線,越豐富的流程原子,越能夠滿(mǎn)足業(yè)務(wù)方的需求。
- 流程控制。主要包括條件執(zhí)行、暫停、繼續(xù)、審批等,允許控制流水線的行為。
- 自動(dòng)觸發(fā)。通過(guò) API、Webhook 等方式自動(dòng)觸發(fā)流水線,會(huì)給使用方帶來(lái)很大便利。
- 權(quán)限控制。作為一個(gè)面向用戶(hù)的平臺(tái),權(quán)限控制必不可少。
Tekton 作為云原生下的 CICD 引擎,用來(lái)構(gòu)建面向 Kubernetes 基礎(chǔ)設(shè)施的 CICD 平臺(tái),非常適用。本篇主要想和大家分享的是 Tekton 流程控制,特別是審批的功能。
2. Tekton 中的流程控制
2.1 runAfter
-?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

通過(guò) runAfter 關(guān)鍵字可以控制任務(wù)的執(zhí)行順序,上面的示例中 build-app 會(huì)在 test-app 執(zhí)行完成之后執(zhí)行。使用 runAfter 可以實(shí)現(xiàn)對(duì)流程的編排。
2.2 conditions

這里首先創(chuàng)建一個(gè) Condition 對(duì)象,檢查代碼倉(cāng)庫(kù)中是否存在指定文件。
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)'
在創(chuàng)建 Pipeline 時(shí),只需要在 Task 中引用這個(gè) Condition,提供必要的參數(shù)即可。下面這個(gè)例子中,僅當(dāng)代碼倉(cāng)庫(kù)中存在 README.md 文件時(shí),my-task 任務(wù)才會(huì)執(zhí)行。
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
當(dāng) PipelineRun Spec 中的狀態(tài)處于 PipelineRunCancelled 時(shí),Reconciler 會(huì)提前取消全部 Task 并更新?tīng)顟B(tài)。
參考代碼: https://github.com/tektoncd/pipeline/blob/c8dc797cf5a6f11f90cb742d014470a444fcdc60/pkg/reconciler/pipelinerun/pipelinerun.go#L147
- 查看正在運(yùn)行的 pipelinerun
kubectl?get?pipelineruns.tekton.dev
NAME?????????????????????????????????????SUCCEEDED???REASON???????????????STARTTIME???COMPLETIONTIME
cancel-pipelinerun-r-67qsr???????????????Unknown?????Running??????????????51m
- 修改 pipelineruns 的 status 為 PipelineRunCancelled
kubectl?patch?PipelineRun?cancel-pipelinerun-r-67qsr?--type=merge?-p?'{"spec":{"status":"PipelineRunCancelled"}}'
- 查看取消的 pipelinerun
kubectl?get?pipelineruns.tekton.dev
NAME?????????????????????????????????????SUCCEEDED???REASON?????????????????STARTTIME???COMPLETIONTIME
cancel-pipelinerun-r-67qsr???????????????False???????PipelineRunCancelled???52m?????????3s
2.4 PipelineRunPending
除了上面的 PipelineRunCancelled 狀態(tài),pipelinerun 還有一個(gè)狀態(tài),PipelineRunPending。PipelineRunPending 實(shí)現(xiàn)的效果是,創(chuàng)建 PipelineRun 但不立即運(yùn)行
- 創(chuàng)建一條 PipelineRunPending 狀態(tài)的流水線
---
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"
- 查看流水線狀態(tài)
kubectl?get?pipelineruns.tekton.dev
NAME?????????????????????????????????????SUCCEEDED???REASON?????????????????STARTTIME???COMPLETIONTIME
pending-pipelinerun??????????????????????Unknown?????PipelineRunPending
這條流水線沒(méi)有執(zhí)行時(shí)間,因?yàn)樗恢碧幱诘却隣顟B(tài)。
- 移除 PipelineRunPending 狀態(tài)
kubectl?patch?PipelineRun?pending-pipelinerun?--type=merge?-p?'{"spec":{"status":""}}'
這條流水線開(kāi)始執(zhí)行。
- 查看流水線狀態(tài)
kubectl?get?pipelineruns.tekton.dev
NAME?????????????????????????????????????SUCCEEDED???REASON?????????????????STARTTIME???COMPLETIONTIME
pending-pipelinerun??????????????????????Unknown?????Running????????????????4s
- 無(wú)法將正在運(yùn)行的流水線修改為 PipelineRunPending 狀態(tài)
在 Tekton v0.24.1 中無(wú)法修改狀態(tài)為 PipelineRunPending,如果運(yùn)行將可以實(shí)現(xiàn)暫停的效果。
kubectl?get?pipelineruns.tekton.dev
NAME?????????????????????????????????????SUCCEEDED???REASON???????????????STARTTIME???COMPLETIONTIME
cancel-pipelinerun???????????????????????Unknown?????Running??????????????9s
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. 如何實(shí)現(xiàn)審批功能
上面提到了 Tekton 中的幾個(gè)流程控制方法,但是社區(qū)并沒(méi)有提供、也不準(zhǔn)備提供審批的功能。因此,在對(duì) Tekton 進(jìn)行二次開(kāi)發(fā)時(shí),需要 CICD 平臺(tái)自行實(shí)現(xiàn)審批和權(quán)限的控制。下面是兩種實(shí)現(xiàn)方案,以供參考:
3.1 方案一,使用 Trigger

如上圖,可以將用戶(hù)的一條流水線拆解為兩條流水線,pipeline-1/2 和 pipeline-2/2。兩條流水線之間引入一個(gè) trigger。
- 當(dāng)流水線 pipeline-1/2 執(zhí)行完成時(shí),通知審批者。
- 審批者審批通過(guò)后,觸發(fā) pipeline-2/2 執(zhí)行。
- pipeline-2/2 執(zhí)行結(jié)束,完成整條流水線。
Tekton 社區(qū)提供了一個(gè) triggers 組件,用來(lái)自動(dòng)化觸發(fā)流水線。如下圖:

- 審批之后,推送一個(gè)觸發(fā)事件 Event
- EventController 收到這個(gè)事件之后,從 TriggerBinding 提取出事件內(nèi)的參數(shù) Parameters
- TriggerTemplate 利用傳遞過(guò)來(lái)的參數(shù) Parameters,創(chuàng)建流水線 pipeline-2/2 。
3.2 方案二,開(kāi)發(fā)一個(gè)審批 Task
開(kāi)發(fā) Task 是 Tekton 的主要擴(kuò)展方式,同時(shí)開(kāi)發(fā) Task 只需要掌握基本的 Shell 和 Yaml 知識(shí)即可。這里提供另外一個(gè)思路就是開(kāi)發(fā)一個(gè)審批 Task。

如上圖,在一條流水線中,插入一個(gè)用于審批控制的 Task-Approve。
- 在使用審批原子時(shí),需要同步創(chuàng)建一個(gè) ConfigMap,用于保存審批的狀態(tài) Status=init
- 當(dāng)流水線執(zhí)行完成 Task-beforeApprove 任務(wù)時(shí),啟動(dòng) Task-Approve 任務(wù),修改狀態(tài) Status=notifying。Task-Approve 任務(wù)一直處于等待狀態(tài)。
- 發(fā)送通知給 Approver,修改狀態(tài) Status=notified
- 審批者審批流水線,允許執(zhí)行,修改狀態(tài) Status=success
- Task-Approve 檢測(cè)到 Status=success,立即結(jié)束等待狀態(tài),完成當(dāng)前 Task
- 流水線繼續(xù)執(zhí)行審批后的任務(wù) Task-afterApprove,直至結(jié)束
下面是一個(gè)示例:
首先創(chuàng)建一個(gè) ConfigMap 用于保存審批狀態(tài)。
apiVersion:?v1
kind:?ConfigMap
metadata:
??name:?approve-cm
data:
??status:?init
編寫(xiě)一個(gè)審批的 Task,默認(rèn)等待 24 小時(shí)審批,否則超時(shí)。如果將狀態(tài)修改為 success 則審批通過(guò),如果將狀態(tài)修改為 refused 則表示拒絕。
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
然后,創(chuàng)建一個(gè)測(cè)試用例
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
- 創(chuàng)建之后查看流水線

日志中會(huì)一直輸出 waiting。
- 審批通過(guò)
kubectl?patch?ConfigMap?approve-cm?--type=merge?-p?'{"data":{"status":"success"}}'
- 查看流水線狀態(tài)

4. 總結(jié)
在進(jìn)行 Tekton 二次開(kāi)發(fā)時(shí),審批是很難繞開(kāi)的功能,但社區(qū)并沒(méi)有提供相關(guān)的特性。本文首先介紹了 Tekton 中流程控制方法,然后提供了兩種實(shí)現(xiàn)審批功能的方案。下面對(duì)方案進(jìn)行簡(jiǎn)單的對(duì)比和總結(jié):
4.1 使用 Trigger 審批
優(yōu)點(diǎn)
- 靈活,審批之后的執(zhí)行,完全由開(kāi)發(fā)者控制,自由度更大。同時(shí)也可以使用后臺(tái)任務(wù)替換 Trigger,使用 Tekton Client 創(chuàng)建流水線。
- 可靠,即使重啟也不會(huì)影響審批。
缺點(diǎn)
- 拆分之后可能不止兩條流水線。
- 需要跨流水線傳遞參數(shù)、產(chǎn)物,增加了維護(hù)的成本。
- 架構(gòu)復(fù)雜度增加,引入了新的組件、后臺(tái)處理邏輯
4.2 開(kāi)發(fā)一個(gè)審批 Task
優(yōu)點(diǎn)
- 使用簡(jiǎn)單。一條 Pipeline 只有一個(gè) DAG,容易理解。
- 更加符合 Tekton 的擴(kuò)展方式。
缺點(diǎn)
- 審批 Task 因?yàn)楣?jié)點(diǎn)故障失敗時(shí),無(wú)法恢復(fù)
- 占用集群資源,審批 Task 常駐集群等待。
- ConfigMap 狀態(tài)更新不及時(shí),會(huì)有一個(gè)延時(shí)(默認(rèn)在秒級(jí)),大約值為 kubelet 的同步周期加上 ConfigMap 在 kubelet 中緩存的 TTL 時(shí)間。
5. 參考
- https://kubernetes.io/zh/docs/tasks/configure-pod-container/configure-pod-configmap/
