由于组织内部的人事调整,我接手了一套 CICD 平台。为了帮助团队成员尽快熟悉这套系统,今天我将详细讲解 CI 和 CD 的概念,并介绍这套系统底层关键组件 Tekon 的基本知识。最后,我将通过一个 Golang 项目作为示例,向大家演示如何使用 Tekton 从零开始快速构建自己的 CICD 平台,实现自动化的流程。
CICD
CI 和 CD 是软件开发中常接触到的两个术语,分别代表持续集成(Continuous Integration)和持续交付(Continuous Delivery)或持续部署(Continuous Deployment)。
CICD 在敏捷开发和 DevOps 扮演着重要的角色,通过自动化的脚本 CICD 可以减少从代码开发到部署期间人工操作次数,从而加快软件交付速度、提高软件质量、促进团队协作。因此,CICD 是实现软件持续高质量交付的关键流程。
持续集成
持续集成的概念早在上个世纪 90 年代就开始出现,经过多年的完善和推广,现在已经被广泛认可和使用。其基本概念是:持续集成是一种软件开发实践,团队成员频繁将他们的工作成果集成在一起;每次提交后,自动触发运行一次包含自动化验证集的构建任务,以便能尽早发现集成问题。
持续集成的核心原则包括频繁提交代码、自动化构建和测试、快速反馈、团队合作等。
通过频繁将代码集成到主干分支中,减少代码集成造成的延期和风险,通过自动化构建和测试进行功能测试、单元测试、集成测试等操作快速发现问题,并及时向开发人员反馈测试结果和推进问题修复。在这个过程中,各团队成员之间需要加强沟通和合作,协同开发和解决问题。
在持续集成中,为了确保功能分支的代码可以顺利集成到主干,团队成员可以采用六步提交法:
- 检出代码前先更新代码库,确保代码库中的最新代码与本地代码相同步;
- 在个人工作区对代码进行修改;
- 在本地进行构建和测试,确保代码符合修改的要求;
- 将主干分支合并到个人工作区后,再次进行构建和测试,防止开发期间代码与他人冲突;
- 将修改后的代码推送到主干分支;
- 在主干分支进行构建和测试,确保主干分支的代码质量和准确性;
持续交付&&持续部署
持续交付和持续部署是在持续集成的基础上更进一步的自动化。
持续交付是指在开发流程中,将代码持续地构建、测试和打包,并尽可能快地交付到生产环境前的预生产环境中。
持续部署是指在持续交付的基础上,将代码在通过预生产环境的测试后,自动部署到生产环境中,与持续交付相比,持续部署不需要人工干预。
那么,如何在企业内快速搭建自己的 CICD 平台呢?Tekton 给我们提供了一种解决方案。
Tekton
Tekton 是一款基于 Kubernetes 实现的 CICD 开源框架,它提供了丰富的组件来满足各种构建、测试和部署的场景。我们可以直接使用 Tekton 来构建自己的 CICD 流程,也可以基于它进行二次开发搭建满足企业内部定制化需求的 CICD 平台。
为什么是 Tekton
在社区中有很多 CICD 工具,比如 Gitlab CI、Jenkins、Travis CI、Circle CI 等,那为什么要选择 Tekton 呢?
原因有三个:
-
Tekton 是一款轻量级的 CICD 框架,对于熟悉 Kebernetes 的同学来说,它的上手门槛非常低;
-
Tekton 具有很强的扩展性,通过自定义 Pipeline 和 Task 可以快速定制和扩展 CICD 流程,可以满足不同的业务场景;
-
Tekton 是一款基于 Kubernetes 实现的 CICD 框架,Tekton 可以充分利用 Kubernetes 的生态,从而大大降低部署和管理的难度;
作为云原生领域事实上的标准,Kubernetes 提供了强大的能力,包括自动化编排、部署、恢复和自愈等功能。
以横向扩容为例,Tekon 可以基于 Kubernetes 提供的节点管理能力快速地完成扩容,从而提高系统的吞吐量和并发处理能力。
此外,Tekton 可以很好地融合进 Kubernetes 的生态,与其他 Kubernetes 工具和服务实现整合,包括监控、告警、日志服务等。这样可以形成基于 Kubernetes 的完整 DevOps 技术栈,为实现 CICD 流程的全生命周期管理提供更好的保障和支持。
因此,基于 Kubernetes 的实现,是我们选择 Tekton 的主要原因。
安装 Tekton
推荐使用 Operator 的方式安装 Tekton:
kubectl apply -f https://storage.googleapis.com/tekton-releases/operator/latest/release.yaml
安装 Tekton 时可以选择lite
、basic
、all
等选项,每个选项中含有不同的组件:
使用 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, 会明确声明具体的输入、输出等内容。
它们的关系示意图如下:
接下来,我将以 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-push
在git-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 查看流水线: