使用 Tekton 重構自動化流水線


前面我們講解了使用 Jenkins 流水線來實現 Kubernetes 應用的 CI/CD,現在我們來將這個流水線遷移到 Tekton 上面來,其實整體思路都是一樣的,就是把要整個工作流划分成不同的任務來執行,前面工作流的階段划分了以下幾個階段:Clone 代碼 -> 單元測試 -> Golang 編譯打包 -> Docker 鏡像構建/推送 -> Kubectl 部署服務

k8s技術圈
k8s技術圈
專注容器、專注 kubernetes 技術......
223篇原創內容
公眾號

在 Tekton 中我們就可以將這些階段直接轉換成 Task 任務,Clone 代碼在 Tekton 中不需要我們主動定義一個任務,只需要在執行的任務上面指定一個輸入的代碼資源即可。下面我們就來將上面的工作流一步一步來轉換成 Tekton 流水線,代碼倉庫同樣還是 http://git.k8s.local/course/devops-demo.git

Clone 代碼

雖然我們可以不用單獨定義一個 Clone 代碼的任務,直接使用 git 類型的輸入資源即可,由於這里涉及到的任務較多,而且很多時候都需要先 Clone 代碼然后再進行操作,所以最好的方式是將代碼 Clone 下來過后通過 Workspace 共享給其他任務,這里我們可以直接使用 Catalog git-clone 來實現這個任務,我們可以根據自己的需求做一些定制,對應的 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)"

一般來說我們只需要提供 output 這個個用於持久化代碼的 workspace,然后還包括 url 和 revision 這兩個參數,其他使用默認的即可。

單元測試

單元測試階段比較簡單,正常來說也是只是單純執行一個測試命令即可,我們這里沒有真正執行單元測試,所以簡單測試下即可,編寫一個如下所示的 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']

編譯打包

然后第二個階段是編譯打包階段,因為我們這個項目的 Dockerfile 不是使用的多階段構建,所以需要先用一個任務去將應用編譯打包成二進制文件,然后將這個編譯過后的文件傳遞到下一個任務進行鏡像構建。

我們已經明確了這個階段要做的事情,編寫任務也就簡單了,創建如下所的 Task 任務,首先需要通過定義一個 workspace 把 clone 任務里面的代碼關聯過來:

# 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

這個構建任務也很簡單,只是我們將需要用到的環境變量直接通過 env 注入了,當然直接寫入到 script 中也是可以的,或者直接使用 command 來執行任務都可以,然后構建生成的 app 這個二進制文件保留在代碼根目錄,這樣也就可以通過 workspace 進行共享了。

Docker 鏡像

接下來就是構建並推送 Docker 鏡像了,前面我們介紹過使用 Kaniko、DooD、DinD 3種模式的鏡像構建方式,這里我們直接使用 DinD 這種模式,我們這里要構建的鏡像 Dockerfile 非常簡單:

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

就行直接將編譯好的二進制文件拷貝到鏡像中即可,所以我們這里同樣需要通過 Workspace 去獲取上一個構建任務的制品,當然要使用 DinD 模式構建鏡像,需要用到 sidecar 功能,創建一個如下所示的任務:

# 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 # 構建步驟
      image: docker:stable
      env:
        - name: DOCKER_HOST # 用 TLS 形式通過 TCP 鏈接 sidecar
          value: tcp://localhost:2376
        - name: DOCKER_TLS_VERIFY # 校驗 TLS
          value: "1"
        - name: DOCKER_CERT_PATH # 使用 sidecar 守護進程生成的證書
          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 構建命令
        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服務,實現真正的 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: {}

這個任務的重點還是要去聲明一個 Workspace,當執行任務的時候要使用和前面構建任務同一個 Workspace,這樣就可以獲得上面編譯成的 app 這個二進制文件了。

部署

