2024-04-03 如何使用tekton快速搭建CI/CD平台

由于组织内部的人事调整,我接手了一套 CICD 平台。为了帮助团队成员尽快熟悉这套系统,今天我将详细讲解 CI 和 CD 的概念,并介绍这套系统底层关键组件 Tekon 的基本知识。最后,我将通过一个 Golang 项目作为示例,向大家演示如何使用 Tekton 从零开始快速构建自己的 CICD 平台,实现自动化的流程。

CICD #

CI 和 CD 是软件开发中常接触到的两个术语,分别代表持续集成(Continuous Integration)和持续交付(Continuous Delivery)或持续部署(Continuous Deployment)。 #
CICD 在敏捷开发和 DevOps 扮演着重要的角色,通过自动化的脚本 CICD 可以减少从代码开发到部署期间人工操作次数,从而加快软件交付速度、提高软件质量、促进团队协作。因此,CICD 是实现软件持续高质量交付的关键流程。 #

image-20240222114650519

持续集成

持续集成的概念早在上个世纪 90 年代就开始出现,经过多年的完善和推广,现在已经被广泛认可和使用。其基本概念是:持续集成是一种软件开发实践,团队成员频繁将他们的工作成果集成在一起;每次提交后,自动触发运行一次包含自动化验证集的构建任务,以便能尽早发现集成问题。

持续集成的核心原则包括频繁提交代码、自动化构建和测试、快速反馈、团队合作等。

通过频繁将代码集成到主干分支中,减少代码集成造成的延期和风险,通过自动化构建和测试进行功能测试、单元测试、集成测试等操作快速发现问题,并及时向开发人员反馈测试结果和推进问题修复。在这个过程中,各团队成员之间需要加强沟通和合作,协同开发和解决问题。

在持续集成中,为了确保功能分支的代码可以顺利集成到主干,团队成员可以采用六步提交法:

  1. 检出代码前先更新代码库,确保代码库中的最新代码与本地代码相同步;
  2. 在个人工作区对代码进行修改;
  3. 在本地进行构建和测试,确保代码符合修改的要求;
  4. 将主干分支合并到个人工作区后,再次进行构建和测试,防止开发期间代码与他人冲突;
  5. 将修改后的代码推送到主干分支;
  6. 在主干分支进行构建和测试,确保主干分支的代码质量和准确性;

image-20240222114702744

持续交付&&持续部署 #

持续交付和持续部署是在持续集成的基础上更进一步的自动化。

持续交付是指在开发流程中,将代码持续地构建、测试和打包,并尽可能快地交付到生产环境前的预生产环境中。

持续部署是指在持续交付的基础上,将代码在通过预生产环境的测试后,自动部署到生产环境中,与持续交付相比,持续部署不需要人工干预。

image-20240222114717137

那么,如何在企业内快速搭建自己的 CICD 平台呢?Tekton 给我们提供了一种解决方案。

Tekton #

Tekton 是一款基于 Kubernetes 实现的 CICD 开源框架,它提供了丰富的组件来满足各种构建、测试和部署的场景。我们可以直接使用 Tekton 来构建自己的 CICD 流程,也可以基于它进行二次开发搭建满足企业内部定制化需求的 CICD 平台。

为什么是 Tekton #

在社区中有很多 CICD 工具,比如 Gitlab CI、Jenkins、Travis CI、Circle CI 等,那为什么要选择 Tekton 呢?

原因有三个:

  1. Tekton 是一款轻量级的 CICD 框架,对于熟悉 Kebernetes 的同学来说,它的上手门槛非常低; #
  2. Tekton 具有很强的扩展性,通过自定义 Pipeline 和 Task 可以快速定制和扩展 CICD 流程,可以满足不同的业务场景; #
  3. Tekton 是一款基于 Kubernetes 实现的 CICD 框架,Tekton 可以充分利用 Kubernetes 的生态,从而大大降低部署和管理的难度; #
