由于组织内部的人事调整,我接手了一套 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 的调度优化以及性能优化等。我们下期再见~