接下來的部署階段,我們同樣可以參考之前 Jenkins 流水線里面的實現,由於項目中我們包含了 Helm Chart 包,所以直接使用 Helm 來部署即可,要實現 Helm 部署,當然我們首先需要一個包含 helm 命令的鏡像,當然完全可以自己去編寫一個這樣的任務,此外我們還可以直接去 hub.tekton.dev 上面查找 Catalog,因為這上面就有很多比較通用的一些任務了,比如 helm-upgrade-from-source 這個 Task 任務就完全可以滿足我們的需求了:

圖片helm tekton

這個 Catalog 下面也包含完整的使用文檔了,我們可以將該任務直接下載下來根據我們自己的需求做一些定制修改,如下所示:

# 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)

因為我們的 Helm Chart 模板就在代碼倉庫中,所以不需要從 Chart Repo 倉庫中獲取,只需要指定 Chart 路徑即可,其他可配置的參數都通過 params 參數暴露出去了,非常靈活,最后我們還獲取了 Helm 部署的狀態,寫入到了 Results 中,方便后續任務處理。

回滾

最后應用部署完成后可能還需要回滾,因為可能部署的應用有錯誤,當然這個回滾動作最好是我們自己去觸發,但是在某些場景下,比如 helm 部署已經明確失敗了,那么我們當然可以自動回滾了,所以就需要判斷當部署失敗的時候再執行回滾,也就是這個任務並不是一定會發生的,只在某些場景下才會出現,我們可以在流水線中通過使用 WhenExpressions 來實現這個功能,之前版本中是使用 Conditions,不過已經廢棄了。要只在滿足某些條件時運行任務,可以使用 when 字段來保護任務執行,when 字段允許你列出對  WhenExpressions 的一系列引用。

WhenExpressions 由 InputOperator 和 Values 幾部分組成:

  • Input 是 WhenExpressions 的輸入,它可以是一個靜態的輸入或變量(Params 或 Results),如果未提供輸入,則默認為空字符串
  • Operator 是一個運算符,表示 Input 和 Values 之間的關系,有效的運算符包括 innotin
  • Values 是一個字符串數組,必須提供一個非空的 Values 數組,它同樣可以包含靜態值或者變量(Params、Results 或者 Workspaces 綁定)

當在一個 Task 任務中配置了 WhenExpressions,在執行 Task 之前會評估聲明的 WhenExpressions,如果結果為 True,則執行任務,如果為 False,則不會執行該任務。

我們這里創建的回滾任務如下所示:

# 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)

流水線

現在我們的整個工作流任務都已經創建完成了,接下來我們就可以將這些任務全部串聯起來組成一個 Pipeline 流水線了,將上面定義的幾個 Task 引用到 Pipeline 中來,當然還需要聲明 Task 中用到的 resources 或者 workspaces 這些數據:

# pipeline.yaml
apiVersion: tekton.dev/v1beta1
kind: Pipeline
metadata:
  name: pipeline
spec:
  workspaces: # 聲明 workspaces
    - name: go-repo-pvc
  params:
    # 定義代碼倉庫
    - name: git_url
    - name: revision
      type: string
      default: "master"
    # 定義鏡像參數
    - name: image
    - name: registry_url
      type: string
      default: "harbor.k8s.local"
    - name: registry_mirror
      type: string
      default: "https://ot2k4d59.mirror.aliyuncs.com/"
    # 定義 helm charts 參數
    - 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 # 編譯二進制程序
      taskRef:
        name: build
      runAfter: # 測試任務執行之后才執行 build task
        - test
        - clone
      workspaces: # 傳遞 workspaces
        - name: go-repo
          workspace: go-repo-pvc
    - name: docker # 構建並推送 Docker 鏡像
      taskRef:
        name: docker
      runAfter:
        - build
      workspaces: # 傳遞 workspaces
        - name: go-repo
          workspace: go-repo-pvc
      params: # 傳遞參數
        - name: image
          value: $(params.image)
        - name: registry_url
          value: $(params.registry_url)
        - name: registry_mirror
          value: $(params.registry_mirror)
    - name: deploy # 部署應用
      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)