作为云原生领域事实上的标准,Kubernetes 提供了强大的能力,包括自动化编排、部署、恢复和自愈等功能。 #
以横向扩容为例,Tekon 可以基于 Kubernetes 提供的节点管理能力快速地完成扩容,从而提高系统的吞吐量和并发处理能力。 #
此外,Tekton 可以很好地融合进 Kubernetes 的生态,与其他 Kubernetes 工具和服务实现整合,包括监控、告警、日志服务等。这样可以形成基于 Kubernetes 的完整 DevOps 技术栈,为实现 CICD 流程的全生命周期管理提供更好的保障和支持。 #
因此,基于 Kubernetes 的实现,是我们选择 Tekton 的主要原因。 #

image-20240228180505542

安装 Tekton #

推荐使用 Operator 的方式安装 Tekton: #
kubectl apply -f https://storage.googleapis.com/tekton-releases/operator/latest/release.yaml
安装 Tekton 时可以选择litebasicall 等选项,每个选项中含有不同的组件: #

image-20240222114748122

使用 Operator 安装Tekton 时,默认会选择选择all选项安装所有组件,如果不需要这么多组件,可以使用其他选项(例如lite)来进行组件安装: #
kubectl apply -f https://raw.githubusercontent.com/tektoncd/operator/main/config/crs/kubernetes/config/lite/operator_v1alpha1_config_cr.yaml

基本概念 #

在开始使用 Tekton 之前,我们先来了解 Tekton 的两个核心对象: #
  • Task:任务,Tekton 中的最小执行单位,由一个按顺序执行的步骤(Step)列表组成; #
  • Pipeline:流水线,由一个或多个 Task 组成,流水线中定义了每个 Task 的执行顺序以及它们的依赖关系; #
Task 代表一个明确的任务,可以是构建、测试或者部署等操作,每个 Task 对应的是一个 Kubernetes 的 Pod,任务 Task 里面的步骤 Step 则对应 Pod 中的容器,流水线 Pipeline 则是对任务 Task 的编排。 #
任务 Task 是步骤 Step 的集合,流水线 Pipeline 在任务 Task 的集合。Task 和 Pipeline 都是定义,他们在实例化后分别称为 TaskRun 和 PipelineRun, 会明确声明具体的输入、输出等内容。 #
它们的关系示意图如下: #

image-20240222114801253

接下来,我将以 Golang 项目为例,演示如何使用 Tekton 搭建 CICD 平台。 #

搭建 CICD 平台 #

本次实践使用的 Kubernetes 集群版本是 v1.26.0,Tekton Pipeline 版本是 v0.56.0。另外,需要准备好代码仓库、镜像仓库、nfs 存储等基础设施。 #
Golang 项目的目录结构如下: #
.
├── Dockerfile
├── go.mod
├── main.go
└── README.md
其中 Dockerfile 的内容如下: #
FROM golang:latest
WORKDIR /app
COPY . /app
RUN go build -o main .
EXPOSE 8080
CMD ["./main"]
整个过程将会使用 Tekton Pipeline 把代码从 Git 仓库克隆到本地,在构建完成后将镜像推送至镜像仓库,最后自动部署到 Kubernetes 集群中。 #

编写 Task #

Task 的定义支持以下字段: #
  • apiVersion:API 版本 #
  • kind:类型 #
  • metadata:元数据 #
  • spec:配置 #
    • description:任务的描述 #
    • steps:任务的步骤 #
    • params:任务的执行参数 #
    • workspaces:任务所需的卷的路径 #
    • results:任务将执行结果抛给下游时使用的名称 #
    • volumes:任务中的步骤中可使用的存储卷 #
    • stepTemplate:步骤的共享模板 #
    • sidecars:步骤中的 Sidecar 容器 #
在 Tekton 的插件库中有很多现成的 Task 可以使用,为了方便大家更好地了解 Tekton Task 的开发流程,我将以克隆 Git 仓库代码为例,详细介绍一下里面的细节。 #
示例如下: #
# git-clone.yaml
apiVersion: tekton.dev/v1
kind: Task
metadata:
  name: git-clone
