一篇帶你創(chuàng)建 Tekton 流水線
前面我們創(chuàng)建的兩個(gè)任務(wù) test 和 build-and-push 都已經(jīng)完成了,我們還可以創(chuàng)建一個(gè)流水線來將這兩個(gè)任務(wù)組織起來,形成一個(gè)流水線,這里就是我們要使用的 Pipeline 這個(gè) CRD 對(duì)象。
創(chuàng)建流水線
比如我們這里的流水線流程為先運(yùn)行 test 任務(wù),如果通過了再執(zhí)行后面的 build-and-push 這個(gè)任務(wù),那么我們可以創(chuàng)建一個(gè)名為 test-pipeline.yaml 的資源對(duì)象,內(nèi)容如下所示:
- # test-pipeline.yaml
- apiVersion: tekton.dev/v1beta1
- kind: Pipeline
- metadata:
- name: test-pipeline
- spec:
- resources: # 為 Tasks 提供輸入和輸出資源聲明
- - name: demo-git
- type: git
- - name: harbor-image
- type: image
- tasks: # 添加task到流水線中
- # 運(yùn)行應(yīng)用測(cè)試
- - name: test
- taskRef:
- name: test
- resources:
- inputs:
- - name: repo # Task 輸入名稱
- resource: demo-git # Pipeline 資源名稱
- # 構(gòu)建并推送 Docker 鏡像
- - name: build-and-push
- taskRef:
- name: build-and-push
- runAfter:
- - test # 測(cè)試任務(wù)執(zhí)行之后
- resources:
- inputs:
- - name: repo # 指定輸入的git倉庫資源
- resource: demo-git
- outputs: # 指定輸出的鏡像資源
- - name: builtImage
- resource: harbor-image
首先我們需要定義流水線需要哪些資源,可以是輸入或者輸出的資源,在這里我們只有一個(gè)輸入,那就是命名為 repo 的應(yīng)用程序源碼的 GitHub 倉庫。接下來定義任務(wù),每個(gè)任務(wù)都通過 taskRef 進(jìn)行引用,并傳遞任務(wù)需要的輸入?yún)?shù)。
同樣直接創(chuàng)建這個(gè)資源對(duì)象即可:
- $ kubectl apply -f test-pipeline.yaml
- pipeline.tekton.dev/test-pipeline created
前面我們提到過和通過創(chuàng)建 TaskRun 去觸發(fā) Task 任務(wù)類似,我們可以通過創(chuàng)建一個(gè) PipelineRun 對(duì)象來運(yùn)行流水線。這里我們創(chuàng)建一個(gè)名為 test-pipelinerun.yaml 的 PipelineRun 對(duì)象來運(yùn)行流水線,文件內(nèi)容如下所示:
- apiVersion: tekton.dev/v1beta1
- kind: PipelineRun
- metadata:
- name: test-pipelinerun
- spec:
- serviceAccountName: build-sa
- pipelineRef:
- name: test-pipeline
- resources:
- - name: demo-git # 指定輸入的git倉庫資源
- resourceRef:
- name: demo-git
- - name: harbor-image # 指定輸出的鏡像資源
- resourceRef:
- name: harbor-image
定義方式和 TaskRun 幾乎一樣,通過 serviceAccountName 屬性指定 ServiceAccount 對(duì)象,pipelineRef 關(guān)聯(lián)流水線對(duì)象。同樣直接創(chuàng)建這個(gè)資源,創(chuàng)建后就會(huì)觸發(fā)我們的流水線任務(wù)了:
- $ kubectl apply -f test-pipelinerun.yaml
- pipelinerun.tekton.dev/test-pipelinerun created
- $ github kubectl get pods | grep test-pipelinerun
- test-pipelinerun-build-and-push-62g65-pod-6jqqf 0/4 Init:1/2 0 3s
- test-pipelinerun-test-c4r9m-pod-j7jjd 0/2 Completed 0 12s
- $ tkn pipelinerun describe test-pipelinerun
- Name: test-pipelinerun
- Namespace: default
- Pipeline Ref: test-pipeline
- Service Account: build-sa
- Timeout: 1h0m0s
- Labels:
- tekton.dev/pipeline=test-pipeline
- 🌡️ Status
- STARTED DURATION STATUS
- 47 seconds ago 22 seconds Succeeded
- 📦 Resources
- NAME RESOURCE REF
- ∙ demo-git demo-git
- ∙ harbor-image harbor-image
- ⚓ Params
- No params
- 📝 Results
- No results
- 📂 Workspaces
- No workspaces
- 🗂 Taskruns
- NAME TASK NAME STARTED DURATION STATUS
- ∙ test-pipelinerun-build-and-push-62g65 build-and-push 38 seconds ago 13 seconds Succeeded
- ∙ test-pipelinerun-test-c4r9m test 46 seconds ago 8 seconds Succeeded
到這里證明我們的流水線執(zhí)行成功了。我們將 Tekton 安裝在 Kubernetes 集群上,定義了一個(gè) Task,并通過 YAML 清單和 Tekton CLI 創(chuàng)建 TaskRun 對(duì)其進(jìn)行了測(cè)試。我們創(chuàng)建了由兩個(gè)任務(wù)組成的 Tektok 流水線,第一個(gè)任務(wù)是從 GitHub 克隆代碼并運(yùn)行應(yīng)用程序測(cè)試,第二個(gè)任務(wù)是構(gòu)建一個(gè) Docker 鏡像并將其推送到 Docker Hub 上。
使用 Results 傳遞數(shù)據(jù)
上面我們?cè)跇?gòu)建鏡像的時(shí)候可以看到鏡像的 TAG 我們是寫死的,或者需要在每次執(zhí)行的時(shí)候通過參數(shù)傳遞進(jìn)去,比較麻煩,那么有沒有什么辦法可以自動(dòng)生成鏡像 TAG 呢?比如根據(jù)時(shí)間戳來生成一個(gè)構(gòu)建的ID。
這里我們可以通過定義一個(gè) Task 任務(wù),然后通過 script 腳本去獲取到數(shù)據(jù)后傳入到 results 中去,我們可以把這些 results 數(shù)據(jù)傳遞到流水線中的其他任務(wù)中去,比如我們想要獲取 git commit 的 SHA 值,或者生成一個(gè)隨機(jī)的 ID 來作為鏡像 TAG,比如這里我們創(chuàng)建一個(gè)名為 generate-build-id 的 Task 任務(wù),定義了 get-timestamp 和 get-buildid 兩個(gè) Steps,一個(gè)用于生成時(shí)間戳,一個(gè)用于生成一個(gè)包含基本版本的結(jié)果值,將結(jié)果添加到 results 中去。
- # generate-build-id.yaml
- apiVersion: tekton.dev/v1beta1
- kind: Task
- metadata:
- name: generate-build-id
- spec:
- description: >-
- Given a base version, this task generates a unique build id by appending
- the base-version to the current timestamp.
- params:
- - name: base-version
- description: Base product version
- type: string
- default: "1.0"
- results:
- - name: timestamp
- description: Current timestamp
- - name: build-id
- description: ID of the current build
- steps:
- - name: get-timestamp
- image: bash:5.0.18
- script: |
- #!/usr/bin/env bash
- ts=`date "+%Y%m%d-%H%M%S"`
- echo "Current Timestamp: ${ts}"
- echo ${ts} | tr -d "\n" | tee $(results.timestamp.path)
- - name: get-buildid
- image: bash:5.0.18
- script: |
- #!/usr/bin/env bash
- ts=`cat $(results.timestamp.path)`
- buildId=$(inputs.params.base-version)-${ts}
- echo ${buildId} | tr -d "\n" | tee $(results.build-id.path)
直接創(chuàng)建上面的 Task:
- kubectl apply -f generate-build-id.yaml
創(chuàng)建完成后,現(xiàn)在我們就可以在 Pipeline 中來使用這個(gè) Task 了,用來生成構(gòu)建 ID,修改 test-pipeline.yaml,增加 generate-build-id 任務(wù):
- # test-pipeline.yaml
- apiVersion: tekton.dev/v1beta1
- kind: Pipeline
- metadata:
- name: test-pipeline
- spec:
- resources: # 為 Tasks 提供輸入和輸出資源聲明
- - name: demo-git
- type: git
- - name: harbor-image
- type: image
- params:
- - name: image-tag
- type: string
- tasks: # 添加task到流水線中
- # 運(yùn)行應(yīng)用測(cè)試
- - name: test
- taskRef:
- name: test
- resources:
- inputs:
- - name: repo # Task 輸入名稱
- resource: demo-git # Pipeline 資源名稱
- - name: get-build-id
- taskRef:
- name: generate-build-id
- params:
- - name: base-version
- value: $(params.image-tag)
- # 構(gòu)建并推送 Docker 鏡像
- - name: build-and-push
- taskRef:
- name: build-and-push
- runAfter:
- - test # 測(cè)試任務(wù)執(zhí)行之后
- resources:
- inputs:
- - name: repo # 指定輸入的git倉庫資源
- resource: demo-git
- outputs: # 指定輸出的鏡像資源
- - name: builtImage
- resource: harbor-image
- params:
- - name: imageTag
- value: "$(tasks.get-build-id.results.build-id)"
然后在 build-and-push 任務(wù)中通過 "$(tasks.get-build-id.results.build-id)" 獲取構(gòu)建的 ID,將這個(gè) ID 作為參數(shù)傳入任務(wù)中去,所以我們也需要在 build-and-push 任務(wù)中增加 build-id 這個(gè)參數(shù):
- # task-build-push.yaml
- apiVersion: tekton.dev/v1beta1
- kind: Task
- metadata:
- name: build-and-push
- spec:
- resources:
- inputs: # 定義輸入資源
- - name: repo #輸入資源,就是github的那個(gè)倉庫
- type: git
- outputs: # 定義輸出資源
- - name: builtImage # 輸出鏡像名字
- type: image
- params:
- - name: pathToDockerfile #指明 dockerfile 在倉庫中的哪個(gè)位置
- type: string
- default: $(resources.inputs.repo.path)/Dockerfile # repo資源的路徑
- description: The path to the dockerfile to build
- - name: pathToContext #指明 dockerfile 在倉庫中的哪個(gè)位置
- type: string
- default: $(resources.inputs.repo.path) # repo資源的路徑
- description: the build context used by docker daemon
- - name: imageTag
- type: string
- default: "v0.2.0"
- description: the docker image tag
- steps:
- - name: build-and-push
- image: docker:stable
- script: |
- #!/usr/bin/env sh
- docker login harbor.k8s.local
- docker build -t $(resources.outputs.builtImage.url):$(params.imageTag) -f $(params.pathToDockerfile) $(params.pathToContext)
- docker push $(resources.outputs.builtImage.url):$(params.imageTag) # 這邊的參數(shù)都是在 input 和 output 中定義的
- volumeMounts:
- - name: dockersock #將docker.sock文件掛載進(jìn)來,使用宿主機(jī)docker daemon 構(gòu)建鏡像
- mountPath: /var/run/docker.sock
- volumes:
- - name: dockersock
- hostPath:
- path: /var/run/docker.sock
然后需要將 builtImage 這個(gè) output 資源的 url 定義中將鏡像 tag 去掉,在 PipelineRun 對(duì)象中新增 image-tag 的參數(shù):
- # test-pipelinerun.yaml
- apiVersion: tekton.dev/v1beta1
- kind: PipelineRun
- metadata:
- name: test-pipelinerun
- spec:
- serviceAccountName: build-sa
- pipelineRef:
- name: test-pipeline
- resources:
- - name: demo-git # 指定輸入的git倉庫資源
- resourceRef:
- name: demo-git
- - name: harbor-image # 指定輸出的鏡像資源
- resourceRef:
- name: harbor-image
- params:
- - name: image-tag
- value: "v0.3.0"
所有修改完成后,重新執(zhí)行我們的整個(gè)流水線即可。
- $ tkn pipelinerun logs test-pipelinerun
- [test : git-source-repo-g68nd] {"level":"info","ts":1623934515.6170688,"caller":"git/git.go:169","msg":"Successfully cloned https://github.com.cnpmjs.org/cnych/tekton-demo @ 5e1e3a1d0f167b9b639df5b802a0f0f81064d21e (grafted, HEAD, origin/master) in path /workspace/repo"}
- [test : git-source-repo-g68nd] {"level":"info","ts":1623934515.6349964,"caller":"git/git.go:207","msg":"Successfully initialized and updated submodules in path /workspace/repo"}
- [test : run-test] PASS
- [test : run-test] ok _/workspace/repo 0.002s
- [get-build-id : get-timestamp] Current Timestamp: 20210617-125634
- [get-build-id : get-timestamp] 20210617-125634
- [get-build-id : get-buildid] v0.3.0-20210617-125634
- [build-and-push : git-source-repo-v2lhk] {"level":"info","ts":1623934601.68953,"caller":"git/git.go:169","msg":"Successfully cloned https://github.com.cnpmjs.org/cnych/tekton-demo @ 5e1e3a1d0f167b9b639df5b802a0f0f81064d21e (grafted, HEAD, origin/master) in path /workspace/repo"}
- [build-and-push : git-source-repo-v2lhk] {"level":"info","ts":1623934601.7080255,"caller":"git/git.go:207","msg":"Successfully initialized and updated submodules in path /workspace/repo"}
- [build-and-push : build-and-push] Authenticating with existing credentials...
- [build-and-push : build-and-push] Login Succeeded
- [build-and-push : build-and-push] WARNING! Your password will be stored unencrypted in /root/.docker/config.json.
- [build-and-push : build-and-push] Configure a credential helper to remove this warning. See
- [build-and-push : build-and-push] https://docs.docker.com/engine/reference/commandline/login/#credentials-store
- [build-and-push : build-and-push]
- [build-and-push : build-and-push] Sending build context to Docker daemon 154.1kB
- [build-and-push : build-and-push] Step 1/6 : FROM golang:1.14-alpine
- ......
- [build-and-push : build-and-push] Successfully built 2358e77bbe0e
- [build-and-push : build-and-push] Successfully tagged harbor.k8s.local/course/tekton-demo:v0.3.0-20210617-125634
- [build-and-push : build-and-push] The push refers to repository [harbor.k8s.local/course/tekton-demo]
- [build-and-push : build-and-push] f9a271a3fb3c: Preparing
- ......
- [build-and-push : build-and-push] 26ec43d351f2: Pushed
- [build-and-push : build-and-push] v0.3.0-20210617-125634: digest: sha256:68be388e3f85dd10a6689a986eb2f7f7f5a5c89bb03f40c3db3178e0ce242752 size: 2198
- [build-and-push : image-digest-exporter-t54fb] {"severity":"INFO","timestamp":"2021-06-17T12:56:46.54052284Z","caller":"logging/config.go:116","message":"Successfully created the logger."}
- [build-and-push : image-digest-exporter-t54fb] {"severity":"INFO","timestamp":"2021-06-17T12:56:46.541010181Z","caller":"logging/config.go:117","message":"Logging level set to: info"}
- [build-and-push : image-digest-exporter-t54fb] {"severity":"INFO","timestamp":"2021-06-17T12:56:46.541254959Z","caller":"imagedigestexporter/main.go:59","message":"No index.json found for: builtImage","commit":"7ca5d61"}
我們可以看到在 get-build-id 任務(wù)中為我們生成了 v0.3.0-20210617-125634 這樣的鏡像 TAG,最后也通過 results 傳遞到了下面的構(gòu)建任務(wù)中去,鏡像的 TAG 也更新了。
Tekton Catalog
當(dāng)然這些任務(wù)其實(shí)都具有一定的通用性的,為此 Tekton 官方提供了一個(gè) Catalog 的服務(wù),用來專門提供一些通用的任務(wù),比如我們想要獲取 Git Commit 的相關(guān)信息,可以使用 https://artifacthub.io/packages/tekton-task/tekton-catalog-tasks/git-clone 這個(gè) Catalog,文檔中也包含相關(guān)的使用說明。
到這里我們就完成了使用 Tekton 創(chuàng)建 CI/CD 流水線的一個(gè)簡(jiǎn)單示例,不過這個(gè)示例還比較簡(jiǎn)單,接下來我們?cè)偻ㄟ^一個(gè)稍微復(fù)雜點(diǎn)的應(yīng)用來完成我們前面的 Jenkins 流水線。