整體流程比較簡單,就是在 Pipeline 需要先聲明使用到的 Workspace、Resource、Params 這些資源,然后將聲明的數據傳遞到 Task 任務中去,需要注意的是最后一個回滾任務,我們需要根據前面的 deploy 任務的結果來判斷是否需要執行該任務,所以這里我們使用了 when 屬性,通過 $(tasks.deploy.results.helm-status) 獲取部署狀態。

執行流水線

現在我們就可以來執行下我們的流水線,看是否符合我們自身的要求,首先我們需要先創建關聯的其他資源對象,比如 Workspace 對應的 PVC、還有 GitLab、Harbor 的認證信息:

# other.yaml
apiVersion: v1
kind: Secret
metadata:
  name: gitlab-auth
  annotations:
    tekton.dev/git-0: http://git.k8s.local
type: kubernetes.io/basic-auth
stringData:
  username: root
  password: admin321

---

apiVersion: v1
kind: Secret
metadata:
  name: harbor-auth
  annotations:
    tekton.dev/docker-0: http://harbor.k8s.local
type: kubernetes.io/basic-auth
stringData:
    username: admin
    password: Harbor12345

---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: tekton-build-sa
secrets:
  - name: harbor-auth
  - name: gitlab-auth

---

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: tekton-clusterrole-binding
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: edit
subjects:
- kind: ServiceAccount
  name: tekton-build-sa
  namespace: default

---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: go-repo-pvc
spec:
  resources:
    requests:
      storage: 1Gi
  volumeMode: Filesystem
  storageClassName: nfs-storage  # 使用 StorageClass 自動生成 PV
  accessModes:
    - ReadWriteOnce

這些關聯的資源對象創建完成后,還需要為上面的 ServiceAccount 綁定一個權限,因為在 Helm 容器中我們要去操作一些集群資源,必然需要先做權限聲明,這里我們可以將 tekton-build-sa 綁定到 edit 這個 ClusterRole 上去。

我們接下來就可以創建一個 PipelineRun 資源對象來觸發我們的流水線構建了:

# pipelinerun.yaml
apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
  name: pipelinerun
spec:
  serviceAccountName: tekton-build-sa
  pipelineRef:
    name: pipeline
  workspaces:
    - name: go-repo-pvc
      persistentVolumeClaim:
        claimName: go-repo-pvc
  params:
    - name: git_url
      value: http://git.k8s.local/course/devops-demo.git
    - name: image
      value: "harbor.k8s.local/course/devops-demo:v0.1.0"
    - name: charts_dir
      value: "./helm"
    - name: release_name
      value: devops-demo
    - name: release_namespace
      value: "kube-ops"
    - name: overwrite_values
      value: "image.repository=harbor.k8s.local/course/devops-demo,image.tag=v0.1.0"
    - name: values_file
      value: "my-values.yaml"

直接創建上面的資源對象就可以執行我們的 Pipeline 流水線了:

$ kubectl apply -f pipelinerun.yaml
$ tkn pr describe pipelinerun
Name:              pipelinerun
Namespace:         default
Pipeline Ref:      pipeline
Service Account:   tekton-build-sa
Timeout:           1h0m0s
Labels:
 tekton.dev/pipeline=pipeline

🌡️  Status

STARTED        DURATION   STATUS
1 minute ago   1 minute   Succeeded(Completed)

📦 Resources

 No resources

⚓ Params

 NAME                  VALUE
 ∙ git_url             http://git.k8s.local/course/devops-demo.git
 ∙ image               harbor.k8s.local/course/devops-demo:v0.1.0
 ∙ charts_dir          ./helm
 ∙ release_name        devops-demo
 ∙ release_namespace   kube-ops
 ∙ overwrite_values    image.repository=harbor.k8s.local/course/devops-demo,image.tag=v0.1.0
 ∙ values_file         my-values.yaml