spec:
  description: git clone
  workspaces:
  - name: git-directory
    description: 克隆代码在本地存储的路径
  - name: ssh-directory
    description: 使用 ssh 克隆代码时所需的相关凭证
  params:
  - name: url
    description: Git仓库地址
  - name: revision
    description: Git分支/标签
  results:
  - name: gitCommitHash
    description: Git提交哈希
  steps:
  - name: git-clone
    image: bitnami/git:latest
    workingDir: $(workspaces.git-directory.path)
    env:
    - name: PARAM_URL
      value: $(params.url)
    - name: PARAM_REVISION
      value: $(params.revision)
    - name: WORKSPACE_GIT_DIRECTORY_PATH
      value: $(workspaces.git-directory.path)
    - name: WORKSPACE_SSH_DIRECTORY_BOUND
      value: $(workspaces.ssh-directory.bound)
    - name: WORKSPACE_SSH_DIRECTORY_PATH
      value: $(workspaces.ssh-directory.path)
    script: |
      #!/bin/bash
      # -x 表示输出每条命令及其结果,-e 表示脚本执行失败时立即退出,-u 表示使用了未定义的变量时立即退出脚本
      set -xeu
      # 设置凭证,使用ssh认证的方式与git进行身份验证
      if [ "${WORKSPACE_SSH_DIRECTORY_BOUND}" == "true" ]; then
        # 将配置中的ssh文件拷贝到~/.ssh目录中,-R 表示递归复制,即复制目录及其子目录和文件,-L 表示解析符号链接,复制符号链接所指向的实际文件,避免复制时拿到的是软连接
        cp -RL "${WORKSPACE_SSH_DIRECTORY_PATH}" ~/.ssh
        chmod 700 ~/.ssh
        chmod -R 400 ~/.ssh/*
      fi

      cd ${WORKSPACE_GIT_DIRECTORY_PATH}
      mkdir -p git_repo
      # 将git_repo添加到Git的安全目录列表
      git config --global --add safe.directory ${WORKSPACE_GIT_DIRECTORY_PATH}/git_repo

      # 克隆代码
      git clone ${PARAM_URL} git_repo
      cd git_repo
      git checkout ${PARAM_REVISION}

      # 将结果输出到results字段中
      GIT_COMMIT_HASH="$(git log --pretty=format:"%h" -n 1)"
      echo -n ${GIT_COMMIT_HASH} | tee $(results.gitCommitHash.path)
在克隆 Git 仓库代码时,需要通过volumes字段来提供 Git 凭证,并使用params字段明确指定要克隆的代码仓库、分支等内容。代码克隆到本地后,还需要将它存储到workspaces中以便后续任务进行测试和构建等工作。同时,为了保留 Git 相关的记录,需要通过results将它们传递给后续的任务使用。 #
Task 的开发过程并不复杂,主要是在步骤 Step 的script字段里通过 shell 脚本来实现,通过类似的方式,我们可以快速实现测试、构建、部署等场景的 Task。 #
接下来我们再开发一个用于构建镜像的任务 Task : #
# build-push.yaml
apiVersion: tekton.dev/v1
kind: Task
metadata:
  name: build-push
spec:
  description: 使用 docker 构建镜像
  params:
  - name: image
    description: 镜像地址
  - name: dockerfilePath
    description: dockerfile路径
  - name: gitCommitHash
    description: git提交哈希
  workspaces:
  - name: git-directory
    description: 克隆代码在本地存储的路径
  - name: dockerconfig
    description: docker配置文件
  volumes:
  - name: docker-socket
    hostPath:
      path: /var/run/docker.sock
      type: Socket
  steps:
  - name: build
    image: docker:git
    workingDir: $(workspaces.git-directory.path)
    volumeMounts:
    - name: docker-socket
      mountPath: /var/run/docker.sock
    env:
    - name: WORKSPACE_DOCKERCONFIG_BOUND
      value: $(workspaces.dockerconfig.bound)
    - name: WORKSPACE_DOCKERCONFIG_PATH
      value: $(workspaces.dockerconfig.path)
    - name: WORKSPACE_GIT_DIRECTORY_PATH
      value: $(workspaces.git-directory.path)
    script: |
      #!/bin/sh
      set -xeu
      if [ "${WORKSPACE_DOCKERCONFIG_BOUND}" == "true" ]; then
        cp -RL "${WORKSPACE_DOCKERCONFIG_PATH}" ~/.docker
        chmod 700 ~/.docker
      fi
      # 将git_repo添加到Git的安全目录列表
      git config --global --add safe.directory ${WORKSPACE_GIT_DIRECTORY_PATH}/git_repo
      cd git_repo
      IMAGE="$(params.image)-$(params.gitCommitHash)"
      docker build -t ${IMAGE} -f $(params.dockerfilePath) .
      docker push ${IMAGE}
在构建的过程中,我使用的是 Docker In Docker 的方式,将 Docker socket 挂载给容器,以便容器中的进程可以通过 Docker API 与宿主机上的 Docker 引擎进行交互。这种方式可以用于开发环境的调试使用,但不推荐在生产环境中使用。 #
到这里,相信大家应该都明白了任务 Task 的开发流程,后续部署应用的任务我就简单地一笔带过,示例如下: #
# deploy-app.yaml
apiVersion: tekton.dev/v1
kind: Task
metadata:
  name: deploy-app
spec:
  description: 部署应用
  steps:
  - name: deploy-app
    image: ubuntu:latest
    script: |
      #!/bin/sh
      echo "部署应用到 Kubernetes 集群"
在完成任务 Task 的编写后,接下来我们需要使用流水线 Pipeline 将它们串起来,实现克隆、构建、推送镜像、部署应用的自动化流程。 #

编排 Pipeline #

通过前面的操作后,我们得到了三个任务 Task: #
  • git-clone:将代码克隆到本地; #
  • build-push:构建代码并推送镜像; #
  • deploy-app:部署服务到集群应用; #
我们来看一下流水线 Pipeline 的编排方式,示例如下: #
# pipeline.yaml
apiVersion: tekton.dev/v1
kind: Pipeline
metadata:
  name: cicd
spec:
  description: 使用git和docker等工具将项目从远程代码仓库拉到容器中构建,将产物推送到镜像仓库,并部署到集群中
  params:
  - name: url
    description: git仓库地址
  - name: revision
    description: git分支/标签
  - name: image
    description: 镜像地址
  - name: dockerfilePath
    description: dockerfile路径
  workspaces:
  - name: git-directory
    description: 克隆代码在本地存储的路径
  - name: ssh-directory
    description: 克隆代码时所需的相关凭证
  - name: dockerconfig
    description: docker配置文件
  tasks:
  - name: git-clone
    taskRef:
      name: git-clone
    params:
    - name: url
      value: $(params.url)
    - name: revision
      value: $(params.revision)
    workspaces:
    - name: git-directory
      workspace: git-directory
    - name: ssh-directory
      workspace: ssh-directory
  - name: build-push
    runAfter: ["git-clone"]
    taskRef:
      name: build-push
    params:
    - name: image
      value: $(params.image)
    - name: dockerfilePath
      value: $(params.dockerfilePath)
    - name: gitCommitHash
      value: $(tasks.git-clone.results.gitCommitHash)
    workspaces:
    - name: git-directory
      workspace: git-directory
    - name: dockerconfig
      workspace: dockerconfig
  - name: deploy-app
    runAfter: ["build-push"]
    taskRef:
      name: deploy-app
在编排流水线时,需要处理好任务之间的顺序。这里我们通过runAfter字段来表明任务build-pushgit-clone之后运行,任务deploy-app又在build-push之后运行,同时还需要关注任务所需的参数和依赖关系。 #
接下来,我们将使用 PipelineRun 运行流水线。 #

运行流水线 #

Tekton 的 PipelineRun 对象负责实例化流水线 Pipeline,明确声明各种参数和依赖,并自动为流水线中的任务 Task 创建相应的 TaskRun。 #
完整的 PipelineRun 对象配置如下: #
# pipelinerun.yaml
apiVersion: tekton.dev/v1
kind: PipelineRun
metadata:
  generateName: cicd-
spec:
  pipelineRef:
    name: cicd
  params:
  - name: url
    value: git@zlw.com:golang/demo.git
  - name: revision
    value: master
  - name: image
    value: hub-zlw.com/golang/demo:0.0.1
  - name: dockerfilePath
    value: ./Dockerfile
  workspaces:
  - name: ssh-directory
    secret:
      secretName: ssh-directory
  - name: dockerconfig
    secret:
      secretName: dockerconfig
  - name: git-directory
    volumeClaimTemplate:
      spec:
        accessModes:
        - ReadWriteOnce
        resources:
          requests:
            storage: 1Gi
  taskRunTemplate:
    podTemplate:
      securityContext:
        fsGroup: 65532
在运行流水线的时,我们提供了以下参数和依赖: #
  • 参数: #
    • url:镜像仓库地址 #
    • revision:代码分支或版本 #
    • Image:构建完成的镜像名称 #
    • dockerfilePath:dockerfile文件的路径 #
  • 依赖: #
    • 密钥: #
      • ssh凭证:用于拉取代码 #
      • docker凭证:用于推送镜像 #
    • 存储:用于存放代码 #
依赖中的密钥和存储,我们需要提前准备,示例如下: #
# git凭证
# -w0表示一行来显示
$ cat ~/.ssh/id_rsa | base64 -w0
LS0tLS1CRU==
$ cat ~/.ssh/known_hosts | base64 -w0
d3BzZ2l0Lmtpbm==

# 将base64后的凭证放在Secret中,包括id_rsa和known_hosts等文件
$ kubectl apply -f - <<EOF
apiVersion: v1
kind: Secret
metadata:
  name: ssh-directory
  namespace: default
type: Opaque
data:
  id_rsa: LS0tLS1CRU==
  known_hosts: d3BzZ2l0Lmtpbm==
EOF

# docker 凭证
$ kubectl apply -f - <<EOF
apiVersion: v1
kind: Secret
metadata:
  name: dockerconfig
type: kubernetes.io/dockerconfigjson
data:
  config.json: eyJhdXRocyI6eyJod==
EOF
存储方面,我使用的是 nfs 作为 Tekton 的存储,由于 nfs 的安装过程稍微比较复杂,后续有需要可以单独细说,这里大家自行准备。 #
最后,我们通过命令行运行流水线和查看流水结果: #
# 运行流水线
$ kubectl create -f pipelinerun.yaml

$ 查看运行结果
$ kubectl get pipelinerun
NAME         SUCCEEDED   REASON      STARTTIME   COMPLETIONTIME
cicd-24c5j   True        Succeeded   19h         19h
cicd-cs9gq   True        Succeeded   20h         20h

当然,我们也可以通过 Tekton 提供的 Dashboard 查看流水线:

image-20240222114838375

image-20240222114852618

到这里,我们已经使用 Tekton 完成了最小闭环的 CICD 流程。借助 Tekton 的生态和能力,我们可以快速构建自己的 CICD 平台,或者在 Tekton 上做二次开发,以满足自身偏好或公司业务需求,打造更加完善的内部生态。 #

总结 #

基于 Tekton 实现的 CICD 平台具有巨大的创新潜力,不仅能为各类型的应用提供 CICD 流程支持,还能用于支持各种云原生服务,例如通过 istio 进行流量控制、实现混沌工程等。此外,Tekton 还可作为通用的工作流引擎使用,以实现基于事件的自动化流程等。 #
在本期内容中,我向大家介绍了 CICD 的基本定义以及如何使用 Tekton 从零开始快速构建 CICD 平台,包括 Tekton 的基本概念、如何开发任务 Task、如何编排流水线 Pipeline 以及如何运行流水线等。在后续的内容中,我将更加深入介绍 Tekton 的更多高级功能,例如与其他工具的集成、Tekton 的调度优化以及性能优化等。我们下期再见~ #