新聞中心
一篇帶給你Tekton系列之理論篇
作者:喬克 2022-03-08 08:32:43
云計算
云原生 Tekton是開源的云原生CI/CD項目,是基于Kubernetes CRD來定義Pipeline,功能強大并且很容易擴展。

上一篇文章我們介紹了Tekton的安裝并且做了簡單的測試,但是我們并不知其所以然,而這篇文章主要帶大家來了解以及學(xué)習(xí)所以然。
Tekton是開源的云原生CI/CD項目,是基于Kubernetes CRD來定義Pipeline,功能強大并且很容易擴展。
在上篇文章中,我們安裝完Tekton之后,可以看到安裝的CRD如下:
# kubectl get crd | grep tekton
clustertasks.tekton.dev 2022-02-28T06:15:38Z
conditions.tekton.dev 2022-02-28T06:15:38Z
extensions.dashboard.tekton.dev 2022-02-28T06:18:40Z
pipelineresources.tekton.dev 2022-02-28T06:15:38Z
pipelineruns.tekton.dev 2022-02-28T06:15:38Z
pipelines.tekton.dev 2022-02-28T06:15:38Z
runs.tekton.dev 2022-02-28T06:15:38Z
taskruns.tekton.dev 2022-02-28T06:15:38Z
tasks.tekton.dev 2022-02-28T06:15:38Z
其中Task、TaskRun、Pipeline、PipelineRun、PipelineResource、Condition作為其核心CRD,這里主要介紹它們。
- Task:定義構(gòu)建任務(wù),它由一系列有序steps構(gòu)成。每個step可以定義輸入和輸出,且可以將上一個step的輸出作為下一個step的輸入。每個step都會由一個container來執(zhí)行。
- TaskRun:Task用于定義具體要做的事情,并不會真正的運行,而TaskRun就是真正的執(zhí)行者,并且會提供執(zhí)行所需需要的參數(shù),一個TaskRun就是一個Pod。
- Pipeline:顧名思義就是流水線,它由一系列Tasks組成。就像Task中的step一樣,上一個Task的輸出可以作為下一個Task的輸入。
- PipelineRun:Pipeline的實際執(zhí)行,創(chuàng)建后會創(chuàng)建Pod來執(zhí)行Task,一個PipelineRun中有多個Task。
- PipelineResource:主要用于定義Pipeline的資源,常見的如Git地址、Docker鏡像等。
- Condition:它主要是在Pipeline中用于判斷的,Task的執(zhí)行與否通過Condition的判斷結(jié)果來決定。
Tips:PipelineResource和Condition都會被廢棄。但是在低版本中還是會繼續(xù)使用,所以這里會簡單介紹一下。
如上圖所示,一個Pipeline是由許多Task組成,每個Task又由許多step組成。在實際工作中,我們可以靈活定義各種Task,然后根據(jù)需要任意組合Task形成各類Pipeline來完成不同的需求。
實現(xiàn)原理
上面大致介紹了Tekton的主要CRD以及它們所具備的能力,那么,Tekton是如何把這些CRD串聯(lián)起來的呢?
我們在安裝完Tekton后,可以看到如下兩個Pod。
# kubectl get po -n tekton-pipelines
NAME READY STATUS RESTARTS AGE
tekton-pipelines-controller-75c456df85-qxvq2 1/1 Running 0 2d22h
tekton-pipelines-webhook-5bc8d6b7c4-w6pdn 1/1 Running 0 2d22h
一個是tekton-pipelines-controller,一個是tekton-pipelines-webhook。其實從命名方式就可以看出,一個是tekton的控制器,用于監(jiān)聽CRD對象,一個是tekton的網(wǎng)絡(luò)鉤子,用于做CRD校驗,其中tekton-pipelines-controller就是Tekton的核心實現(xiàn)Pod。
tekton-pipelines-controller在啟動的時候會初始化兩個Controller:PipelineRunController以及TaskRunController。我們可以通過main.go(cmd/controller/main.go)看到,如下:
......
go func() {
// start the web server on port and accept requests
log.Printf("Readiness and health check server listening on port %s", port)
log.Fatal(http.ListenAndServe(":"+port, mux))
}()
ctx = filteredinformerfactory.WithSelectors(ctx, v1beta1.ManagedByLabelKey)
sharedmain.MainWithConfig(ctx, ControllerLogKey, cfg,
taskrun.NewController(opts, clock.RealClock{}),
pipelinerun.NewController(opts, clock.RealClock{}),
)
}
如上所示會通過taskrun.NewController和pipelinerun.NewController來進行初始化,然后通過sharedmain.MainWithConfig調(diào)用controller.StartAll來啟動所有Controller。
PipelineRunController通過監(jiān)聽PipelineRun對象的變化,然后從PipelineSpec中獲取Task列表并構(gòu)建成一張有向無環(huán)圖(DAG),然后通過遍歷DAG找到可被調(diào)度的Task節(jié)點創(chuàng)建對應(yīng)的TaskRun對象。具體可以通過(pkg/reconciler/pipelinerun/pipelinerun.go)中的reconcile方法進行查看。
TaskRunController監(jiān)聽到TaskRun對象的變化,就會將TaskRun中的Task轉(zhuǎn)化為Pod,由Kubernetes調(diào)度執(zhí)行。可以通過(pkg/reconciler/taskrun/taskrun.go)中的reconcile方法進行查看。
利用 Kubernetes 的 OwnerReference 機制, PipelineRun Own TaskRun、TaskRun Own Pod、Pod 狀態(tài)變更時,觸發(fā) TaskRun 的 reconcile 邏輯, TaskRun 狀態(tài)變更時觸發(fā) PipelineRun 的 reconcile 邏輯。
當(dāng)TaskRun的Pod變成running過后,就會通知第一個step容器來執(zhí)行(通過一個名叫entrypoint的二進制文件來完成)。
當(dāng)然這個entrypoint二進制文件也有運行條件的,當(dāng)且僅當(dāng)pipeline的狀態(tài)的annotation通過Kubernetes Download Api以文件的方式注入到step container后才會啟動提供的命令。這句話是不是有點繞?按照官方的說法是:Tekton Pipeline是通過Kubernetes Annotation來跟蹤Pipeline的狀態(tài),而且這些annotations會通過Kubernetes Download Api以文件的方式注入到Step Container中,Step Container中的entrypoint會監(jiān)聽著這些文件,當(dāng)特定的annotation以文件的形式注入進來過后,entrypoint才會去執(zhí)行命令。比方說,一個Task中有兩個step,第二個step中的entrypoint會等待,直到annotation以文件的形式告訴它第一個step已經(jīng)完成。
我們來梳理一下整體的流程,如下:
- 用戶通過client創(chuàng)建PipelineRun資源。
- PipelineRunController監(jiān)聽到PipelineRun資源,就把里面的Task組成DAG(有向無環(huán)圖),遍歷DAG得到Task,并創(chuàng)建TaskRun。
- TaskRunController監(jiān)聽到TaskRun資源,就會通過Kubernetes將Task轉(zhuǎn)化為Pod啟動(Task受Condition條件控制)。
- Pod啟動后會運行Task中的每一個Step完成具體的指令。
- 運行完成后Pod會變成Completed狀態(tài),同時也會更新PipelineRun的狀態(tài)。
到此一個Pipeline就運行完成了。
PipelineResources
這里將PipelintResource提到最前面來說明,主要是后面的操作有需要它的地方。
PipelineResource用于定義資源的信息,雖然會被棄用,但是在舊版本中依然會使用。
PipelineResource的定義很簡單,如下:
apiVersion: tekton.dev/v1alpha1
kind: PipelineResource
metadata:
name: hello-word-resource
spec:
type: git
params:
- name: url
value: https://gitee.com/coolops/springboot-helloworld.git
在TaskRun中就可以引用hello-word-resource資源得到具體的git地址。
TasksTask就是一個任務(wù)模板,Task的定義中可以包含變量,在真正執(zhí)行的時候需要給變量賦值。
Task
通過input.params定義入?yún)?,每一個入?yún)⑦€可以指定默認值,在每一個step中可以$(params.A)引用變量。steps字段表示當(dāng)前Task有哪些步驟組成,每一個step都會通過定義一個container來執(zhí)行具體的操作。
Task主要包括以下元素:
- Parameters:用于定義params參數(shù)。
- Resources:定義輸入、輸出資源,老版本由PipelineResources定義,不過在新版本中PipelineResources將被棄用。
- Steps:定義具體的操作步驟。
- Workspaces:定義工作區(qū),Task可以共享工作區(qū)。
- Results:定義結(jié)果輸出,可以用于展示或者給另外的Task使用。
Task的定義如下:
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: maven-build
spec:
resources:
inputs:
- name: repo
type: git
steps:
- name: build
image: maven:3.3-jdk-8
command:
- mvn clean package
workingDir: /workspace/repo
再定義一個構(gòu)建Dokcer鏡像并推送到Hub的Task,如下:
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: build-and-push-image
spec:
params:
- name: pathToDockerfile
type: string
default: /workspace/repo/Dockerfile
description: define Dockerfile path
- name: pathToContext
type: string
default: /workspace/repo
description: Docker deamon build context
- name: imageRepo
type: string
default: registry.cn-hangzhou.aliyuncs.com
description: docker image repo
resources:
inputs:
- name: repo
type: git
outputs:
- name: builtImage
type: image
steps:
- name: build-image
image: docker:stable
scripts: |
#!/usr/bin/env sh
docker login $(params.imageRepo)
docker build -t $(resources.outputs.builtImage.url) -f $(params.pathToDockerfile) $(params.pathToContext)
docker push $(resources.outputs.builtImage.url)
volumeMounts:
- name: dockersock
mountPath: /var/run/docker.sock
volumes:
- name: dockersock
hostPath:
path: /var/run/docker.sock
如上,我們可以通過直接編寫shell腳本的方式來實現(xiàn)需求,而且使用docker構(gòu)建鏡像需要sock文件,可以像pod掛載那樣掛載需要的東西。
step還有其他的配置,比如為某個step設(shè)置超時時間,如下:
steps:
- name: sleep-then-timeout
image: ubuntu
script: |
#!/usr/bin/env bash
echo "I am supposed to sleep for 60 seconds!"
sleep 60
timeout: 5s
更多的操作可以通過(https://tekton.dev/docs/pipelines/tasks/)進行學(xué)習(xí)研究。
TaskRuns
Task在定義好之后,并不會被執(zhí)行,就像我們定義了一個函數(shù),如果沒被調(diào)用的話,這個函數(shù)就不會被執(zhí)行一樣。而TaskRun就可以就好似調(diào)用方,用它來執(zhí)行Task里的具體內(nèi)容。
TaskRun會設(shè)置Task需要的參數(shù),并通過taskRef字段來引用Task,如下:
apiVersion: tekton.dev/v1beta1
kind: TaskRun
metadata:
name: build-and-push-image
spec:
params:
- name: imageRepo
value: registry.cn-zhangjiakou.aliyuncs.com
taskRef:
name: build-and-push-image # 關(guān)聯(lián)定義好的task
resources:
inputs:
- name: repo # 指定輸入的倉庫資源
resourceRef:
name: hello-word-resource
outputs: # 指定輸出的鏡像資源
- name: builtImage
resourceRef:
name: hello-word-image
通過如上的定義,就將build-and-push-image的Task進行關(guān)聯(lián),并且通過resources定義Task需要的sources參數(shù),然后通過parms來定義參數(shù),該參數(shù)會替代掉Task中的默認參數(shù)。
在實際中,基本不會去定義TaskRun,除非自己去測試某個Task是否正常。
Pipelines
一個TaskRun只能執(zhí)行一個Task,當(dāng)我們需要同時編排許多Task的時候,就需要使用Pipeline了,就像使用Jenkinsfile來編排不同的任務(wù)一樣。
Pipeline是一個編排Task的模板,通過spec.params來聲明執(zhí)行時需要的入?yún)?,通過spec.tasks來編排具體的task,除此之外還可以通過runAfter來控制Task的先后順序。
先定義一個簡單的Pipeline,如下:
apiVersion: tekton.dev/v1beta1
kind: Pipeline
metadata:
name: build-and-push-image
spec:
resources:
- name: repo
type: git
- name: builtImage
type: image
tasks:
# 構(gòu)建并推送 Docker 鏡像
- name: build-and-push-image
taskRef:
name: build-and-push-image
resources:
inputs:
- name: repo # Task 輸入名稱
resource: repo # Pipeline 資源名稱
outputs:
- name: builtImage
resource: builtImage
上面定義的Pipeline關(guān)聯(lián)了build-and-push-image Task,該Task所需要的輸入輸出參數(shù),通過Pipeline的spec.resources定義,這里的spec.resources依然依賴PipelineResources中定義的具體資源。
上面提到過,如果要在Pipeline中控制Task順序,則要使用runAfter參數(shù),如下:
- 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
如上build-app的Task依賴test-app的Task。
除此之外,還可以將上個Task的輸出作為下一個Task的輸入,如下。
- name: build-app
taskRef:
name: build-push
resources:
outputs:
- name: image
resource: my-image
- name: deploy-app
taskRef:
name: deploy-kubectl
resources:
inputs:
- name: image
resource: my-image
from:
- build-app
如上通過from關(guān)鍵字來引入其他Task的輸出。
如果要在Pipeline中使用條件判斷,也可以像以下方式使用when關(guān)鍵字。
tasks:
- name: deploy-to-dev
when:
- input: "$(params.branch)"
operator: in
values: ["dev"]
taskRef:
name: deploy-to-dev
---
tasks:
- name: deploy-to-test
when:
- input: "$(params.branch)"
operator: in
values: ["test"]
taskRef:
name: deploy-to-test
注意:when和condition不能同時在一個Task中使用,不然會被認定為無效。
還有一個關(guān)鍵字和when效果一樣,就是condition。
condition的作用就是用一些條件來保護Task,只有在滿足條件的情況下才會運行Task。在Task運行之前,會對所有的條件進行判斷,只有全部條件成功,才會運行Task,否則不會允許。
如下定義一個簡單的條件語句。
tasks:
- name: deploy-if-branch-is-master
conditions:
- conditionRef: is-master-branch
params:
- name: branch-name
value: my-value
taskRef:
name: deploy
當(dāng)然條件約束僅針對當(dāng)前的Task,如果其他Task不受當(dāng)前Task影響,則不受約束。
更多的使用方式見(https://tekton.dev/docs/pipelines/pipelines/)。
PipelineRuns
Pipeline和Task一樣,單純的定義完并不會運行,Pipeline需要借助PipelineRun來完成真正的執(zhí)行。
PipelineRun會自動為Pipeline中定義的Task創(chuàng)建對應(yīng)的TaskRun。
下面定義一個簡單的PipelineRun。
apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
name: build-and-push-image
spec:
pipelineRef:
name: build-and-push-image
resources:
- name: repo
resourceRef:
name: demo-git
- name: builtImage
resourceRef:
name: harbor-image
其中spec.pipelineRef用來關(guān)聯(lián)定義的Pipeline,spec.resources用來給Pipeline傳遞參數(shù)。
上面的repo和builtImage參數(shù)依然需要通過PipelineResources定義。不過在新版本,也可以通過resourceSpec來進行定義,如下。
apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
name: build-and-push-image
spec:
pipelineRef:
name: build-and-push-image
resources:
- name: repo
resouorceSpec:
type: git
params:
- name: url
value: https://gitee.com/coolops/springboot-helloworld.git
- name: builtImage
resouorceSpec:
type: image
params:
- name: url
value: registry.cn-hangzhou.aliyuncs.com/coolops/helloworld:latest
Conditions
condition用于在Pipeline中進行條件判斷,不過在新版本中會被廢棄,使用上面介紹的when替代,這里不再做多的介紹了。
鑒權(quán)管理
上面介紹了主要的CRD以及它們的使用方式,但是還有一種是需要我們關(guān)注的,比如代碼倉庫的密碼怎么管理?鏡像倉庫的密碼怎么管理?因為這些都是在實際工作中需要使用的。
Tekton通過在PipelineRun中指定ServiceAccount來實現(xiàn)。不過Tekton要求定義的每個Secret都需要指定對應(yīng)的annotation。目前支持的annotation有以下兩種:
- Git:tekton.dev/git-**0:** https**:**//github.com。
- Docker:tekton.dev/docker-**0:** https**:**//gcr.io。
目前這兩種分別支持以下類型。
Tekton到底是如何使用到這些secret的呢?
原來,為了使用這些Secret,Tekton在實例化Pod的時候就會執(zhí)行憑證初始化, Tekton會將具體的Secret進行關(guān)聯(lián)并聚合到/tekton/creds目錄中,之后才會執(zhí)行具體的Task步驟。
下面我們具體操作一下,以鏡像倉庫為例。
(1)創(chuàng)建secret:
apiVersion: v1
kind: Secret
metadata:
name: docker-registry-secret
annotations:
tekton.dev/docker-0: https://gcr.io # Described below
type: kubernetes.io/basic-auth
stringData:
username:
password:
其中tekton.dev/docker-0: [https://gcr.io](https://gcr.io)用來指定對應(yīng)的倉庫地址。
(2)創(chuàng)建seviceaccount:
apiVersion: v1
kind: ServiceAccount
metadata:
name: docker-registry-sa
secrets:
- name: docker-registry-secret
(3)在PipelineRun中引用:
apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
name: demo-pipeline
namespace: default
spec:
serviceAccountName: docker-registry-sa
pipelineRef:
name: demo-pipeline
如果需要同時使用多個serviceaccount怎么辦呢?比如我們在一條完成的Pipeline中,在拉取代碼的時候會用到Git的賬戶,在推送鏡像的時候會用到鏡像倉庫的賬戶。
這時候我們就不能用serviceAccountName了,而是需要使用serviceAccountNames。serviceAccountNames是一個List,可以指定Task關(guān)聯(lián)具體的serviceaccount,如下。
apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
name: demo-pipeline
namespace: default
spec:
serviceAccountNames:
- taskName: build-app
serviceAccountName: gitlab-sa
- taskName: push-image
serviceAccountName: docker-registry-sa
pipelineRef:
name: demo-pipeline
到這里基本的資源以及介紹完了,弄懂這篇文章,寫一個簡單的Pipeline應(yīng)該不成問題,后續(xù)的文章會分享具體的實踐。
當(dāng)前文章:一篇帶給你Tekton系列之理論篇
分享路徑:http://m.5511xx.com/article/dhjidjj.html


咨詢
建站咨詢
