大家好,我是民工哥!
提到运维自动化,我们就不得不提起 Jenkins 这个工具,它是一个功能强大且灵活的持续集成工具,适用于各种规模的软件项目,并能够通过自动化和集成化的方式提高软件开发和测试的效率。
现在,你还在 Jenkins 上点点吗?
那就太 OUT 了!快来试试 Tekton 的灵活自动化吧。
Tekton 简介
Tekton是一个用于构建CI/CD(持续集成和持续交付)系统的云原生解决方案,也是一个强大的、灵活的开源框架,允许开发者构建、测试和发布应用。它由Tekton Pipelines(提供构建块)和支持组件(如Tekton CLI和Tekton Catalog)组成,是一个完整的生态系统。
Tekton在Kubernetes集群上作为扩展安装和运行,包含一组Kubernetes自定义资源,这些资源定义了可以为pipelines创建和重用的构建块。Tekton实体具有高度的可定制性、可重复使用性和可扩展性,使得用户可以根据需要灵活定义和扩展CI/CD流水线。
Tekton最初是Knative的子项目build-pipeline,主要用于给Knative的build模块增加pipeline功能。后来独立出来,成为一个通用的CI/CD工具,现在是Linux基金会项目CD基金会的一部分。
Tekton 功能特性
以下是Tekton的一些主要功能特性:
- 跨平台与灵活性:Tekton 允许开发者跨多云环境或本地系统进行构建、测试与部署。为用户提供了高度的灵活性,可以根据具体需求快速灵活地定义流水线。
- 工件管理:Tekton 提供了工件管理功能,能够存储、管理和保护工件。此外,Tekton管道可以很好地与其它第三方工具相配合,实现更广泛的集成和协作。
- 可定制性:Tekton 实体是完全可定制的,这意味着平台工程师可以定义非常详细的构建基目录,以供开发人员在各种情况下使用,可以满足不同的构建和部署需求。
- 可重用性:Tekton 实体不仅可定制,而且是完全可移植的。一旦定义了某个管道,组织内的任何人都可以使用它并重用其构造块。这大大加速了复杂管道的构建过程,避免了重复工作。
- 可扩展性:Tekton 具有强大的可扩展性。Tekton Catalog是Tekton社区驱动的存储库,其中包含丰富的预制组件,用户可以快速创建新的并扩展现有管道。此外,Tekton的工作负载在Kubernetes容器中执行,通过简单地将节点添加到群集,可以轻松增加工作负载容量。
- 安全性与合规性:Tekton Chains 提供了类似DevSecOps的能力,可以对整个CI/CD流程进行签名、追踪和审计,从而增加CI/CD流程的安全合规性。
- 调试与问题排查:Tekton 增加了断点功能,当容器运行失败时不会立即退出,这使得用户能够进入容器进行问题排查。同时,Tekton还提供了两份调试信息,分别位于
/tekton/debug/scripts
和/tekton/debug/info/step-no
,方便开发者进行调试和问题定位。
Tekton 组件
Tekton的主要组件包括:
- Tekton Pipelines:这是 Tekton 最核心的组件,它定义了一组 Kubernetes 的自定义资源(Custom Resources),包括 Pipeline、PipelineRun、Task、TaskRun、TaskSet、Trigger 和 TriggerTemplate 等。这些资源为创建、管理和执行CI/CD流水线提供了基础。Pipeline定义了一个流水线的结构,包括一系列的Task(任务),而Task则定义了在流水线中执行的具体步骤。
- Tekton CLI:这是Tekton的命令行界面工具,它允许用户通过命令行与Tekton Pipelines进行交互。用户可以使用Tekton CLI来创建、查看、更新和删除流水线、任务等资源,以及触发流水线的执行。
- Tekton Catalog:这是一个社区驱动的Tekton构建块存储库。它包含了大量预定义的组件和任务,用户可以快速利用这些组件来创建新的流水线或扩展现有管道。这大大提高了开发效率,减少了重复工作。
- Tekton Hub是一个基于 Web 的图形界面,用于访问 Tekton Catalog。
- Tekton Triggers:此组件允许用户根据事件来实例化流水线。例如,当用户每次将PR(Pull Request)合并到GitHub仓库时,可以触发流水线实例和构建工作。这使得Tekton能够自动响应代码变更,实现持续集成和持续部署。
- Tekton Dashboard:这是Tekton Pipelines的基于Web的一个图形界面,提供了有关流水线执行的详细信息。用户可以通过这个界面实时监控流水线的状态、查看日志、进行故障排查等操作。
Tekton Pipelines 基本概念
最基本的四个概念:Task、TaskRun、Pipeline、PipelineRun。
- Task: Task 为构建任务,是 Tekton 中不可分割的最小单位,正如同 Pod 在 Kubernetes 中的概念一样。在 Task 中,可以有多个 Step,每个 Step 由一个 Container 按照顺序来执行。
- Pipeline: Pipeline 由一个或多个 Task 组成。在 Pipeline 中,用户可以定义这些 Task 的执行顺序以及依赖关系来组成 DAG(有向无环图)。
- PipelineRun: PipelineRun 是 Pipeline 的实际执行产物,当用户定义好 Pipeline 后,可以通过创建 PipelineRun 的方式来执行流水线,并生成一条流水线记录。
- TaskRun: PipelineRun 被创建出来后,会对应 Pipeline 里面的 Task 创建各自的 TaskRun。一个 TaskRun 控制一个 Pod,Task 中的 Step 对应 Pod 中的 Container。当然,TaskRun 也可以单独被创建。
Tekton VS Jenkins
Tekton 和 Jenkins 在多个方面存在显著的差异!
运行环境与架构
- Tekton本身运行于Kubernetes环境中,其流水线中的每一个任务在运行时都会启动一个Pod来执行。这使得Tekton能够复用云原生环境中的各种镜像设施,无需单独部署VM上的应用来管理CI/CD过程。因此,Tekton更适合于云原生应用的CI/CD。
- Jenkins则是一个更广泛应用的CI/CD工具,其前身Hudson诞生于2005年,由于优秀的架构设计和良好的产品体验,Jenkins/Hudson成为第一个被大规模应用的持续集成产品。
集成与定制
- Tekton提供声明式语法来定义任务和步骤,具有高度的可扩展性和灵活性。用户可以自定义任务和步骤,方便集成第三方工具和库,并支持多种任务类型,如一次性任务、周期性任务等。
- Jenkins也提供了丰富的功能和插件来支持CI/CD过程,但其架构和扩展性可能与Tekton有所不同。
社区支持
- Tekton由于其云原生的特性和与Kubernetes的紧密集成,得到了许多云原生应用团队的青睐。例如,Jenkins X项目选择了Tekton作为其流水线执行引擎,并计划移除内部的Jenkins静态master支持。
- Jenkins作为一个成熟的CI/CD工具,已经在行业中建立了广泛的用户群体和社区支持。
总的来说,Tekton和Jenkins各有其优势和应用场景。选择哪一个工具取决于团队的具体需求、技术栈以及运行环境。
在后面的介绍中会给大家提供一些建议供实际参考使用。
Tekton 部署
Tekton 是基于 Kubernetes 的,因此需要一个运行正常的 K8s 环境。
访问 Tekton 的官方文档或 GitHub 仓库(https://github.com/tektoncd/pipeline#required-kubernetes-version),选择与你的 K8s 集群版本相匹配的 Tekton 版本。
版本不匹配可能会导致部署失败,出现如“Version check failed, kubernetes version xxx is not compatible”的错误日志。
由于国内的网络原因,无法正常访问 gcr.io 上面的镜像,所以需要将需要的镜像文件下载下来,放到自己的镜像仓库中。
下载部署文件
访问 Tekton 的官方存储库,下载用于 Kubernetes 环境部署的 yaml 文件。可以从 https://storage.googleapis.com/tekton-releases/pipeline/lates... 下载最新的release.yaml文件。
#Tekton controller
wget https://storage.googleapis.com/tekton-releases/pipeline/previous/v0.32.3/release.yaml -O tekton-pipeline-release.yaml
#Tekton Dashboard
wget https://github.com/tektoncd/dashboard/releases/download/v0.25.0/tekton-dashboard-release.yaml -O tekton-dashboard-release.yaml
拉取 gcr.io 镜像,推送到自己的镜像
在 mac/linux 的工作机器上,执行如下代码(如果你用的 Windows,可以通过安装 gnu 的命令行工具来获取 grep, awk 的工具。
grep -Eo "gcr.io([^\" ]*)" tekton-pipeline-release.yaml | awk '{print "docker pull "$0}' > pull.sh
grep -Eo "gcr.io([^\" ]*)" tekton-pipeline-release.yaml | awk -F"@" '{origin = $0;gsub("gcr.io/","",$1);print "docker tag " origin " my-registry.cn-beijing.cr.aliyuncs.com/gcrio/"$1 ";\ndocker push my-registry.cn-beijing.cr.aliyuncs.com/gcrio/" $1}' > tag.push.sh
source pull.sh
source push.sh
然后更改 tekton-pipeline-release 中的地址为自己的镜像仓库(Linux):
sed -i "s/gcr.io/my-registry.cn-beijing.cr.aliyuncs.com\/gcrio/g" tekton-pipeline-release.yaml
mac 上这样运行(如果 mac 上面安装了 gnu sed,可以直接执行上面的 sed 命令):
sed -i "" "s/gcr.io/my-registry.cn-beijing.cr.aliyuncs.com\/gcrio/g" tekton-pipeline-release.yaml
然后编辑 tekton-pipeline-release.yaml 里面的内容,去掉镜像仓库中的 SHA256,通过版本号或者 latest 来引用。
部署 Tekton
部署 Tekton pipeline
kubectl create namespace tekton-pipelines
kubectl apply -f tekton-pipeline-release.yaml
部署 Tekton Dashboard
dashboard 用到的镜像目前只有1个。直接生成 docker pull, docker tag, docker push 命令, 输出到 console, 然后手工执行这些命令即可:
grep -Eo "gcr.io([^\" ]*)" tekton-dashboard-release.yaml | awk '{print "docker pull "$0}' grep -Eo "gcr.io([^\" ]*)" tekton-dashboard-release.yaml | awk -F"@" '{origin = $0;gsub("gcr.io/","",$1);print "docker tag " origin " my-registry.cn-beijing.cr.aliyuncs.com/gcrio/"$1 ";\ndocker push my-registry.cn-beijing.cr.aliyuncs.com/gcrio/" $1}'
拷贝前面两个命令的输出, 执行一下命令(docker pull、docker push)。
然后替换文件中的镜像地址(Linux):
sed -i "s/gcr.io/my-registry.cn-beijing.cr.aliyuncs.com\/gcrio/g" tekton-dashboard-release.yaml
mac 上这样运行:
sed -i "" "s/gcr.io/my-registry.cn-beijing.cr.aliyuncs.com\/gcrio/g" tekton-dashboard-release.yaml
然后部署 tekton dashboard:
kubectl apply -f tekton-dashboard-release.yaml
检查 pod 的状态:
kubectl get pods --namespace tekton-pipelines
可以看到如下的输出
pod 正常 running 之后,转发集群端口到本地,即可通过本地访问:
kubectl --namespace tekton-pipelines port-forward svc/tekton-dashboard 9097:9097
然后就可以本地访问 Dashboard: http://localhost:9097
使用 Tekton 创建 CICD 流水线
创建一个最简单的 task 名称为 hello, 这个 task 使用 alpine 的镜像启动一个容器,执行指定脚本。
#hello-world.yaml 文件内容, 定义名称为 hello 的 task
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: hello
spec:
steps:
- name: echo
image: alpine
script: |
#!/bin/sh
echo "Hello World"
部署 Task
kubectl apply --filename hello-world.yaml
创建一个 TaskRun 来执行 hello 这个 Task
#hello-world-run.yaml
apiVersion: tekton.dev/v1beta1
kind: TaskRun
metadata:
name: hello-task-run
spec:
taskRef:
name: hello
在集群中运行 task:
kubectl apply --filename hello-world-run.yaml
使用 Tekton 发布 kubernetes 应用
基于Kubernetes 服务部署 Tekton Pipeline 实例,部署完成后使用tekton来完成源码拉取、应用打包、镜像推送和应用部署。
本文实现一个 golang-helloworld 项目 CI/CD 的完整流程,具体包括以下步骤:
- 从 gitee 仓库拉取代码,将源码构建成二进制文件
- 根据 Dockerfile 构建镜像并推送到阿里云ACR镜像仓库
- 使用sed命令替换yaml文件中的镜像地址为上一步构建的镜像
- 使用 kubectl apply -f 命令部署yaml文件到kubernetes集群
创建serviceaccount
镜推送到外部镜像仓库需要进行认证,创建登录阿里云ACR仓库的secret
kubectl create secret docker-registry aliyun-acr \
--docker-server=https://registry.cn-shenzhen.aliyuncs.com \
--docker-username=<your-username> \
--docker-password=<your-password> \
--dry-run=client -o json | jq -r '.data.".dockerconfigjson"' | base64 -d > /tmp/config.json
kubectl create secret generic docker-config --from-file=/tmp/config.json
创建kubernetes secret
kubectl create secret generic kubernetes-config --from-file=/root/.kube/config
创建serviceAccount
$ cat serviceaccount.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: build-bot
secrets:
- name: docker-config
- name: kubernetes-config
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: tekton-kubectl-role
rules:
- apiGroups:
- "*"
resources:
- pods
- deployments
- deployments/scale
- deployments/status
verbs:
- get
- list
- watch
- create
- delete
- patch
- update
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: tekton-kubectl-binding
subjects:
- kind: ServiceAccount
name: build-bot
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: tekton-kubectl-role
应用yaml文件
kubectl apply -f serviceaccount.yaml
创建 git-clone task
在执行镜像构建前Dockerfile存放在git仓库中,需要将代码克隆到本地,需要安装git-clone task,这里使用官方task。
kubectl apply -f \https://raw.githubusercontent.com/tektoncd/catalog/main/task/git-clone/0.9/git-clone.yaml
创建kaniko-build task
创建kaniko-build task,用于构建dokcer镜像,基于官方kaniko-task改造。
$ cat kaniko-build-task.yaml
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: kaniko-build
spec:
params:
- name: IMAGE_URL
description: Name (reference) of the image to build.
- name: IMAGE_TAG
description: Tag to apply to the built image
default: latest
- name: DOCKERFILE
description: Path to the Dockerfile to build.
default: ./Dockerfile
- name: CONTEXT
description: The build context used by Kaniko.
default: ./
- name: EXTRA_ARGS
type: array
default: []
- name: BUILDER_IMAGE
description: The image on which builds will run (default is v1.5.1)
default: gcr.io/kaniko-project/executor:v1.5.1@sha256:c6166717f7fe0b7da44908c986137ecfeab21f31ec3992f6e128fff8a94be8a5
workspaces:
- name: source
description: Holds the context and Dockerfile
- name: dockerconfig
description: Includes a docker `config.json`
optional: true
mountPath: /kaniko/.docker
results:
- name: IMAGE_DIGEST
description: Digest of the image just built.
- name: IMAGE_URL
description: URL of the image just built.
steps:
- name: build-and-push
workingDir: $(workspaces.source.path)
image: $(params.BUILDER_IMAGE)
args:
- $(params.EXTRA_ARGS)
- --dockerfile=$(params.DOCKERFILE)
- --context=$(workspaces.source.path)/$(params.CONTEXT)
- --destination=$(params.IMAGE_URL):$(params.IMAGE_TAG)
- --digest-file=$(results.IMAGE_DIGEST.path)
securityContext:
runAsUser: 0
应用yaml文件
kubectl apply -f kaniko-build-task.yaml
创建kubernetes-deploy task
创建kubernetes-deploy task,用于部署yaml文件到kubernetes集群。
$ cat kubernetes-deploy-task.yaml
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: kubernetes-deploy
spec:
workspaces:
- name: manifest-dir
- name: kubeconfig-dir
mountPath: /root/.kube
params:
- name: pathToYamlFile
description: The path to the yaml file to deploy within the git source
default: deployment.yaml
- name: IMAGE_URL
- name: IMAGE_TAG
- name: KUBECTL_IMAGE
default: docker.io/bitnami/kubectl:latest
steps:
- name: run-kubectl
image: $(params.KUBECTL_IMAGE)
workingDir: $(workspaces.manifest-dir.path)
script: |
sed -i s#IMAGE#$(params.IMAGE_URL)#g $(params.pathToYamlFile)
sed -i s#TAG#$(params.IMAGE_TAG)#g $(params.pathToYamlFile)
kubectl apply -f $(params.pathToYamlFile)
应用yaml文件
kubectl apply -f kubernetes-deploy-task.yaml
创建pipeline和pipelinerun
$ cat pipeline-run.yaml
apiVersion: tekton.dev/v1beta1
kind: Pipeline
metadata:
name: devops-hello-world-pipeline
spec:
workspaces:
- name: shared-data
- name: docker-config
- name: kubernetes-config
params:
# git-clone
- name: git_url
type: string
- name: revision
type: string
- name: gitInitImage
type: string
# kaniko-build
- name: dockerfile
type: string
description: reference of the image to build
- name: builder_image
type: string
description: reference of the image to build
- name: image_url
description: Url of image repository
- name: image_tag
description: Tag to apply to the built image
default: latest
# kubernetes-deploy
- name: kubectl_image
type: string
tasks:
- name: clone
taskRef:
name: git-clone
workspaces:
- name: output
workspace: shared-data
params:
- name: url
value: $(params.git_url)
- name: revision
value: $(params.revision)
- name: gitInitImage
value: $(params.gitInitImage)
- name: build-push-image
params:
- name: DOCKERFILE
value: $(params.dockerfile)
- name: IMAGE_URL
value: $(params.image_url)
- name: IMAGE_TAG
value: $(tasks.clone.results.commit)
- name: BUILDER_IMAGE
value: $(params.builder_image)
taskRef:
name: kaniko
runAfter:
- clone
workspaces:
- name: source
workspace: shared-data
- name: dockerconfig
workspace: docker-config
- name: deploy-to-k8s
taskRef:
name: kubernetes-deploy
params:
- name: KUBECTL_IMAGE
value: $(params.kubectl_image)
- name: IMAGE_URL
value: $(params.image_url)
- name: IMAGE_TAG
value: $(tasks.clone.results.commit)
- name: pathToYamlFile
value: deployment.yaml
workspaces:
- name: manifest-dir
workspace: shared-data
- name: kubeconfig-dir
workspace: kubernetes-config
runAfter:
- build-push-image
---
apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
generateName: devops-hello-world-pipeline-run-
spec:
serviceAccountName: build-bot
pipelineRef:
name: devops-hello-world-pipeline
params:
# git-clone
- name: git_url
value: https://gitee.com/willzhangee/tekton-golang-demo.git
- name: revision
value: master
- name: gitInitImage
#value: gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init:latest
value: dyrnq/tektoncd-pipeline-cmd-git-init:latest
# kaniko
- name: dockerfile
value: ./Dockerfile
- name: builder_image
# value: gcr.io/kaniko-project/executor:v1.5.1@sha256:c6166717f7fe0b7da44908c986137ecfeab21f31ec3992f6e128fff8a94be8a5
value: docker.io/bitnami/kaniko:latest
- name: image_url
value: registry.cn-shenzhen.aliyuncs.com/cnmirror/devops-hello-world
- name: image_tag
value: latest
# kubernetes-deploy
- name: kubectl_image
value: 'docker.io/bitnami/kubectl:latest'
workspaces:
- name: shared-data
volumeClaimTemplate:
spec:
accessModes:
- ReadWriteOnce
storageClassName: openebs-hostpath
resources:
requests:
storage: 1Gi
- name: docker-config
secret:
secretName: docker-config
- name: kubernetes-config
secret:
secretName: kubernetes-config
参数说明
gitInitImage# 执行git clone任务的镜像,官方镜像无法访问,推荐在docekrhub中查找替代镜像。
builder_image #执行kaniko 构建任务的镜像,官方镜像无法访问,推荐在docekrhub中查找替代镜像。
image_url #最终构建的应用镜像。
serviceAccountName #指定serviceAccountName用于认证。
shared-data workspace #用于在不同任务之间共享数据,PipelineRun中定义了volumeClaimTemplate类型的workspaces,能够动态申请所需的持久卷,使用kubectl get storageclass命令,确认k8s集群有默认可用的storageclass资源可用,本示例输出为openebs-hostpath (default)。
docker-config workspace #用于镜像仓库认证的secret卷,将secret中的`config.json挂载到/kaniko/.docker`下。
kubernetes-config #用于访问kubernetes,挂载到/root/.kube目录下。
应用yaml文件
kubectl create -f pipeline-run.yaml
查看pipelinerun执行结果
连接到kubernetes 确认部署的应用
root@kube001:~# kubectl get pods -l run=go-web-app
NAME READY STATUS RESTARTS AGE
go-web-app-79454cfdd7-dcz7p 1/1 Running 0 64s
查看镜像信息
root@kube001:~# kubectl get pods go-web-app-79454cfdd7-dcz7p -o jsonpath='{.spec.containers[0].image}'
registry.cn-shenzhen.aliyuncs.com/cnmirror/devops-hello-world:927ec5cc665690ad798ffbbd02a8db520692951e
Tekton 可以用来做什么?
Tekton是一个开源的、云原生的CI/CD框架,旨在为容器化环境下的持续集成和持续交付提供强大的工具和功能支持。通过使用Tekton,开发人员可以实现以下目标:
- 快速灵活定义流水线:Tekton允许开发人员通过Kubernetes云平台定义CI/CD流水线,使得构建、测试和发布应用变得更加便捷。
- 声明式配置:Tekton提供了一种声明式的方式来描述流水线中的各个步骤、任务、输入输出等信息,使得整个流程更加清晰可读。这种声明式的方式不仅简化了配置过程,还提高了配置的可维护性。
- 灵活的任务编排:Tekton通过Task资源来定义每个具体的任务,这些任务是流水线中的最小单位。开发人员可以根据需要自由组合和编排任务,使得复杂的CI/CD场景得以轻松应对。
- 可重复使用和可扩展性:Tekton实体是完全可移植和可定制的,一旦定义,组织内的任何人都可以使用给定的管道并重用其构造块。同时,Tekton社区驱动的目录(Tekton Catalog)提供了预制组件,使得开发人员可以快速创建新的并扩展现有管道。
- 标准化:Tekton在Kubernetes集群上作为扩展安装并运行,与完善的Kubernetes资源模型兼容,因此能够无缝地与容器编排系统结合使用。
此外,Tekton还提供了其他一些有用的特性,如断点调试支持、Matrix语法(用于不同参数、环境的交叉验证)以及Result API(用于管理运行任务的pod资源)等。这些特性进一步增强了Tekton的灵活性和可扩展性,使得它成为一个强大的CI/CD工具。
Tekton 和 CI/CD 框架的优劣
Tekton 优势
- 云原生集成:Tekton是为云原生环境设计的,它充分利用了Kubernetes的特性,使得在容器化环境中运行CI/CD流程变得更加高效和可靠。
- 声明式语法:Tekton使用声明式语法来定义任务和步骤,这使得配置和管理CI/CD流程变得更加简单和直观。
- 灵活性和可扩展性:Tekton允许用户自定义任务和步骤,并可以集成第三方工具和库,从而满足不同的项目需求。同时,它还支持多种任务类型,如一次性任务、周期性任务等,方便用户根据实际需求进行定制。
- 可观察性:Tekton提供了丰富的日志和指标信息,帮助用户监控和调试CI/CD流程的运行情况,从而及时发现和解决问题。
Tekton劣势
- 学习成本:对于不熟悉Kubernetes和云原生技术的团队来说,学习和使用Tekton可能会有一定的学习成本。
- 环境限制:Tekton更适用于云原生环境和Kubernetes集群,如果团队的环境或需求与这些不符,可能不太适合使用Tekton。
我该如何选择?
如果你的应用大部分都是云原生环境的应用,部署于 k8s 之上,且团队中有人对于 k8s/docker 比较熟悉,推荐使用 Tekton 来作为 CICD 的基础设施。
如果不是云原生应用,或者团队对于 k8s/docker 不够熟悉, 建议使用 Jenkins 来做 CICD。
总的来说,Tekton 为开发人员提供了一种高效、灵活和可扩展的方式来构建、测试和发布应用,从而加速软件开发和交付的过程。无论是个人项目还是大型组织,Tekton 都能为 CI/CD 流程提供强大的支持。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。