新聞中心
使用 Tekton 重構(gòu)自動(dòng)化流水線
作者:陽(yáng)明 2021-06-28 06:32:46
云計(jì)算
自動(dòng)化 在 Tekton 中我們就可以將這些階段直接轉(zhuǎn)換成 Task 任務(wù),clone 代碼在 Tekton 中不需要我們主動(dòng)定義一個(gè)任務(wù),只需要在執(zhí)行的任務(wù)上面指定一個(gè)輸入的代碼資源即可

前面我們講解了使用 Jenkins 流水線來(lái)實(shí)現(xiàn) Kubernetes 應(yīng)用的 CI/CD,現(xiàn)在我們來(lái)將這個(gè)流水線遷移到 Tekton 上面來(lái),其實(shí)整體思路都是一樣的,就是把要整個(gè)工作流劃分成不同的任務(wù)來(lái)執(zhí)行,前面工作流的階段劃分了以下幾個(gè)階段:Clone 代碼 -> 單元測(cè)試 -> Golang 編譯打包 -> Docker 鏡像構(gòu)建/推送 -> Kubectl 部署服務(wù)。
在 Tekton 中我們就可以將這些階段直接轉(zhuǎn)換成 Task 任務(wù),Clone 代碼在 Tekton 中不需要我們主動(dòng)定義一個(gè)任務(wù),只需要在執(zhí)行的任務(wù)上面指定一個(gè)輸入的代碼資源即可。下面我們就來(lái)將上面的工作流一步一步來(lái)轉(zhuǎn)換成 Tekton 流水線,代碼倉(cāng)庫(kù)同樣還是 http://git.k8s.local/course/devops-demo.git。
Clone 代碼
雖然我們可以不用單獨(dú)定義一個(gè) Clone 代碼的任務(wù),直接使用 git 類型的輸入資源即可,由于這里涉及到的任務(wù)較多,而且很多時(shí)候都需要先 Clone 代碼然后再進(jìn)行操作,所以最好的方式是將代碼 Clone 下來(lái)過(guò)后通過(guò) Workspace 共享給其他任務(wù),這里我們可以直接使用 Catalog git-clone 來(lái)實(shí)現(xiàn)這個(gè)任務(wù),我們可以根據(jù)自己的需求做一些定制,對(duì)應(yīng)的 Task 如下所示:
- # task-clone.yaml
- apiVersion: tekton.dev/v1beta1
- kind: Task
- metadata:
- name: git-clone
- spec:
- workspaces:
- - name: output
- description: The git repo will be cloned onto the volume backing this Workspace.
- - name: basic-auth
- optional: true
- description: |
- A Workspace containing a .gitconfig and .git-credentials file. These
- will be copied to the user's home before any git commands are run. Any
- other files in this Workspace are ignored. It is strongly recommended
- to use ssh-directory over basic-auth whenever possible and to bind a
- Secret to this Workspace over other volume types.
- params:
- - name: url
- description: Repository URL to clone from.
- type: string
- - name: revision
- description: Revision to checkout. (branch, tag, sha, ref, etc...)
- type: string
- default: ""
- - name: refspec
- description: Refspec to fetch before checking out revision.
- default: ""
- - name: submodules
- description: Initialize and fetch git submodules.
- type: string
- default: "true"
- - name: depth
- description: Perform a shallow clone, fetching only the most recent N commits.
- type: string
- default: "1"
- - name: sslVerify
- description: Set the `http.sslVerify` global git config. Setting this to `false` is not advised unless you are sure that you trust your git remote.
- type: string
- default: "true"
- - name: subdirectory
- description: Subdirectory inside the `output` Workspace to clone the repo into.
- type: string
- default: ""
- - name: sparseCheckoutDirectories
- description: Define the directory patterns to match or exclude when performing a sparse checkout.
- type: string
- default: ""
- - name: deleteExisting
- description: Clean out the contents of the destination directory if it already exists before cloning.
- type: string
- default: "true"
- - name: verbose
- description: Log the commands that are executed during `git-clone`'s operation.
- type: string
- default: "true"
- - name: gitInitImage
- description: The image providing the git-init binary that this Task runs.
- type: string
- default: "cnych/tekton-git-init:v0.24.1"
- - name: userHome
- description: |
- Absolute path to the user's home directory. Set this explicitly if you are running the image as a non-root user or have overridden
- the gitInitImage param with an image containing custom user configuration.
- type: string
- default: "/root"
- results:
- - name: commit
- description: The precise commit SHA that was fetched by this Task.
- - name: url
- description: The precise URL that was fetched by this Task.
- steps:
- - name: clone
- image: "$(params.gitInitImage)"
- env:
- - name: HOME
- value: "$(params.userHome)"
- - name: PARAM_URL
- value: $(params.url)
- - name: PARAM_REVISION
- value: $(params.revision)
- - name: PARAM_REFSPEC
- value: $(params.refspec)
- - name: PARAM_SUBMODULES
- value: $(params.submodules)
- - name: PARAM_DEPTH
- value: $(params.depth)
- - name: PARAM_SSL_VERIFY
- value: $(params.sslVerify)
- - name: PARAM_SUBDIRECTORY
- value: $(params.subdirectory)
- - name: PARAM_DELETE_EXISTING
- value: $(params.deleteExisting)
- - name: PARAM_VERBOSE
- value: $(params.verbose)
- - name: PARAM_SPARSE_CHECKOUT_DIRECTORIES
- value: $(params.sparseCheckoutDirectories)
- - name: PARAM_USER_HOME
- value: $(params.userHome)
- - name: WORKSPACE_OUTPUT_PATH
- value: $(workspaces.output.path)
- - name: WORKSPACE_BASIC_AUTH_DIRECTORY_BOUND
- value: $(workspaces.basic-auth.bound)
- - name: WORKSPACE_BASIC_AUTH_DIRECTORY_PATH
- value: $(workspaces.basic-auth.path)
- script: |
- #!/usr/bin/env sh
- set -eu
- if [ "${PARAM_VERBOSE}" = "true" ] ; then
- set -x
- fi
- if [ "${WORKSPACE_BASIC_AUTH_DIRECTORY_BOUND}" = "true" ] ; then
- cp "${WORKSPACE_BASIC_AUTH_DIRECTORY_PATH}/.git-credentials" "${PARAM_USER_HOME}/.git-credentials"
- cp "${WORKSPACE_BASIC_AUTH_DIRECTORY_PATH}/.gitconfig" "${PARAM_USER_HOME}/.gitconfig"
- chmod 400 "${PARAM_USER_HOME}/.git-credentials"
- chmod 400 "${PARAM_USER_HOME}/.gitconfig"
- fi
- CHECKOUT_DIR="${WORKSPACE_OUTPUT_PATH}/${PARAM_SUBDIRECTORY}"
- cleandir() {
- # Delete any existing contents of the repo directory if it exists.
- #
- # We don't just "rm -rf ${CHECKOUT_DIR}" because ${CHECKOUT_DIR} might be "/"
- # or the root of a mounted volume.
- if [ -d "${CHECKOUT_DIR}" ] ; then
- # Delete non-hidden files and directories
- rm -rf "${CHECKOUT_DIR:?}"/*
- # Delete files and directories starting with . but excluding ..
- rm -rf "${CHECKOUT_DIR}"/.[!.]*
- # Delete files and directories starting with .. plus any other character
- rm -rf "${CHECKOUT_DIR}"/..?*
- fi
- }
- if [ "${PARAM_DELETE_EXISTING}" = "true" ] ; then
- cleandir
- fi
- /ko-app/git-init \
- -url="${PARAM_URL}" \
- -revision="${PARAM_REVISION}" \
- -refspec="${PARAM_REFSPEC}" \
- -path="${CHECKOUT_DIR}" \
- -sslVerify="${PARAM_SSL_VERIFY}" \
- -submodules="${PARAM_SUBMODULES}" \
- -depth="${PARAM_DEPTH}" \
- -sparseCheckoutDirectories="${PARAM_SPARSE_CHECKOUT_DIRECTORIES}"
- cd "${CHECKOUT_DIR}"
- RESULT_SHA="$(git rev-parse HEAD)"
- EXIT_CODE="$?"
- if [ "${EXIT_CODE}" != 0 ] ; then
- exit "${EXIT_CODE}"
- fi
- printf "%s" "${RESULT_SHA}" > "$(results.commit.path)"
- printf "%s" "${PARAM_URL}" > "$(results.url.path)"
一般來(lái)說(shuō)我們只需要提供 output 這個(gè)個(gè)用于持久化代碼的 workspace,然后還包括 url 和 revision 這兩個(gè)參數(shù),其他使用默認(rèn)的即可。
單元測(cè)試
單元測(cè)試階段比較簡(jiǎn)單,正常來(lái)說(shuō)也是只是單純執(zhí)行一個(gè)測(cè)試命令即可,我們這里沒(méi)有真正執(zhí)行單元測(cè)試,所以簡(jiǎn)單測(cè)試下即可,編寫一個(gè)如下所示的 Task:
- # task-test.yaml
- apiVersion: tekton.dev/v1beta1
- kind: Task
- metadata:
- name: test
- spec:
- steps:
- - name: test
- image: golang:1.14-alpine
- command: ['echo']
- args: ['this is a test task']
編譯打包
然后第二個(gè)階段是編譯打包階段,因?yàn)槲覀冞@個(gè)項(xiàng)目的 Dockerfile 不是使用的多階段構(gòu)建,所以需要先用一個(gè)任務(wù)去將應(yīng)用編譯打包成二進(jìn)制文件,然后將這個(gè)編譯過(guò)后的文件傳遞到下一個(gè)任務(wù)進(jìn)行鏡像構(gòu)建。
我們已經(jīng)明確了這個(gè)階段要做的事情,編寫任務(wù)也就簡(jiǎn)單了,創(chuàng)建如下所的 Task 任務(wù),首先需要通過(guò)定義一個(gè) workspace 把 clone 任務(wù)里面的代碼關(guān)聯(lián)過(guò)來(lái):
- # task-build.yaml
- apiVersion: tekton.dev/v1beta1
- kind: Task
- metadata:
- name: build
- spec:
- workspaces:
- - name: go-repo
- mountPath: /workspace/repo
- steps:
- - name: build
- image: golang:1.14-alpine
- workingDir: /workspace/repo
- script: |
- go build -v -o app
- env:
- - name: GOPROXY
- value: https://goproxy.cn
- - name: GOOS
- value: linux
- - name: GOARCH
- value: amd64
這個(gè)構(gòu)建任務(wù)也很簡(jiǎn)單,只是我們將需要用到的環(huán)境變量直接通過(guò) env 注入了,當(dāng)然直接寫入到 script 中也是可以的,或者直接使用 command 來(lái)執(zhí)行任務(wù)都可以,然后構(gòu)建生成的 app 這個(gè)二進(jìn)制文件保留在代碼根目錄,這樣也就可以通過(guò) workspace 進(jìn)行共享了。
Docker 鏡像
接下來(lái)就是構(gòu)建并推送 Docker 鏡像了,前面我們介紹過(guò)使用 Kaniko、DooD、DinD 3種模式的鏡像構(gòu)建方式,這里我們直接使用 DinD 這種模式,我們這里要構(gòu)建的鏡像 Dockerfile 非常簡(jiǎn)單:
- FROM alpine
- WORKDIR /home
- # 修改alpine源為阿里云
- RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories && \
- apk update && \
- apk upgrade && \
- apk add ca-certificates && update-ca-certificates && \
- apk add --update tzdata && \
- rm -rf /var/cache/apk/*
- COPY app /home/
- ENV TZ=Asia/Shanghai
- EXPOSE 8080
- ENTRYPOINT ./app
就行直接將編譯好的二進(jìn)制文件拷貝到鏡像中即可,所以我們這里同樣需要通過(guò) Workspace 去獲取上一個(gè)構(gòu)建任務(wù)的制品,當(dāng)然要使用 DinD 模式構(gòu)建鏡像,需要用到 sidecar 功能,創(chuàng)建一個(gè)如下所示的任務(wù):
- # task-docker.yaml
- apiVersion: tekton.dev/v1beta1
- kind: Task
- metadata:
- name: docker
- spec:
- workspaces:
- - name: go-repo
- params:
- - name: image
- description: Reference of the image docker will produce.
- - name: registry_mirror
- description: Specific the docker registry mirror
- default: ""
- - name: registry_url
- description: private docker images registry url
- steps:
- - name: docker-build # 構(gòu)建步驟
- image: docker:stable
- env:
- - name: DOCKER_HOST # 用 TLS 形式通過(guò) TCP 鏈接 sidecar
- value: tcp://localhost:2376
- - name: DOCKER_TLS_VERIFY # 校驗(yàn) TLS
- value: "1"
- - name: DOCKER_CERT_PATH # 使用 sidecar 守護(hù)進(jìn)程生成的證書
- value: /certs/client
- - name: DOCKER_PASSWORD
- valueFrom:
- secretKeyRef:
- name: harbor-auth
- key: password
- - name: DOCKER_USERNAME
- valueFrom:
- secretKeyRef:
- name: harbor-auth
- key: username
- workingDir: $(workspaces.go-repo.path)
- script: | # docker 構(gòu)建命令
- docker login $(params.registry_url) -u $DOCKER_USERNAME -p $DOCKER_PASSWORD
- docker build --no-cache -f ./Dockerfile -t $(params.image) .
- docker push $(params.image)
- volumeMounts: # 聲明掛載證書目錄
- - mountPath: /certs/client
- name: dind-certs
- sidecars: # sidecar 模式,提供 docker daemon服務(wù),實(shí)現(xiàn)真正的 DinD 模式
- - image: docker:dind
- name: server
- args:
- - --storage-driver=vfs
- - --userland-proxy=false
- - --debug
- - --insecure-registry=$(params.registry_url)
- - --registry-mirror=$(params.registry_mirror)
- securityContext:
- privileged: true
- env:
- - name: DOCKER_TLS_CERTDIR # 將生成的證書寫入與客戶端共享的路徑
- value: /certs
- volumeMounts:
- - mountPath: /certs/client
- name: dind-certs
- readinessProbe: # 等待 dind daemon 生成它與客戶端共享的證書
- periodSeconds: 1
- exec:
- command: ["ls", "/certs/client/ca.pem"]
- volumes: # 使用 emptyDir 的形式即可
- - name: dind-certs
- emptyDir: {}
這個(gè)任務(wù)的重點(diǎn)還是要去聲明一個(gè) Workspace,當(dāng)執(zhí)行任務(wù)的時(shí)候要使用和前面構(gòu)建任務(wù)同一個(gè) Workspace,這樣就可以獲得上面編譯成的 app 這個(gè)二進(jìn)制文件了。
部署
接下來(lái)的部署階段,我們同樣可以參考之前 Jenkins 流水線里面的實(shí)現(xiàn),由于項(xiàng)目中我們包含了 Helm Chart 包,所以直接使用 Helm 來(lái)部署即可,要實(shí)現(xiàn) Helm 部署,當(dāng)然我們首先需要一個(gè)包含 helm 命令的鏡像,當(dāng)然完全可以自己去編寫一個(gè)這樣的任務(wù),此外我們還可以直接去 hub.tekton.dev 上面查找 Catalog,因?yàn)檫@上面就有很多比較通用的一些任務(wù)了,比如 helm-upgrade-from-source 這個(gè) Task 任務(wù)就完全可以滿足我們的需求了:
helm tekton
這個(gè) Catalog 下面也包含完整的使用文檔了,我們可以將該任務(wù)直接下載下來(lái)根據(jù)我們自己的需求做一些定制修改,如下所示:
- # task-deploy.yaml
- apiVersion: tekton.dev/v1beta1
- kind: Task
- metadata:
- name: deploy
- spec:
- params:
- - name: charts_dir
- description: The directory in source that contains the helm chart
- - name: release_name
- description: The helm release name
- - name: release_namespace
- description: The helm release namespace
- default: ""
- - name: overwrite_values
- description: "Specify the values you want to overwrite, comma separated: autoscaling.enabled=true,replicas=1"
- default: ""
- - name: values_file
- description: "The values file to be used"
- default: "values.yaml"
- - name: helm_image
- description: "helm image to be used"
- default: "docker.io/lachlanevenson/k8s-helm:v3.3.4@sha256:e1816be207efbd342cba9d3d32202e237e3de20af350617f8507dc033ea66803" #tag: v3.3.4
- workspaces:
- - name: source
- results:
- - name: helm-status
- description: Helm deploy status
- steps:
- - name: upgrade
- image: $(params.helm_image)
- workingDir: /workspace/source
- script: |
- echo current installed helm releases
- helm list --namespace "$(params.release_namespace)"
- echo installing helm chart...
- helm upgrade --install --wait --values "$(params.charts_dir)/$(params.values_file)" --create-namespace --namespace "$(params.release_namespace)" $(params.release_name) $(params.charts_dir) --debug --set "$(params.overwrite_values)"
- status=`helm status $(params.release_name) --namespace "$(params.release_namespace)" | awk '/STATUS/ {print $2}'`
- echo ${status} | tr -d "\n" | tee $(results.helm-status.path)
因?yàn)槲覀兊?Helm Chart 模板就在代碼倉(cāng)庫(kù)中,所以不需要從 Chart Repo 倉(cāng)庫(kù)中獲取,只需要指定 Chart 路徑即可,其他可配置的參數(shù)都通過(guò) params 參數(shù)暴露出去了,非常靈活,最后我們還獲取了 Helm 部署的狀態(tài),寫入到了 Results 中,方便后續(xù)任務(wù)處理。
回滾
最后應(yīng)用部署完成后可能還需要回滾,因?yàn)榭赡懿渴鸬膽?yīng)用有錯(cuò)誤,當(dāng)然這個(gè)回滾動(dòng)作最好是我們自己去觸發(fā),但是在某些場(chǎng)景下,比如 helm 部署已經(jīng)明確失敗了,那么我們當(dāng)然可以自動(dòng)回滾了,所以就需要判斷當(dāng)部署失敗的時(shí)候再執(zhí)行回滾,也就是這個(gè)任務(wù)并不是一定會(huì)發(fā)生的,只在某些場(chǎng)景下才會(huì)出現(xiàn),我們可以在流水線中通過(guò)使用 WhenExpressions 來(lái)實(shí)現(xiàn)這個(gè)功能,之前版本中是使用 Conditions,不過(guò)已經(jīng)廢棄了。要只在滿足某些條件時(shí)運(yùn)行任務(wù),可以使用 when 字段來(lái)保護(hù)任務(wù)執(zhí)行,when 字段允許你列出對(duì) WhenExpressions 的一系列引用。
WhenExpressions 由 Input、Operator 和 Values 幾部分組成:
- Input 是 WhenExpressions 的輸入,它可以是一個(gè)靜態(tài)的輸入或變量(Params 或 Results),如果未提供輸入,則默認(rèn)為空字符串
- Operator 是一個(gè)運(yùn)算符,表示 Input 和 Values 之間的關(guān)系,有效的運(yùn)算符包括 in、notin
- Values 是一個(gè)字符串?dāng)?shù)組,必須提供一個(gè)非空的 Values 數(shù)組,它同樣可以包含靜態(tài)值或者變量(Params、Results 或者 Workspaces 綁定)
當(dāng)在一個(gè) Task 任務(wù)中配置了 WhenExpressions,在執(zhí)行 Task 之前會(huì)評(píng)估聲明的 WhenExpressions,如果結(jié)果為 True,則執(zhí)行任務(wù),如果為 False,則不會(huì)執(zhí)行該任務(wù)。
我們這里創(chuàng)建的回滾任務(wù)如下所示:
- # task-rollback.yaml
- apiVersion: tekton.dev/v1beta1
- kind: Task
- metadata:
- name: rollback
- spec:
- params:
- - name: release_name
- description: The helm release name
- - name: release_namespace
- description: The helm release namespace
- default: ""
- - name: helm_image
- description: "helm image to be used"
- default: "docker.io/lachlanevenson/k8s-helm:v3.3.4@sha256:e1816be207efbd342cba9d3d32202e237e3de20af350617f8507dc033ea66803" #tag: v3.3.4
- steps:
- - name: rollback
- image: $(params.helm_image)
- script: |
- echo rollback current installed helm releases
- helm rollback $(params.release_name) --namespace $(params.release_namespace)
流水線
現(xiàn)在我們的整個(gè)工作流任務(wù)都已經(jīng)創(chuàng)建完成了,接下來(lái)我們就可以將這些任務(wù)全部串聯(lián)起來(lái)組成一個(gè) Pipeline 流水線了,將上面定義的幾個(gè) Task 引用到 Pipeline 中來(lái),當(dāng)然還需要聲明 Task 中用到的 resources 或者 workspaces 這些數(shù)據(jù):
- # pipeline.yaml
- apiVersion: tekton.dev/v1beta1
- kind: Pipeline
- metadata:
- name: pipeline
- spec:
- workspaces: # 聲明 workspaces
- - name: go-repo-pvc
- params:
- # 定義代碼倉(cāng)庫(kù)
- - name: git_url
- - name: revision
- type: string
- default: "master"
- # 定義鏡像參數(shù)
- - name: image
- - name: registry_url
- type: string
- default: "harbor.k8s.local"
- - name: registry_mirror
- type: string
- default: "https://ot2k4d59.mirror.aliyuncs.com/"
- # 定義 helm charts 參數(shù)
- - name: charts_dir
- - name: release_name
- - name: release_namespace
- default: "default"
- - name: overwrite_values
- default: ""
- - name: values_file
- default: "values.yaml"
- tasks: # 添加task到流水線中
- - name: clone
- taskRef:
- name: git-clone
- workspaces:
- - name: output
- workspace: go-repo-pvc
- params:
- - name: url
- value: $(params.git_url)
- - name: revision
- value: $(params.revision)
- - name: test
- taskRef:
- name: test
- - name: build # 編譯二進(jìn)制程序
- taskRef:
- name: build
- runAfter: # 測(cè)試任務(wù)執(zhí)行之后才執(zhí)行 build task
- - test
- - clone
- workspaces: # 傳遞 workspaces
- - name: go-repo
- workspace: go-repo-pvc
- - name: docker # 構(gòu)建并推送 Docker 鏡像
- taskRef:
- name: docker
- runAfter:
- - build
- workspaces: # 傳遞 workspaces
- - name: go-repo
- workspace: go-repo-pvc
- params: # 傳遞參數(shù)
- - name: image
- value: $(params.image)
- - name: registry_url
- value: $(params.registry_url)
- - name: registry_mirror
- value: $(params.registry_mirror)
- - name: deploy # 部署應(yīng)用
- taskRef:
- name: deploy
- runAfter:
- - docker
- workspaces:
- - name: source
- workspace: go-repo-pvc
- params:
- - name: charts_dir
- value: $(params.charts_dir)
- - name: release_name
- value: $(params.release_name)
- - name: release_namespace
- value: $(params.release_namespace)
- - name: overwrite_values
- value: $(params.overwrite_values)
- - name: values_file
- value: $(params.values_file)
- - name: rollback # 回滾
- taskRef:
- name: rollback
- when:
- - input: "$(tasks.deploy.results.helm-status)"
- operator: in
- values: ["failed"]
- params:
- - name: release_name
- value: $(params.release_name)
- - name: release_namespace
- value: $(params.release_namespace)
整體流程比較簡(jiǎn)單,就是在 Pipeline 需要先聲明使用到的 Workspace、Resource、Params 這些資源,然后將聲明的數(shù)據(jù)傳遞到 Task 任務(wù)中去,需要注意的是最后一個(gè)回滾任務(wù),我們需要根據(jù)前面的 deploy 任務(wù)的結(jié)果來(lái)判斷是否需要執(zhí)
網(wǎng)頁(yè)題目:使用Tekton重構(gòu)自動(dòng)化流水線
瀏覽路徑:http://m.fisionsoft.com.cn/article/djojdeo.html


咨詢
建站咨詢