📝 Results

 No results

📂 Workspaces

 NAME            SUB PATH   WORKSPACE BINDING
 ∙ go-repo-pvc   ---        PersistentVolumeClaim (claimName=go-repo-pvc)

🗂  Taskruns

 NAME                         TASK NAME   STARTED          DURATION     STATUS
 ∙ pipelinerun-deploy-zpmg9   deploy      33 seconds ago   15 seconds   Succeeded
 ∙ pipelinerun-docker-rkhxq   docker      1 minute ago     45 seconds   Succeeded
 ∙ pipelinerun-build-gnnsp    build       1 minute ago     15 seconds   Succeeded
 ∙ pipelinerun-test-z5ppb     test        1 minute ago     5 seconds    Succeeded
 ∙ pipelinerun-clone-xdrjh    clone       1 minute ago     8 seconds    Succeeded

# 部署成功了
$ curl devops-demo.k8s.local
{"msg":"Hello DevOps On Kubernetes"}

在 Dashboard 上也可以看到可以流水線可以正常執行,由於部署成功了,所以 rollback 回滾的任務也就被忽略了:

圖片pipeline deployed

觸發器

整個流水線已經成功執行了,接下來最后一步就是將 Gitlab 和 Tekton 進行對接,也就是通過 Tekton Trigger 來自動觸發構建。關於 Tekton Trigger 的使用前面我們已經詳細講解過了,細節就不過多討論,當然在使用之前也一定要先部署 interceptors

kubectl apply -f https://storage.googleapis.com/tekton-releases/triggers/previous/v0.14.2/interceptors.yaml

首先添加一個用於 Gitlab Webhook 訪問的 Secret Token,同樣要將這個 Secret 關聯到上面使用的 ServiceAccount 上面去,然后繼續添加對應的 RBAC 權限:

# other.yaml
# ......
apiVersion: v1
kind: Secret
metadata:
  name: gitlab-secret
type: Opaque
stringData:
  secretToken: "1234567"

---

apiVersion: v1
kind: ServiceAccount
metadata:
  name: tekton-build-sa
secrets:
  - name: harbor-auth
  - name: gitlab-auth
  - name: gitlab-secret

---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: tekton-triggers-gitlab-minimal
rules:
# EventListeners need to be able to fetch all namespaced resources
- apiGroups: ["triggers.tekton.dev"]
  resources: ["eventlisteners", "triggerbindings", "triggertemplates", "triggers"]
  verbs: ["get", "list", "watch"]
- apiGroups: [""]
# configmaps is needed for updating logging config
  resources: ["configmaps"]
  verbs: ["get", "list", "watch"]
# Permissions to create resources in associated TriggerTemplates
- apiGroups: ["tekton.dev"]
  resources: ["pipelineruns", "pipelineresources", "taskruns"]
  verbs: ["create"]
- apiGroups: [""]
  resources: ["serviceaccounts"]
  verbs: ["impersonate"]
