- 云原生
- CI/CD
- DevOps
- 后端
【免费下载链接】pipeline
A cloud-native Pipeline resource.
导读
本文基于 Tekton Pipeline 仓库的 Pipelines in Pipelines 官方文档,系统讲解如何在PipelineTask中通过pipelineRef或pipelineSpec引用/内嵌一个子 Pipeline,从而在单个父 Pipeline 中编排"克隆 → 安全扫描 → 通知"这类跨 Pipeline 的复合流程。读完本文你将掌握:如何启用 alpha 特性、如何编写嵌套 Pipeline 的 YAML、如何传递params与workspaces、底层控制器如何创建子PipelineRun并检测循环引用,以及当前 alpha 实现的主要限制与规避方案。
功能状态:Pipelines in Pipelines 是一个 alpha 特性,必须在
enable-api-fieldsfeature flag 中显式设置为"alpha"才能使用。启用后,引用子 Pipeline 的PipelineTask会产生一个由父PipelineRun拥有的子PipelineRun;递归的pipelineRef引用会通过沿父PipelineRun的 owner 链向上遍历来检测,并快速失败、给出明确错误。当前实现的详细限制参见本文 Known Limitations 一节。
启用 alpha 特性:enable-api-fields: "alpha"
在开始编写嵌套 Pipeline 之前,必须先将enable-api-fieldsfeature flag 调整为"alpha"。该 flag 定义在 config/config-feature-flags.yaml,仓库中的默认值是"beta":
# config/config-feature-flags.yaml enable-api-fields: "beta"将其改为"alpha"并重新应用该 ConfigMap 后,控制器才会允许在PipelineTask中指定pipelineRef或pipelineSpec。从源码验证这一点:在 pipeline_validation.go 的validateRefOrSpec中,只要检测到PipelineTask.PipelineRef或PipelineTask.PipelineSpec非空,就会调用config.ValidateEnabledAPIFields(ctx, pipelineRef, config.AlphaAPIFields)进行校验——即这两个字段被硬性绑定到 alpha 门控;只有当len(nonNilFields) == 0时才会退回 beta/stable 下只要求taskRef/taskSpec的检查。
另外,pipeline_types.go 中PipelineTask.PipelineRef的字段注释也明确指出:"This is an alpha field. You must set theenable-api-fieldsfeature flag to"alpha"for this field to be supported. When enabled, the referenced Pipeline is executed as a child PipelineRun owned by the parent PipelineRun."——与文档描述完全一致。
在PipelineTask中指定pipelineRef
在编排阶段定义嵌套 Pipeline 的最直接方式,是在PipelineTask上同时指定pipelineRef(与既有的taskRef、taskSpec并列)。下面的例子定义了一个名为security-scans的 Pipeline,它被运行在名为clone-scan-notify的父 Pipeline 内部:
apiVersion: tekton.dev/v1 kind: Pipeline metadata: name: security-scans spec: tasks: - name: scorecards taskRef: name: scorecards - name: codeql taskRef: name: codeql --- apiVersion: tekton.dev/v1 kind: Pipeline metadata: name: clone-scan-notify spec: tasks: - name: git-clone taskRef: name: git-clone - name: security-scans pipelineRef: name: security-scans - name: notification taskRef: name: notification要点说明:
- 父 Pipeline 中名为
security-scans的PipelineTask通过pipelineRef指向集群中已存在的另一个 Pipeline 对象;在父PipelineRun被调度时,控制器会为该任务创建独立的子PipelineRun(见下文"底层实现")。 - 在同一个
PipelineTask中,taskRef、taskSpec、pipelineRef、pipelineSpec四者只能且必须恰好出现一个。校验逻辑位于 pipeline_validation.go:如果同时指定多个,会报expected exactly one, got multiple;如果一个都没指定,在 alpha 模式下会报ErrMissingOneOf(taskRef, taskSpec, pipelineRef, pipelineSpec)。 - 此外,内联的
pipelineSpec与disable-inline-spec特性联动:validateEnabledInlineSpec会在disable-inline-spec包含pipeline时禁止内联pipelineSpec(见 pipeline_validation.go)。
在PipelineTask中指定pipelineSpec
如果不想预先创建独立的 Pipeline 对象,也可以把子 Pipeline 的spec直接内嵌到父 Pipeline 的PipelineTask中。将上面的pipelineRef示例改写为pipelineSpec:
apiVersion: tekton.dev/v1 kind: Pipeline metadata: name: clone-scan-notify spec: tasks: - name: git-clone taskRef: name: git-clone - name: security-scans pipelineSpec: tasks: - name: scorecards taskRef: name: scorecards - name: codeql taskRef: name: codeql - name: notification taskRef: name: notificationpipelineRef与pipelineSpec在运行时殊途同归:控制器最终都会为这个PipelineTask生成一个子PipelineRun,区别仅在于子PipelineRun的 spec 来源——前者引用现成的 Pipeline 对象,后者直接内嵌完整的PipelineSpec。从源码看,二者的创建路径完全一致,见 pipelinerun.go:
childSpec := v1.PipelineRunSpec{ TaskRunTemplate: pr.Spec.TaskRunTemplate, Params: rpt.PipelineTask.Params, Workspaces: childWorkspaces, } if rpt.PipelineTask.PipelineRef != nil { childSpec.PipelineRef = rpt.PipelineTask.PipelineRef } else { childSpec.PipelineSpec = rpt.ResolvedPipeline.PipelineSpec }指定 Parameters:向子 Pipeline 传参
嵌套 Pipeline 消费 Parameters 的方式与普通 Pipeline 中的 Task 完全一致:在PipelineTask上声明params,把父 Pipeline 的参数(或字面值)转发给子 Pipeline。以下示例把父 Pipeline 的repo参数同时传给git-clone任务和嵌套的security-scansPipeline:
apiVersion: tekton.dev/v1 kind: Pipeline metadata: name: clone-scan-notify spec: params: - name: repo value: $(params.repo) tasks: - name: git-clone params: - name: repo value: $(params.repo) taskRef: name: git-clone - name: security-scans params: - name: repo value: $(params.repo) pipelineRef: name: security-scans - name: notification taskRef: name: notification实现层面,子PipelineRun的spec.params直接取自PipelineTask.Params(见 pipelinerun.go 中Params: rpt.PipelineTask.Params),再由子 PipelineRun 按常规参数替换逻辑解析。需要注意的是:params 是目前跨 Pipeline 传递数据(包括把父 Pipeline 的 results 输入给子 Pipeline)的主要通道——因为子 Pipeline 的 Results 尚不能回传给父级(详见 Known Limitations 中的"子 Pipeline 的 Results 不会向上传播")。
指定 Workspaces:把父工作区映射给子 Pipeline
引用子 Pipeline 的PipelineTask可以像普通 Task 一样,把父 Pipeline 的工作区绑定映射到子 Pipeline 声明的工作区。PipelineTask上声明的绑定会被传播给子PipelineRun,再由子PipelineRun转发给子 Pipeline 的工作区。完整示例:
apiVersion: tekton.dev/v1 kind: Pipeline metadata: name: security-scans spec: workspaces: - name: source tasks: - name: scorecards workspaces: - name: source taskRef: name: scorecards --- apiVersion: tekton.dev/v1 kind: Pipeline metadata: name: clone-scan-notify spec: workspaces: - name: shared-ws tasks: - name: git-clone workspaces: - name: output workspace: shared-ws taskRef: name: git-clone - name: security-scans runAfter: - git-clone workspaces: - name: source workspace: shared-ws pipelineRef: name: security-scans上例中,父 Pipeline 声明了名为shared-ws的工作区;security-scans这个PipelineTask通过workspace: shared-ws把父工作区映射给子 Pipeline 声明的source工作区,并用runAfter: [git-clone]保证顺序——子 Pipeline 内的scorecards任务再通过自己的 workspace 绑定消费source。
底层解析逻辑在 pipelinerun.go 的getChildPipelineRunWorkspaces中实现:它把父PipelineRun的所有 workspace 绑定建成parentWorkspaces映射,然后对PipelineTask.Workspaces逐项解析——若ws.Workspace为空则默认使用同名绑定;PVC 与 VolumeClaimTemplate 类型的绑定还会记录父级 PVC 名,最终复用与 TaskRun 完全一致的taskWorkspaceByWorkspaceVolumeSource逻辑生成子PipelineRun的绑定。因此 TaskRun 工作区支持的 VolumeSource(PVC、VolumeClaimTemplate、ConfigMap/Secret/EmptyDir 等)在嵌套场景下同样适用。
底层实现:子 PipelineRun 的创建与循环检测
从 pipelinerun.go 的源码可以看到整个创建流程:
- 父
PipelineRun的 reconciler 在调度阶段通过createChildPipelineRuns遍历rpt.ChildPipelineRunNames,逐个调用createChildPipelineRun。 - 若该
PipelineTask使用pipelineRef,先执行detectPipelineRefCycle做循环引用检测(见下文)。 - 组装子
PipelineRun:继承父PipelineRun的TaskRunTemplate;spec.params取自PipelineTask.Params;spec.workspaces由getChildPipelineRunWorkspaces解析;pipelineRef/pipelineSpec按前文逻辑二选一写入。 - 子
PipelineRun通过OwnerReferences: []metav1.OwnerReference{*kmeta.NewControllerRef(pr)}被父PipelineRun拥有,同时带上继承自父级的 Labels/Annotations(并用子 Pipeline 的实际名称覆盖tekton.dev/pipeline标签,见 pipelinerun.go)。 - 最后通过
PipelineRuns(pr.Namespace).Create(...)创建子对象,日志会输出Creating a new child (PIP) PipelineRun object ...。
循环引用检测:detectPipelineRefCycle(pipelinerun.go)从当前PipelineRun出发,沿ownerReferences链向上遍历,每一层读取tekton.dev/pipeline标签并与目标 Pipeline 名称比对;一旦在祖先链中发现相同名称,立即返回controller.NewPermanentError,错误信息形如:
detected cycle in pipeline-in-pipeline: pipeline "security-scans" is already running in ancestor chain ...需要说明的是:该检测是 best-effort 的,且发生在子 PipelineRun 的 reconcile 阶段,而非父 Pipeline 提交时(详见 Known Limitations)。若在向上查找父PipelineRun时发生 lister 瞬时失败,错误会原样返回让控制器重试,避免误判为永久失败。
Known Limitations:当前 alpha 实现的已知限制
以下限制均为初始 alpha 实现的现状,预期在后续工作中逐步解决。
其他当前限制
when表达式不能引用子 Pipeline 的结果:父 Pipeline 中的when表达式无法引用使用了pipelineRef/pipelineSpec的PipelineTask的 results(这是下方"Results 不传播"缺口的直接推论)。timeout与retries不生效:PipelineTask上配置的timeout与retries不会应用到子PipelineRun。子 PipelineRun 使用自己的默认超时,且每个父 reconcile 周期至多创建一次。- 父级
timeouts不向下传播:父PipelineRun.Spec.Timeouts不会传播给子PipelineRun,子 PipelineRun 使用自身的默认值。 - 循环检测是 best-effort 且在子 reconcile 时进行:如上文所述,它沿
ownerReferences链并匹配tekton.dev/pipeline标签,因此循环是在出问题的子 PipelineRun 被 reconcile 时才被发现,而不是在父 Pipeline 提交时。 - 非可选子工作区校验在子 PipelineRun 处发生:若父 Pipeline 遗漏了某个非可选子工作区的绑定,失败的是子 PipelineRun 而非父 PipelineRun(跟踪问题见 pipeline 仓库 issue #9924 的注释引用,原始追踪编号为 tektoncd/pipeline#9924)。源码中对应行为见 pipelinerun.go:
getChildPipelineRunWorkspaces在父侧找不到绑定时会continue跳过,把校验责任留给子 PipelineRun。 - 执行
Status不对外暴露:使用pipelineRef/pipelineSpec的PipelineTask的 ExecutionStatus不会上抛,因此finally任务无法通过$(tasks.<pipelineTaskName>.status)判断子 Pipeline 是成功、失败还是被跳过。 - 不支持
matrix扇出:使用pipelineRef/pipelineSpec的PipelineTask不能与matrix组合以产生多个子PipelineRun。
子 Pipeline 的 Results 不会向上传播
子 Pipeline 产生的Results不会出现在父PipelineRun上:
- 不会被聚合进父
PipelineRun的status.results; - 无法被其他
PipelineTask通过$(tasks.<task-name>.results.<result-name>)消费,也无法在父 Pipeline 的when表达式中使用。
为保证这一点,Pipeline 的 validating webhook 会在编写阶段拒绝任何指向使用了pipelineRef/pipelineSpec的PipelineTask的结果引用,报错形如:
result reference to pipelineTask "child" is not supported: referenced task uses pipelineRef or pipelineSpec and result propagation from child Pipelines is not yet implemented
该校验的实现位于 pipeline_validation.go 的validatePipelineRefResultReferencesDisallowed:它收集所有使用pipelineRef/pipelineSpec的PipelineTask名称,再对tasks与finally中所有PipelineTaskResultRefs逐一比对,命中即报错。函数注释解释了原因:reconciler 的convertToResultRefs路径从ResolvedPipelineTask.TaskRuns读取结果,而对子 Pipeline 任务该字段为空,若不加此护栏,这类引用会在控制器运行时引发 panic。
规避方案:在结果传播实现落地之前,请通过PipelineTask上的params把值传入子 Pipeline;父 Pipeline 自身的results继续由普通TaskRun产生。
小结与建议
Pipelines in Pipelines 为 Tekton 提供了"编排 Pipeline 的 Pipeline"能力:通过pipelineRef或pipelineSpec,可以把一段成熟的流水线(如安全扫描、合规检查)作为一个整体步骤嵌入更大的编排流程,实现 Pipeline 的复用与分层组合。当前 alpha 阶段的核心约束是子 Pipeline 的 Results 与执行状态无法回传、超时/重试/matrix 不支持,因此它最适合"子 Pipeline 只做副作用型工作(写入 workspace、产出 artifacts),父级流程用 params 单向驱动"的场景;需要跨层级传递结果时,应先用 params 传递输入、用 TaskRun 产出父级 results。
想继续深入,可以阅读以下仓库资源:
- 官方文档:docs/pipelines-in-pipelines.md
- 类型定义(
PipelineTask.PipelineRef/PipelineSpec/Workspaces/Params):pkg/apis/pipeline/v1/pipeline_types.go - 校验逻辑(alpha 门控、ref-or-spec 互斥、结果引用护栏):pkg/apis/pipeline/v1/pipeline_validation.go
- 控制器实现(子 PipelineRun 创建、工作区解析、循环检测):pkg/reconciler/pipelinerun/pipelinerun.go
- Feature flag 默认配置:config/config-feature-flags.yaml
- 云原生
- CI/CD
- DevOps
- 后端
【免费下载链接】pipeline
A cloud-native Pipeline resource.
相关推荐
Tekton Pipelines实战:构建多阶段Pipeline并触发PipelineRun的10个技巧
Tekton Pipelines实战:构建多阶段Pipeline并触发PipelineRun的10个技巧 Tekton Pipelines 是一个云原生 CI/
云原生CI/CDDevOps后端Tekton Pipeline 深入指南
Tekton Pipeline 深入指南 1. 项目介绍 Tekton Pipeline https://github.com/tektoncd/pipelin
云原生CI/CDDevOps后端在 Tekton Pipeline 中运行 Testcontainers:Docker-in-Docker Sidecar 完整配置指南
在 Tekton Pipeline 中运行 Testcontainers:Docker in Docker Sidecar 完整配置指南 Testcontain
测试容器运行时
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考