第 9/60 天
引言
在上一篇文章中,我们完成了 Tekton Pipelines 的安装部署。但安装只是第一步——真正让 Tekton 发挥价值的是编写自定义的 Task 和 Pipeline。Task 是 Tekton 中最基本的执行单元,而 Step 是 Task 中的最小操作步骤。理解 Task 与 Step 的编写方式,是掌握 Tekton 流水线开发的基础。
本文将从一个完整的实战角度出发,从零编写一个 Git 代码检出 → 单元测试 → 构建镜像的多步骤 Task,涵盖参数传递、Workspace 挂载、Results 输出等核心机制。你将学会如何像编写程序一样,结构化和可复用地编排 CI 任务。
核心概念
Task 与 Step 的关系
| 概念 | 类比 | 说明 |
|---|---|---|
| Task | 函数 | 一个可重复使用的 CI 任务单元,包含多个 Step |
| Step | 函数中的语句 | 在容器中执行的最小操作,每个 Step 运行在一个独立的容器中 |
| Pipeline | 主程序 | 编排多个 Task 的执行顺序与依赖关系 |
| PipelineRun | 函数调用 | Pipeline 的一次具体执行实例 |
Task 的关键要素
- params:定义输入参数,支持默认值,类型包括 string、array 等
- workspaces:定义共享存储卷,使多个 Step 之间可以共享文件
- results:定义输出结果,供后续 Task 消费
- steps:实际执行的操作序列,每个 Step 是一个容器
- sidecars:辅助容器,如日志收集器、代理等
Step 执行特性
- 每个 Step 运行在独立的容器中,但共享 Task 的 Pod 网络和存储
- Step 按顺序执行,前一个失败则后续不执行
- 默认容器镜像需要包含 shell 环境(如
busybox、alpine) - 可以通过
script字段嵌入多行脚本
实战步骤
步骤 1:编写第一个最简单的 Task
首先创建一个基本的 “Hello World” Task,熟悉 Task 的声明式结构:
apiVersion: tekton.dev/v1
kind: Task
metadata:
name: hello-world
namespace: tekton-pipelines
spec:
params:
- name: username
type: string
description: 被问候的用户名
default: "Tekton"
steps:
- name: greet
image: alpine:3.18
script: |
#!/bin/sh
echo "Hello, $(params.username)! Welcome to Tekton!"
echo "当前时间: $(date -u)"
创建并运行该 Task:
# 创建 Task
kubectl apply -f hello-world-task.yaml
# 创建 TaskRun 手动触发执行
kubectl create -f - <<EOF
apiVersion: tekton.dev/v1
kind: TaskRun
metadata:
name: hello-world-run
namespace: tekton-pipelines
spec:
taskRef:
name: hello-world
params:
- name: username
value: "DevOps Engineer"
EOF
# 查看执行日志
kubectl logs --selector=tekton.dev/taskRun=hello-world-run --all-containers --tail=20
步骤 2:带 Workspace 的代码检出 Task
Workspace 是 Tekton 中共享文件的机制,相当于 Pod 中的 Volume。这里的 Task 使用 Git Workspace 来检出代码:
apiVersion: tekton.dev/v1
kind: Task
metadata:
name: git-clone
namespace: tekton-pipelines
spec:
params:
- name: url
type: string
description: Git 仓库地址
- name: revision
type: string
description: 分支、标签或 commit SHA
default: "main"
- name: subdirectory
type: string
description: 检出到 Workspace 的子目录
default: ""
workspaces:
- name: source
description: 代码存放的工作区
steps:
- name: clone
image: alpine/git:2.40.1
script: |
#!/bin/sh
set -e
PARAM_URL=$(params.url)
PARAM_REVISION=$(params.revision)
PARAM_SUBDIR=$(params.subdirectory)
if [ -n "$PARAM_SUBDIR" ]; then
mkdir -p "$(workspaces.source.path)/$PARAM_SUBDIR"
cd "$(workspaces.source.path)/$PARAM_SUBDIR"
else
cd "$(workspaces.source.path)"
fi
git init
git remote add origin "$PARAM_URL"
git fetch --depth=1 origin "$PARAM_REVISION"
git checkout -f FETCH_HEAD
echo "Git 检出完成: $PARAM_URL @ $PARAM_REVISION"
git log --oneline -1
步骤 3:带 Results 输出的单元测试 Task
Results 允许 Task 输出数据供下游 Task 使用。这里创建一个运行 Go 测试并输出测试结果的 Task:
apiVersion: tekton.dev/v1
kind: Task
metadata:
name: go-unit-test
namespace: tekton-pipelines
spec:
params:
- name: package
type: string
description: 测试的 Go 包路径
default: "./..."
- name: test-flags
type: string
description: 额外的 go test 参数
default: "-v -count=1"
workspaces:
- name: source
description: 包含源代码的工作区
results:
- name: test-status
description: 测试结果状态(PASS/FAIL)
- name: test-report
description: 测试报告摘要
steps:
- name: run-tests
image: golang:1.21-alpine
script: |
#!/bin/sh
set -e
cd "$(workspaces.source.path)"
# 运行测试并捕获输出
go test $(params.package) $(params.test-flags) 2>&1 | tee /tmp/test-output.txt
# 检查测试结果
if [ ${PIPESTATUS[0]} -eq 0 ]; then
echo -n "PASS" > "$(results.test-status.path)"
echo -n "所有测试通过" > "$(results.test-report.path)"
else
echo -n "FAIL" > "$(results.test-status.path)"
head -c 4096 /tmp/test-output.txt > "$(results.test-report.path)"
exit 1
fi
步骤 4:完整的构建流水线 Task(组合使用)
现在我们将以上概念整合,编写一个真正的 CI 构建 Task:从 Git 检出代码 → 单元测试 → 使用 kaniko 构建 Docker 镜像 → 输出镜像摘要:
apiVersion: tekton.dev/v1
kind: Task
metadata:
name: build-push-image
namespace: tekton-pipelines
spec:
params:
- name: repo-url
type: string
description: Git 仓库地址
- name: revision
type: string
description: Git 分支或 tag
default: "main"
- name: image-name
type: string
description: 镜像名称(不含标签)
- name: image-tag
type: string
description: 镜像标签
default: "latest"
- name: dockerfile
type: string
description: Dockerfile 路径
default: "./Dockerfile"
- name: context
type: string
description: 构建上下文路径
default: "."
workspaces:
- name: source
description: 源代码工作区
- name: dockerconfig
description: Docker 认证配置(可选)
optional: true
results:
- name: image-digest
description: 构建的镜像摘要
- name: image-url
description: 完整的镜像 URL
steps:
- name: git-clone
image: alpine/git:2.40.1
script: |
#!/bin/sh
set -e
cd "$(workspaces.source.path)"
git init
git remote add origin "$(params.repo-url)"
git fetch --depth=1 origin "$(params.revision)"
git checkout -f FETCH_HEAD
echo "Commit: $(git log --oneline -1)"
- name: unit-test
image: golang:1.21-alpine
script: |
#!/bin/sh
set -e
cd "$(workspaces.source.path)"
go test ./... -v -count=1
echo "单元测试通过"
- name: build-and-push
image: gcr.io/kaniko-project/executor:v1.14.0
script: |
#!/bin/sh
set -e
# 构建镜像参数
DESTINATION="$(params.image-name):$(params.image-tag)"
if [ -d "$(workspaces.dockerconfig.path)" ]; then
/kaniko/executor
--context="$(workspaces.source.path)/$(params.context)"
--dockerfile="$(workspaces.source.path)/$(params.dockerfile)"
--destination="$DESTINATION"
--docker-config="$(workspaces.dockerconfig.path)/config.json"
else
/kaniko/executor
--context="$(workspaces.source.path)/$(params.context)"
--dockerfile="$(workspaces.source.path)/$(params.dockerfile)"
--destination="$DESTINATION"
fi
# 输出镜像摘要
echo -n "$DESTINATION" > "$(results.image-url.path)"
echo "镜像构建并推送完成: $DESTINATION"
步骤 5:使用 Pipeline 编排多个 Task
通过 Pipeline 将上述多个 Task 串联起来,形成完整的 CI 流水线:
apiVersion: tekton.dev/v1
kind: Pipeline
metadata:
name: ci-pipeline
namespace: tekton-pipelines
spec:
params:
- name: repo-url
type: string
- name: revision
type: string
default: "main"
- name: image-name
type: string
- name: image-tag
type: string
default: "latest"
workspaces:
- name: shared-workspace
- name: docker-credentials
optional: true
tasks:
- name: clone
taskRef:
name: git-clone
params:
- name: url
value: "$(params.repo-url)"
- name: revision
value: "$(params.revision)"
workspaces:
- name: source
workspace: shared-workspace
- name: run-tests
taskRef:
name: go-unit-test
runAfter:
- clone
workspaces:
- name: source
workspace: shared-workspace
- name: build
taskRef:
name: build-push-image
params:
- name: repo-url
value: "$(params.repo-url)"
- name: revision
value: "$(params.revision)"
- name: image-name
value: "$(params.image-name)"
- name: image-tag
value: "$(params.image-tag)"
workspaces:
- name: source
workspace: shared-workspace
- name: dockerconfig
workspace: docker-credentials
步骤 6:创建 PipelineRun 执行完整流水线
# 创建 PipelineRun
kubectl create -f - <<EOF
apiVersion: tekton.dev/v1
kind: PipelineRun
metadata:
name: ci-pipeline-run-001
namespace: tekton-pipelines
spec:
pipelineRef:
name: ci-pipeline
params:
- name: repo-url
value: "https://github.com/example/my-app.git"
- name: revision
value: "main"
- name: image-name
value: "harbor.stellardata.top/library/my-app"
- name: image-tag
value: "v1.0.0-$(date +%Y%m%d-%H%M%S)"
workspaces:
- name: shared-workspace
volumeClaimTemplate:
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
EOF
# 查看 PipelineRun 状态
tkn pr describe ci-pipeline-run-001 -n tekton-pipelines
# 实时查看日志
tkn pr logs ci-pipeline-run-001 -n tekton-pipelines -f
# 按 Task 分层查看
tkn pr logs ci-pipeline-run-001 -n tekton-pipelines -a
常见问题
Q1: Task 和 Step 执行失败时如何排查?
使用 tkn tr logs <taskrun-name> -n <namespace> -f 查看实时日志。如果 Step 失败,明确失败之前的最后一个输出。更深入的方式是 kubectl get pods -n tekton-pipelines | grep <taskrun-name> 获取 Pod 名称,然后 kubectl describe pod <pod-name> 查看容器退出状态码。
Q2: Workspace 挂载失败怎么办?
检查 Workspace 名称是否在 Task 声明中定义(workspaces 字段),且在 PipelineRun 中正确绑定。常见错误:使用了 optional: true 但未提供实际存储,而代码中又尝试访问该路径。使用 volumeClaimTemplate 自动创建 PVC 是最稳妥的方式。
Q3: 如何让 Task 支持动态参数(如构建时间戳)?
在 PipelineRun 中通过 $(params.xxx) 引用,也可以在 Shell 脚本中生成。如镜像标签中使用 $(date +%Y%m%d) 需要写成 Shell 表达式,在 Step 的 script 中直接执行即可。注意 Tekton 的变量替换只支持 $(params.xxx) 和 $(workspaces.xxx.path) 等内置变量,不支持 Shell 内联。
Q4: Step 之间的环境变量如何传递?
每个 Step 运行在独立的容器中,环境变量不共享。需要使用 Workspace 文件系统传递:Step A 写入文件,Step B 读取。或者使用 results 输出,但 results 只能存储字符串(默认 4KB 限制)。对于大文件(如构建产物),始终使用 Workspace。
Q5: Task 多个 Step 能否并行执行?
不能。同一个 Task 内的 Step 是顺序执行的。如果需要并行,需要拆分为多个 Task,在 Pipeline 中用 runAfter 或 finally 控制编排。Tekton 的并行粒度是 Task 级别,而非 Step 级别。
总结
- Task 是 Tekton 的核心构建块:一个 Task 包含多个 Step,每个 Step 运行在独立容器中,共享 Workspace 和网络
- 参数化设计提高复用性:通过
params、workspaces、results三要素,Task 可以像函数一样被任意 Pipeline 调用 - Workspace 是数据共享的基石:Git 代码、构建产物、配置信息都通过 Workspace 在 Step 之间传递
- Results 实现 Task 间通信:上游 Task 的 Results 可被下游 Task 通过
$(tasks.<task-name>.results.<result-name>)引用 - Pipeline 编排驱动完整 CI 流程:通过
runAfter控制依赖关系,finally确保清理步骤始终执行
下一篇文章我们将深入 Tekton Pipeline 的多阶段编排与依赖管理,学习如何构建更复杂的生产级 CI 流水线。















暂无评论内容