- apiGroups: ["policy"]
  resources: ["podsecuritypolicies"]
  resourceNames: ["tekton-triggers"]
  verbs: ["use"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: tekton-triggers-gitlab-binding
subjects:
- kind: ServiceAccount
  name: tekton-build-sa
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: tekton-triggers-gitlab-minimal
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: tekton-triggers-gitlab-clusterrole
rules:
  # EventListeners need to be able to fetch any clustertriggerbindings
- apiGroups: ["triggers.tekton.dev"]
  resources: ["clustertriggerbindings", "clusterinterceptors"]
  verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: tekton-triggers-gitlab-clusterbinding
subjects:
- kind: ServiceAccount
  name: tekton-build-sa
  namespace: default
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: tekton-triggers-gitlab-clusterrole

接着就可以來創建 EventListener 資源對象了,用來接收 Gitlab 的 Push Event 事件,如下所示:

# gitlab-listener.yaml
apiVersion: triggers.tekton.dev/v1alpha1
kind: EventListener
metadata:
  name: gitlab-listener  # 該事件監聽器會創建一個名為el-gitlab-listener的Service對象
spec:
  serviceAccountName: tekton-build-sa
  triggers:
  - name: gitlab-push-events-trigger
    interceptors:
    - ref:
        name: gitlab
      params:
      - name: secretRef  # 引用 gitlab-secret 的 Secret 對象中的 secretToken 的值
        value:
          secretName: gitlab-secret
          secretKey: secretToken
      - name: eventTypes
        value:
          - Push Hook # 只接收 GitLab Push 事件
    bindings:  # 定義TriggerBinding,配置參數
    - name: gitrevision
      value: $(body.checkout_sha)
    - name: gitrepositoryurl
      value: $(body.repository.git_http_url)
    template:
      ref: gitlab-template

上面我們通過 TriggerBinding 定義了兩個參數 gitrevisiongitrepositoryurl,這兩個參數的值可以通過 Gitlab 發送過來的 POST 請求中獲取到數據,然后我們就可以將這兩個參數傳遞到  TriggerTemplate 對象中去,這里的模板其實也就是將上面我們定義的 PipelineRun 對象模板化而已,主要是替換 git_url 和鏡像 TAG 這兩個參數,如下所示:

# gitlab-template.yaml
apiVersion: triggers.tekton.dev/v1alpha1
kind: TriggerTemplate
metadata:
  name: gitlab-template
spec:
  params: # 定義參數,和 TriggerBinding 中的保持一致
    - name: gitrevision
    - name: gitrepositoryurl
  resourcetemplates: # 定義資源模板
    - apiVersion: tekton.dev/v1beta1
      kind: PipelineRun # 定義 pipeline 模板
      metadata:
        generateName: gitlab-run- # TaskRun 名稱前綴
      spec:
        serviceAccountName: tekton-build-sa
        pipelineRef:
          name: pipeline
        workspaces:
          - name: go-repo-pvc
            persistentVolumeClaim:
              claimName: go-repo-pvc
        params:
          - name: git_url
            value: $(tt.params.gitrepositoryurl)
          - name: image
            value: "harbor.k8s.local/course/devops-demo:$(tt.params.gitrevision)"
          - name: charts_dir
            value: "./helm"
          - name: release_name
            value: devops-demo
          - name: release_namespace
            value: "kube-ops"
          - name: overwrite_values
            value: "image.repository=harbor.k8s.local/course/devops-demo,image.tag=$(tt.params.gitrevision)"
          - name: values_file
            value: "my-values.yaml"

直接創建上面新建的幾個資源對象即可,這會創建一個 eventlistern 服務用來接收 Webhook 請求:

$ kubectl get eventlistener
NAME              ADDRESS                                                    AVAILABLE   REASON                     READY   REASON
gitlab-listener   http://el-gitlab-listener.default.svc.cluster.local:8080   True        MinimumReplicasAvailable   True

所以一定還要記得在 Gitlab 倉庫中配置上 Webhook:

圖片gitlab webhook

這樣我們整個觸發器和監聽器就配置好了,接下來我們去修改下我們的項目代碼,然后提交代碼,正常提交過后就會在集群中創建一個 PipelinRun 對象用來執行我們的流水線了:

$ kubectl get pipelinerun
NAME                       SUCCEEDED   REASON      STARTTIME   COMPLETIONTIME
gitlab-run-kx6zr           True        Completed   2m14s       50s
$ curl devops-demo.k8s.local
{"msg":"Hello Tekton"}

可以看到流水線執行成功后,應用已經成功部署了我們新提交的代碼,到這里我們就完成了使用 Tekton 來重構項目的流水線。

 

K8S 進階訓練營


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM