使用 Tekton 重构自动化流水线

云计算 自动化
在 Tekton 中我们就可以将这些阶段直接转换成 Task 任务,Clone 代码在 Tekton 中不需要我们主动定义一个任务,只需要在执行的任务上面指定一个输入的代码资源即可

[[407592]]

前面我们讲解了使用 Jenkins 流水线来实现 Kubernetes 应用的 CI/CD,现在我们来将这个流水线迁移到 Tekton 上面来,其实整体思路都是一样的,就是把要整个工作流划分成不同的任务来执行,前面工作流的阶段划分了以下几个阶段:Clone 代码 -> 单元测试 -> Golang 编译打包 -> Docker 镜像构建/推送 -> Kubectl 部署服务。

在 Tekton 中我们就可以将这些阶段直接转换成 Task 任务,Clone 代码在 Tekton 中不需要我们主动定义一个任务,只需要在执行的任务上面指定一个输入的代码资源即可。下面我们就来将上面的工作流一步一步来转换成 Tekton 流水线,代码仓库同样还是 http://git.k8s.local/course/devops-demo.git。

Clone 代码

虽然我们可以不用单独定义一个 Clone 代码的任务,直接使用 git 类型的输入资源即可,由于这里涉及到的任务较多,而且很多时候都需要先 Clone 代码然后再进行操作,所以最好的方式是将代码 Clone 下来过后通过 Workspace 共享给其他任务,这里我们可以直接使用 Catalog git-clone 来实现这个任务,我们可以根据自己的需求做一些定制,对应的 Task 如下所示:

  1. # task-clone.yaml 
  2. apiVersion: tekton.dev/v1beta1 
  3. kind: Task 
  4. metadata: 
  5.   name: git-clone 
  6. spec: 
  7.   workspaces: 
  8.     - nameoutput 
  9.       description: The git repo will be cloned onto the volume backing this Workspace. 
  10.     - name: basic-auth 
  11.       optional: true 
  12.       description: | 
  13.         A Workspace containing a .gitconfig and .git-credentials file. These 
  14.         will be copied to the user's home before any git commands are run. Any 
  15.         other files in this Workspace are ignored. It is strongly recommended 
  16.         to use ssh-directory over basic-auth whenever possible and to bind a 
  17.         Secret to this Workspace over other volume types. 
  18.   params: 
  19.     - name: url 
  20.       description: Repository URL to clone from
  21.       type: string 
  22.     - name: revision 
  23.       description: Revision to checkout. (branch, tag, sha, ref, etc...) 
  24.       type: string 
  25.       default"" 
  26.     - name: refspec 
  27.       description: Refspec to fetch before checking out revision. 
  28.       default"" 
  29.     - name: submodules 
  30.       description: Initialize and fetch git submodules. 
  31.       type: string 
  32.       default"true" 
  33.     - name: depth 
  34.       description: Perform a shallow clone, fetching only the most recent N commits. 
  35.       type: string 
  36.       default"1" 
  37.     - name: sslVerify 
  38.       description: Set the `http.sslVerify` global git config. Setting this to `falseis not advised unless you are sure that you trust your git remote. 
  39.       type: string 
  40.       default"true" 
  41.     - name: subdirectory 
  42.       description: Subdirectory inside the `output` Workspace to clone the repo into
  43.       type: string 
  44.       default"" 
  45.     - name: sparseCheckoutDirectories 
  46.       description: Define the directory patterns to match or exclude when performing a sparse checkout. 
  47.       type: string 
  48.       default"" 
  49.     - name: deleteExisting 
  50.       description: Clean out the contents of the destination directory if it already exists before cloning. 
  51.       type: string 
  52.       default"true" 
  53.     - name: verbose 
  54.       description: Log the commands that are executed during `git-clone`'s operation. 
  55.       type: string 
  56.       default"true" 
  57.     - name: gitInitImage 
  58.       description: The image providing the git-init binary that this Task runs. 
  59.       type: string 
  60.       default"cnych/tekton-git-init:v0.24.1" 
  61.     - name: userHome 
  62.       description: | 
  63.         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 
  64.         the gitInitImage param with an image containing custom user configuration. 
  65.       type: string 
  66.       default"/root" 
  67.   results: 
  68.     - namecommit 
  69.       description: The precise commit SHA that was fetched by this Task. 
  70.     - name: url 
  71.       description: The precise URL that was fetched by this Task. 
  72.   steps: 
  73.     - name: clone 
  74.       image: "$(params.gitInitImage)" 
  75.       env: 
  76.       - name: HOME 
  77.         value: "$(params.userHome)" 
  78.       - name: PARAM_URL 
  79.         value: $(params.url) 
  80.       - name: PARAM_REVISION 
  81.         value: $(params.revision) 
  82.       - name: PARAM_REFSPEC 
  83.         value: $(params.refspec) 
  84.       - name: PARAM_SUBMODULES 
  85.         value: $(params.submodules) 
  86.       - name: PARAM_DEPTH 
  87.         value: $(params.depth) 
  88.       - name: PARAM_SSL_VERIFY 
  89.         value: $(params.sslVerify) 
  90.       - name: PARAM_SUBDIRECTORY 
  91.         value: $(params.subdirectory) 
  92.       - name: PARAM_DELETE_EXISTING 
  93.         value: $(params.deleteExisting) 
  94.       - name: PARAM_VERBOSE 
  95.         value: $(params.verbose) 
  96.       - name: PARAM_SPARSE_CHECKOUT_DIRECTORIES 
  97.         value: $(params.sparseCheckoutDirectories) 
  98.       - name: PARAM_USER_HOME 
  99.         value: $(params.userHome) 
  100.       - name: WORKSPACE_OUTPUT_PATH 
  101.         value: $(workspaces.output.path) 
  102.       - name: WORKSPACE_BASIC_AUTH_DIRECTORY_BOUND 
  103.         value: $(workspaces.basic-auth.bound) 
  104.       - name: WORKSPACE_BASIC_AUTH_DIRECTORY_PATH 
  105.         value: $(workspaces.basic-auth.path) 
  106.       script: | 
  107.         #!/usr/bin/env sh 
  108.         set -eu 
  109.  
  110.         if [ "${PARAM_VERBOSE}" = "true" ] ; then 
  111.           set -x 
  112.         fi 
  113.  
  114.         if [ "${WORKSPACE_BASIC_AUTH_DIRECTORY_BOUND}" = "true" ] ; then 
  115.           cp "${WORKSPACE_BASIC_AUTH_DIRECTORY_PATH}/.git-credentials" "${PARAM_USER_HOME}/.git-credentials" 
  116.           cp "${WORKSPACE_BASIC_AUTH_DIRECTORY_PATH}/.gitconfig" "${PARAM_USER_HOME}/.gitconfig" 
  117.           chmod 400 "${PARAM_USER_HOME}/.git-credentials" 
  118.           chmod 400 "${PARAM_USER_HOME}/.gitconfig" 
  119.         fi 
  120.  
  121.         CHECKOUT_DIR="${WORKSPACE_OUTPUT_PATH}/${PARAM_SUBDIRECTORY}" 
  122.  
  123.         cleandir() { 
  124.           # Delete any existing contents of the repo directory if it exists. 
  125.           # 
  126.           # We don't just "rm -rf ${CHECKOUT_DIR}" because ${CHECKOUT_DIR} might be "/" 
  127.           # or the root of a mounted volume. 
  128.           if [ -d "${CHECKOUT_DIR}" ] ; then 
  129.             # Delete non-hidden files and directories 
  130.             rm -rf "${CHECKOUT_DIR:?}"/* 
  131.             # Delete files and directories starting with . but excluding .. 
  132.             rm -rf "${CHECKOUT_DIR}"/.[!.]* 
  133.             # Delete files and directories starting with .. plus any other character 
  134.             rm -rf "${CHECKOUT_DIR}"/..?* 
  135.           fi 
  136.         } 
  137.  
  138.         if [ "${PARAM_DELETE_EXISTING}" = "true" ] ; then 
  139.           cleandir 
  140.         fi 
  141.  
  142.         /ko-app/git-init \ 
  143.           -url="${PARAM_URL}" \ 
  144.           -revision="${PARAM_REVISION}" \ 
  145.           -refspec="${PARAM_REFSPEC}" \ 
  146.           -path="${CHECKOUT_DIR}" \ 
  147.           -sslVerify="${PARAM_SSL_VERIFY}" \ 
  148.           -submodules="${PARAM_SUBMODULES}" \ 
  149.           -depth="${PARAM_DEPTH}" \ 
  150.           -sparseCheckoutDirectories="${PARAM_SPARSE_CHECKOUT_DIRECTORIES}" 
  151.         cd "${CHECKOUT_DIR}" 
  152.         RESULT_SHA="$(git rev-parse HEAD)" 
  153.         EXIT_CODE="$?" 
  154.         if [ "${EXIT_CODE}" != 0 ] ; then 
  155.           exit "${EXIT_CODE}" 
  156.         fi 
  157.         printf "%s" "${RESULT_SHA}" > "$(results.commit.path)" 
  158.         printf "%s" "${PARAM_URL}" > "$(results.url.path)" 

一般来说我们只需要提供 output 这个个用于持久化代码的 workspace,然后还包括 url 和 revision 这两个参数,其他使用默认的即可。

单元测试

单元测试阶段比较简单,正常来说也是只是单纯执行一个测试命令即可,我们这里没有真正执行单元测试,所以简单测试下即可,编写一个如下所示的 Task:

  1. # task-test.yaml 
  2. apiVersion: tekton.dev/v1beta1 
  3. kind: Task 
  4. metadata: 
  5.   name: test 
  6. spec: 
  7.   steps: 
  8.     - name: test 
  9.       image: golang:1.14-alpine 
  10.       command: ['echo'
  11.       args: ['this is a test task'

编译打包

然后第二个阶段是编译打包阶段,因为我们这个项目的 Dockerfile 不是使用的多阶段构建,所以需要先用一个任务去将应用编译打包成二进制文件,然后将这个编译过后的文件传递到下一个任务进行镜像构建。

我们已经明确了这个阶段要做的事情,编写任务也就简单了,创建如下所的 Task 任务,首先需要通过定义一个 workspace 把 clone 任务里面的代码关联过来:

  1. # task-build.yaml 
  2. apiVersion: tekton.dev/v1beta1 
  3. kind: Task 
  4. metadata: 
  5.   name: build 
  6. spec: 
  7.   workspaces: 
  8.     - name: go-repo 
  9.       mountPath: /workspace/repo 
  10.   steps: 
  11.     - name: build 
  12.       image: golang:1.14-alpine 
  13.       workingDir: /workspace/repo 
  14.       script: | 
  15.         go build -v -o app 
  16.       env: 
  17.         - name: GOPROXY 
  18.           value: https://goproxy.cn 
  19.         - name: GOOS 
  20.           value: linux 
  21.         - name: GOARCH 
  22.           value: amd64 

这个构建任务也很简单,只是我们将需要用到的环境变量直接通过 env 注入了,当然直接写入到 script 中也是可以的,或者直接使用 command 来执行任务都可以,然后构建生成的 app 这个二进制文件保留在代码根目录,这样也就可以通过 workspace 进行共享了。

Docker 镜像

接下来就是构建并推送 Docker 镜像了,前面我们介绍过使用 Kaniko、DooD、DinD 3种模式的镜像构建方式,这里我们直接使用 DinD 这种模式,我们这里要构建的镜像 Dockerfile 非常简单:

  1. FROM alpine 
  2. WORKDIR /home 
  3.  
  4. # 修改alpine源为阿里云 
  5. RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories && \ 
  6.   apk update && \ 
  7.   apk upgrade && \ 
  8.   apk add ca-certificates && update-ca-certificates && \ 
  9.   apk add --update tzdata && \ 
  10.   rm -rf /var/cache/apk/* 
  11.  
  12. COPY app /home/ 
  13. ENV TZ=Asia/Shanghai 
  14.  
  15. EXPOSE 8080 
  16.  
  17. ENTRYPOINT ./app 

就行直接将编译好的二进制文件拷贝到镜像中即可,所以我们这里同样需要通过 Workspace 去获取上一个构建任务的制品,当然要使用 DinD 模式构建镜像,需要用到 sidecar 功能,创建一个如下所示的任务:

  1. # task-docker.yaml 
  2. apiVersion: tekton.dev/v1beta1 
  3. kind: Task 
  4. metadata: 
  5.   name: docker 
  6. spec: 
  7.   workspaces: 
  8.     - name: go-repo 
  9.   params: 
  10.     - name: image 
  11.       description: Reference of the image docker will produce. 
  12.     - name: registry_mirror 
  13.       description: Specific the docker registry mirror 
  14.       default"" 
  15.     - name: registry_url 
  16.       description: private docker images registry url 
  17.   steps: 
  18.     - name: docker-build # 构建步骤 
  19.       image: docker:stable 
  20.       env: 
  21.         - name: DOCKER_HOST # 用 TLS 形式通过 TCP 链接 sidecar 
  22.           value: tcp://localhost:2376 
  23.         - name: DOCKER_TLS_VERIFY # 校验 TLS 
  24.           value: "1" 
  25.         - name: DOCKER_CERT_PATH # 使用 sidecar 守护进程生成的证书 
  26.           value: /certs/client 
  27.         - name: DOCKER_PASSWORD 
  28.           valueFrom: 
  29.             secretKeyRef: 
  30.               name: harbor-auth 
  31.               keypassword 
  32.         - name: DOCKER_USERNAME 
  33.           valueFrom: 
  34.             secretKeyRef: 
  35.               name: harbor-auth 
  36.               key: username 
  37.       workingDir: $(workspaces.go-repo.path) 
  38.       script: | # docker 构建命令 
  39.         docker login $(params.registry_url) -u $DOCKER_USERNAME -p $DOCKER_PASSWORD 
  40.         docker build --no-cache -f ./Dockerfile -t $(params.image) . 
  41.         docker push $(params.image) 
  42.       volumeMounts: # 声明挂载证书目录 
  43.         - mountPath: /certs/client 
  44.           name: dind-certs 
  45.   sidecars: # sidecar 模式,提供 docker daemon服务,实现真正的 DinD 模式 
  46.     - image: docker:dind 
  47.       name: server 
  48.       args: 
  49.         - --storage-driver=vfs 
  50.         - --userland-proxy=false 
  51.         - --debug 
  52.         - --insecure-registry=$(params.registry_url) 
  53.         - --registry-mirror=$(params.registry_mirror) 
  54.       securityContext: 
  55.         privileged: true 
  56.       env: 
  57.         - name: DOCKER_TLS_CERTDIR # 将生成的证书写入与客户端共享的路径 
  58.           value: /certs 
  59.       volumeMounts: 
  60.         - mountPath: /certs/client 
  61.           name: dind-certs 
  62.       readinessProbe: # 等待 dind daemon 生成它与客户端共享的证书 
  63.         periodSeconds: 1 
  64.         exec
  65.           command: ["ls""/certs/client/ca.pem"
  66.   volumes: # 使用 emptyDir 的形式即可 
  67.     - name: dind-certs 
  68.       emptyDir: {} 

这个任务的重点还是要去声明一个 Workspace,当执行任务的时候要使用和前面构建任务同一个 Workspace,这样就可以获得上面编译成的 app 这个二进制文件了。

部署

接下来的部署阶段,我们同样可以参考之前 Jenkins 流水线里面的实现,由于项目中我们包含了 Helm Chart 包,所以直接使用 Helm 来部署即可,要实现 Helm 部署,当然我们首先需要一个包含 helm 命令的镜像,当然完全可以自己去编写一个这样的任务,此外我们还可以直接去 hub.tekton.dev 上面查找 Catalog,因为这上面就有很多比较通用的一些任务了,比如 helm-upgrade-from-source 这个 Task 任务就完全可以满足我们的需求了:

 

helm tekton

这个 Catalog 下面也包含完整的使用文档了,我们可以将该任务直接下载下来根据我们自己的需求做一些定制修改,如下所示:

  1. # task-deploy.yaml 
  2. apiVersion: tekton.dev/v1beta1 
  3. kind: Task 
  4. metadata: 
  5.   name: deploy 
  6. spec: 
  7.   params: 
  8.     - name: charts_dir 
  9.       description: The directory in source that contains the helm chart 
  10.     - name: release_name 
  11.       description: The helm release name 
  12.     - name: release_namespace 
  13.       description: The helm release namespace 
  14.       default"" 
  15.     - name: overwrite_values 
  16.       description: "Specify the values you want to overwrite, comma separated: autoscaling.enabled=true,replicas=1" 
  17.       default"" 
  18.     - name: values_file 
  19.       description: "The values file to be used" 
  20.       default"values.yaml" 
  21.     - name: helm_image 
  22.       description: "helm image to be used" 
  23.       default"docker.io/lachlanevenson/k8s-helm:v3.3.4@sha256:e1816be207efbd342cba9d3d32202e237e3de20af350617f8507dc033ea66803" #tag: v3.3.4 
  24.   workspaces: 
  25.     - name: source 
  26.   results: 
  27.     - name: helm-status 
  28.       description: Helm deploy status 
  29.   steps: 
  30.     - name: upgrade 
  31.       image: $(params.helm_image) 
  32.       workingDir: /workspace/source 
  33.       script: | 
  34.         echo current installed helm releases 
  35.         helm list --namespace "$(params.release_namespace)" 
  36.  
  37.         echo installing helm chart... 
  38.         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)" 
  39.  
  40.         status=`helm status $(params.release_name) --namespace "$(params.release_namespace)" | awk '/STATUS/ {print $2}'` 
  41.         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 由 Input、Operator 和 Values 几部分组成:

  • Input 是 WhenExpressions 的输入,它可以是一个静态的输入或变量(Params 或 Results),如果未提供输入,则默认为空字符串
  • Operator 是一个运算符,表示 Input 和 Values 之间的关系,有效的运算符包括 in、notin
  • Values 是一个字符串数组,必须提供一个非空的 Values 数组,它同样可以包含静态值或者变量(Params、Results 或者 Workspaces 绑定)

当在一个 Task 任务中配置了 WhenExpressions,在执行 Task 之前会评估声明的 WhenExpressions,如果结果为 True,则执行任务,如果为 False,则不会执行该任务。

我们这里创建的回滚任务如下所示:

  1. # task-rollback.yaml 
  2. apiVersion: tekton.dev/v1beta1 
  3. kind: Task 
  4. metadata: 
  5.   namerollback 
  6. spec: 
  7.   params: 
  8.     - name: release_name 
  9.       description: The helm release name 
  10.     - name: release_namespace 
  11.       description: The helm release namespace 
  12.       default"" 
  13.     - name: helm_image 
  14.       description: "helm image to be used" 
  15.       default"docker.io/lachlanevenson/k8s-helm:v3.3.4@sha256:e1816be207efbd342cba9d3d32202e237e3de20af350617f8507dc033ea66803" #tag: v3.3.4 
  16.   steps: 
  17.     - namerollback 
  18.       image: $(params.helm_image) 
  19.       script: | 
  20.         echo rollback current installed helm releases 
  21.         helm rollback $(params.release_name) --namespace $(params.release_namespace) 

流水线

现在我们的整个工作流任务都已经创建完成了,接下来我们就可以将这些任务全部串联起来组成一个 Pipeline 流水线了,将上面定义的几个 Task 引用到 Pipeline 中来,当然还需要声明 Task 中用到的 resources 或者 workspaces 这些数据:

  1. # pipeline.yaml 
  2. apiVersion: tekton.dev/v1beta1 
  3. kind: Pipeline 
  4. metadata: 
  5.   name: pipeline 
  6. spec: 
  7.   workspaces: # 声明 workspaces 
  8.     - name: go-repo-pvc 
  9.   params: 
  10.     # 定义代码仓库 
  11.     - name: git_url 
  12.     - name: revision 
  13.       type: string 
  14.       default"master" 
  15.     # 定义镜像参数 
  16.     - name: image 
  17.     - name: registry_url 
  18.       type: string 
  19.       default"harbor.k8s.local" 
  20.     - name: registry_mirror 
  21.       type: string 
  22.       default"https://ot2k4d59.mirror.aliyuncs.com/" 
  23.     # 定义 helm charts 参数 
  24.     - name: charts_dir 
  25.     - name: release_name 
  26.     - name: release_namespace 
  27.       default"default" 
  28.     - name: overwrite_values 
  29.       default"" 
  30.     - name: values_file 
  31.       default"values.yaml" 
  32.   tasks: # 添加task到流水线中 
  33.     - name: clone 
  34.       taskRef: 
  35.         name: git-clone 
  36.       workspaces: 
  37.         - nameoutput 
  38.           workspace: go-repo-pvc 
  39.       params: 
  40.         - name: url 
  41.           value: $(params.git_url) 
  42.         - name: revision 
  43.           value: $(params.revision) 
  44.     - name: test 
  45.       taskRef: 
  46.         name: test 
  47.     - name: build # 编译二进制程序 
  48.       taskRef: 
  49.         name: build 
  50.       runAfter: # 测试任务执行之后才执行 build task 
  51.         - test 
  52.         - clone 
  53.       workspaces: # 传递 workspaces 
  54.         - name: go-repo 
  55.           workspace: go-repo-pvc 
  56.     - name: docker # 构建并推送 Docker 镜像 
  57.       taskRef: 
  58.         name: docker 
  59.       runAfter: 
  60.         - build 
  61.       workspaces: # 传递 workspaces 
  62.         - name: go-repo 
  63.           workspace: go-repo-pvc 
  64.       params: # 传递参数 
  65.         - name: image 
  66.           value: $(params.image) 
  67.         - name: registry_url 
  68.           value: $(params.registry_url) 
  69.         - name: registry_mirror 
  70.           value: $(params.registry_mirror) 
  71.     - name: deploy # 部署应用 
  72.       taskRef: 
  73.         name: deploy 
  74.       runAfter: 
  75.         - docker 
  76.       workspaces: 
  77.         - name: source 
  78.           workspace: go-repo-pvc 
  79.       params: 
  80.         - name: charts_dir 
  81.           value: $(params.charts_dir) 
  82.         - name: release_name 
  83.           value: $(params.release_name) 
  84.         - name: release_namespace 
  85.           value: $(params.release_namespace) 
  86.         - name: overwrite_values 
  87.           value: $(params.overwrite_values) 
  88.         - name: values_file 
  89.           value: $(params.values_file) 
  90.     - namerollback # 回滚 
  91.       taskRef: 
  92.         namerollback 
  93.       when
  94.         - input: "$(tasks.deploy.results.helm-status)" 
  95.           operator: in 
  96.           values: ["failed"
  97.       params: 
  98.         - name: release_name 
  99.           value: $(params.release_name) 
  100.         - name: release_namespace 
  101.           value: $(params.release_namespace) 

整体流程比较简单,就是在 Pipeline 需要先声明使用到的 Workspace、Resource、Params 这些资源,然后将声明的数据传递到 Task 任务中去,需要注意的是最后一个回滚任务,我们需要根据前面的 deploy 任务的结果来判断是否需要执行该任务,所以这里我们使用了 when 属性,通过 $(tasks.deploy.results.helm-status) 获取部署状态。

执行流水线

现在我们就可以来执行下我们的流水线,看是否符合我们自身的要求,首先我们需要先创建关联的其他资源对象,比如 Workspace 对应的 PVC、还有 GitLab、Harbor 的认证信息:

  1. # other.yaml 
  2. apiVersion: v1 
  3. kind: Secret 
  4. metadata: 
  5.   name: gitlab-auth 
  6.   annotations: 
  7.     tekton.dev/git-0: http://git.k8s.local 
  8. type: kubernetes.io/basic-auth 
  9. stringData: 
  10.   username: root 
  11.   password: admin321 
  12.  
  13. --- 
  14.  
  15. apiVersion: v1 
  16. kind: Secret 
  17. metadata: 
  18.   name: harbor-auth 
  19.   annotations: 
  20.     tekton.dev/docker-0: http://harbor.k8s.local 
  21. type: kubernetes.io/basic-auth 
  22. stringData: 
  23.     username: admin 
  24.     password: Harbor12345 
  25.  
  26. --- 
  27. apiVersion: v1 
  28. kind: ServiceAccount 
  29. metadata: 
  30.   name: tekton-build-sa 
  31. secrets: 
  32.   - name: harbor-auth 
  33.   - name: gitlab-auth 
  34.  
  35. --- 
  36.  
  37. apiVersion: rbac.authorization.k8s.io/v1 
  38. kind: ClusterRoleBinding 
  39. metadata: 
  40.   name: tekton-clusterrole-binding 
  41. roleRef: 
  42.   apiGroup: rbac.authorization.k8s.io 
  43.   kind: ClusterRole 
  44.   name: edit 
  45. subjects: 
  46. - kind: ServiceAccount 
  47.   name: tekton-build-sa 
  48.   namespace: default 
  49.  
  50. --- 
  51. apiVersion: v1 
  52. kind: PersistentVolumeClaim 
  53. metadata: 
  54.   name: go-repo-pvc 
  55. spec: 
  56.   resources: 
  57.     requests: 
  58.       storage: 1Gi 
  59.   volumeMode: Filesystem 
  60.   storageClassName: nfs-storage  # 使用 StorageClass 自动生成 PV 
  61.   accessModes: 
  62.     - ReadWriteOnce 

这些关联的资源对象创建完成后,还需要为上面的 ServiceAccount 绑定一个权限,因为在 Helm 容器中我们要去操作一些集群资源,必然需要先做权限声明,这里我们可以将 tekton-build-sa 绑定到 edit 这个 ClusterRole 上去。

我们接下来就可以创建一个 PipelineRun 资源对象来触发我们的流水线构建了:

  1. # pipelinerun.yaml 
  2. apiVersion: tekton.dev/v1beta1 
  3. kind: PipelineRun 
  4. metadata: 
  5.   name: pipelinerun 
  6. spec: 
  7.   serviceAccountName: tekton-build-sa 
  8.   pipelineRef: 
  9.     name: pipeline 
  10.   workspaces: 
  11.     - name: go-repo-pvc 
  12.       persistentVolumeClaim: 
  13.         claimName: go-repo-pvc 
  14.   params: 
  15.     - name: git_url 
  16.       value: http://git.k8s.local/course/devops-demo.git 
  17.     - name: image 
  18.       value: "harbor.k8s.local/course/devops-demo:v0.1.0" 
  19.     - name: charts_dir 
  20.       value: "./helm" 
  21.     - name: release_name 
  22.       value: devops-demo 
  23.     - name: release_namespace 
  24.       value: "kube-ops" 
  25.     - name: overwrite_values 
  26.       value: "image.repository=harbor.k8s.local/course/devops-demo,image.tag=v0.1.0" 
  27.     - name: values_file 
  28.       value: "my-values.yaml" 

直接创建上面的资源对象就可以执行我们的 Pipeline 流水线了:

  1. $ kubectl apply -f pipelinerun.yaml 
  2. $ tkn pr describe pipelinerun 
  3. Name:              pipelinerun 
  4. Namespace:         default 
  5. Pipeline Ref:      pipeline 
  6. Service Account:   tekton-build-sa 
  7. Timeout:           1h0m0s 
  8. Labels: 
  9.  tekton.dev/pipeline=pipeline 
  10.  
  11. 🌡️  Status 
  12.  
  13. STARTED        DURATION   STATUS 
  14. minute ago   1 minute   Succeeded(Completed) 
  15.  
  16. 📦 Resources 
  17.  
  18.  No resources 
  19.  
  20. ⚓ Params 
  21.  
  22.  NAME                  VALUE 
  23.  ∙ git_url             http://git.k8s.local/course/devops-demo.git 
  24.  ∙ image               harbor.k8s.local/course/devops-demo:v0.1.0 
  25.  ∙ charts_dir          ./helm 
  26.  ∙ release_name        devops-demo 
  27.  ∙ release_namespace   kube-ops 
  28.  ∙ overwrite_values    image.repository=harbor.k8s.local/course/devops-demo,image.tag=v0.1.0 
  29.  ∙ values_file         my-values.yaml 
  30.  
  31. 📝 Results 
  32.  
  33.  No results 
  34.  
  35. 📂 Workspaces 
  36.  
  37.  NAME            SUB PATH   WORKSPACE BINDING 
  38.  ∙ go-repo-pvc   ---        PersistentVolumeClaim (claimName=go-repo-pvc) 
  39.  
  40. 🗂  Taskruns 
  41.  
  42.  NAME                         TASK NAME   STARTED          DURATION     STATUS 
  43.  ∙ pipelinerun-deploy-zpmg9   deploy      33 seconds ago   15 seconds   Succeeded 
  44.  ∙ pipelinerun-docker-rkhxq   docker      1 minute ago     45 seconds   Succeeded 
  45.  ∙ pipelinerun-build-gnnsp    build       1 minute ago     15 seconds   Succeeded 
  46.  ∙ pipelinerun-test-z5ppb     test        1 minute ago     5 seconds    Succeeded 
  47.  ∙ pipelinerun-clone-xdrjh    clone       1 minute ago     8 seconds    Succeeded 
  48.  
  49. # 部署成功了 
  50. $ curl devops-demo.k8s.local 
  51. {"msg":"Hello DevOps On Kubernetes"

在 Dashboard 上也可以看到可以流水线可以正常执行,由于部署成功了,所以 rollback 回滚的任务也就被忽略了:

 

pipeline deployed

触发器

整个流水线已经成功执行了,接下来最后一步就是将 Gitlab 和 Tekton 进行对接,也就是通过 Tekton Trigger 来自动触发构建。关于 Tekton Trigger 的使用前面我们已经详细讲解过了,细节就不过多讨论,当然在使用之前也一定要先部署 interceptors:

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

首先添加一个用于 Gitlab Webhook 访问的 Secret Token,同样要将这个 Secret 关联到上面使用的 ServiceAccount 上面去,然后继续添加对应的 RBAC 权限:

  1. # other.yaml 
  2. # ...... 
  3. apiVersion: v1 
  4. kind: Secret 
  5. metadata: 
  6.   name: gitlab-secret 
  7. type: Opaque 
  8. stringData: 
  9.   secretToken: "1234567" 
  10.  
  11. --- 
  12.  
  13. apiVersion: v1 
  14. kind: ServiceAccount 
  15. metadata: 
  16.   name: tekton-build-sa 
  17. secrets: 
  18.   - name: harbor-auth 
  19.   - name: gitlab-auth 
  20.   - name: gitlab-secret 
  21.  
  22. --- 
  23. apiVersion: rbac.authorization.k8s.io/v1 
  24. kind: Role 
  25. metadata: 
  26.   name: tekton-triggers-gitlab-minimal 
  27. rules: 
  28. # EventListeners need to be able to fetch all namespaced resources 
  29. - apiGroups: ["triggers.tekton.dev"
  30.   resources: ["eventlisteners""triggerbindings""triggertemplates""triggers"
  31.   verbs: ["get""list""watch"
  32. - apiGroups: [""
  33. # configmaps is needed for updating logging config 
  34.   resources: ["configmaps"
  35.   verbs: ["get""list""watch"
  36. # Permissions to create resources in associated TriggerTemplates 
  37. - apiGroups: ["tekton.dev"
  38.   resources: ["pipelineruns""pipelineresources""taskruns"
  39.   verbs: ["create"
  40. - apiGroups: [""
  41.   resources: ["serviceaccounts"
  42.   verbs: ["impersonate"
  43. - apiGroups: ["policy"
  44.   resources: ["podsecuritypolicies"
  45.   resourceNames: ["tekton-triggers"
  46.   verbs: ["use"
  47. --- 
  48. apiVersion: rbac.authorization.k8s.io/v1 
  49. kind: RoleBinding 
  50. metadata: 
  51.   name: tekton-triggers-gitlab-binding 
  52. subjects: 
  53. - kind: ServiceAccount 
  54.   name: tekton-build-sa 
  55. roleRef: 
  56.   apiGroup: rbac.authorization.k8s.io 
  57.   kind: Role 
  58.   name: tekton-triggers-gitlab-minimal 
  59. --- 
  60. kind: ClusterRole 
  61. apiVersion: rbac.authorization.k8s.io/v1 
  62. metadata: 
  63.   name: tekton-triggers-gitlab-clusterrole 
  64. rules: 
  65.   # EventListeners need to be able to fetch any clustertriggerbindings 
  66. - apiGroups: ["triggers.tekton.dev"
  67.   resources: ["clustertriggerbindings""clusterinterceptors"
  68.   verbs: ["get""list""watch"
  69. --- 
  70. apiVersion: rbac.authorization.k8s.io/v1 
  71. kind: ClusterRoleBinding 
  72. metadata: 
  73.   name: tekton-triggers-gitlab-clusterbinding 
  74. subjects: 
  75. - kind: ServiceAccount 
  76.   name: tekton-build-sa 
  77.   namespace: default 
  78. roleRef: 
  79.   apiGroup: rbac.authorization.k8s.io 
  80.   kind: ClusterRole 
  81.   name: tekton-triggers-gitlab-clusterrole 

接着就可以来创建 EventListener 资源对象了,用来接收 Gitlab 的 Push Event 事件,如下所示:

  1. # gitlab-listener.yaml 
  2. apiVersion: triggers.tekton.dev/v1alpha1 
  3. kind: EventListener 
  4. metadata: 
  5.   name: gitlab-listener  # 该事件监听器会创建一个名为el-gitlab-listener的Service对象 
  6. spec: 
  7.   serviceAccountName: tekton-build-sa 
  8.   triggers: 
  9.   - name: gitlab-push-events-trigger 
  10.     interceptors: 
  11.     - ref: 
  12.         name: gitlab 
  13.       params: 
  14.       - name: secretRef  # 引用 gitlab-secret 的 Secret 对象中的 secretToken 的值 
  15.         value: 
  16.           secretName: gitlab-secret 
  17.           secretKey: secretToken 
  18.       - name: eventTypes 
  19.         value: 
  20.           - Push Hook # 只接收 GitLab Push 事件 
  21.     bindings:  # 定义TriggerBinding,配置参数 
  22.     - name: gitrevision 
  23.       value: $(body.checkout_sha) 
  24.     - name: gitrepositoryurl 
  25.       value: $(body.repository.git_http_url) 
  26.     template: 
  27.       ref: gitlab-template 

上面我们通过 TriggerBinding 定义了两个参数 gitrevision、gitrepositoryurl,这两个参数的值可以通过 Gitlab 发送过来的 POST 请求中获取到数据,然后我们就可以将这两个参数传递到 TriggerTemplate 对象中去,这里的模板其实也就是将上面我们定义的 PipelineRun 对象模板化而已,主要是替换 git_url 和镜像 TAG 这两个参数,如下所示:

  1. # gitlab-template.yaml 
  2. apiVersion: triggers.tekton.dev/v1alpha1 
  3. kind: TriggerTemplate 
  4. metadata: 
  5.   name: gitlab-template 
  6. spec: 
  7.   params: # 定义参数,和 TriggerBinding 中的保持一致 
  8.     - name: gitrevision 
  9.     - name: gitrepositoryurl 
  10.   resourcetemplates: # 定义资源模板 
  11.     - apiVersion: tekton.dev/v1beta1 
  12.       kind: PipelineRun # 定义 pipeline 模板 
  13.       metadata: 
  14.         generateName: gitlab-run- # TaskRun 名称前缀 
  15.       spec: 
  16.         serviceAccountName: tekton-build-sa 
  17.         pipelineRef: 
  18.           name: pipeline 
  19.         workspaces: 
  20.           - name: go-repo-pvc 
  21.             persistentVolumeClaim: 
  22.               claimName: go-repo-pvc 
  23.         params: 
  24.           - name: git_url 
  25.             value: $(tt.params.gitrepositoryurl) 
  26.           - name: image 
  27.             value: "harbor.k8s.local/course/devops-demo:$(tt.params.gitrevision)" 
  28.           - name: charts_dir 
  29.             value: "./helm" 
  30.           - name: release_name 
  31.             value: devops-demo 
  32.           - name: release_namespace 
  33.             value: "kube-ops" 
  34.           - name: overwrite_values 
  35.             value: "image.repository=harbor.k8s.local/course/devops-demo,image.tag=$(tt.params.gitrevision)" 
  36.           - name: values_file 
  37.             value: "my-values.yaml" 

直接创建上面新建的几个资源对象即可,这会创建一个 eventlistern 服务用来接收 Webhook 请求:

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

所以一定还要记得在 Gitlab 仓库中配置上 Webhook:

 

gitlab webhook

这样我们整个触发器和监听器就配置好了,接下来我们去修改下我们的项目代码,然后提交代码,正常提交过后就会在集群中创建一个 PipelinRun 对象用来执行我们的流水线了:

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

可以看到流水线执行成功后,应用已经成功部署了我们新提交的代码,到这里我们就完成了使用 Tekton 来重构项目的流水线。

 

责任编辑:姜华 来源: k8s技术圈
相关推荐

2021-06-26 14:22:34

Tekton流水线Kubernetes

2021-06-18 05:48:02

Tekton DevopsKubernetes

2017-03-02 14:12:13

流水线代码Clojure

2021-11-26 08:14:05

云原生CICD

2011-10-19 08:04:12

2023-09-27 08:24:49

2022-07-18 06:05:28

Gitlab流水线

2017-02-28 16:00:45

DevOpsMarkdownreST

2023-05-10 15:08:00

Pipeline设计模式

2022-03-30 09:43:19

jscodeshif自动化重构开发

2013-06-06 09:31:52

2024-01-07 12:47:35

Golang流水线设计模式

2017-02-28 15:40:30

Docker流水线Azure

2021-11-08 07:41:16

Go流水线编程

2021-01-05 08:39:51

容器前端流水线

2022-01-26 08:12:42

Jenkins开源流水线

2023-05-09 10:48:21

AppStackZadig

2023-08-18 10:24:52

GitLabCI 流水线

2023-04-02 21:49:10

开源Tekton

2018-04-24 09:00:00

开发自动化软件架构
点赞
收藏

51CTO技术栈公众号