☰
Tekton Pipeline `nop` 镜像深度解析:优雅终止 Sidecar 与 Affinity Assistant 常驻容器的内部实现
2026/9/25 15:23:38 网站建设 项目流程
  • 云原生
  • CI/CD
  • DevOps
  • 后端

【免费下载链接】pipeline

A cloud-native Pipeline resource.

项目地址:https://gitcode.com/gh_mirrors/pipelin/pipeline
点击查看免费下载

nop是 Tekton Pipeline 内部使用的一个“最小化空操作”镜像,承担两项关键职能:在 TaskRun 全部 step 执行完毕后优雅终止 Sidecar 容器,以及为 Affinity Assistant 提供“只存在、不做事”的常驻容器以支撑 Workspace 亲和性调度。本文将结合仓库源码与控制器配置,从镜像设计动机、Sidecar 停止的 Patch 机制、tekton_run_indefinitely特殊参数,到镜像的注入与校验方式,完整还原这一“小而关键”组件的底层原理。

一、nop镜像是什么:一个服务于两个内部职能的最小镜像

按照 cmd/nop/README.md 的定义,nop镜像只负责 Tekton 的两项内部功能:

  1. 停止 Sidecar 容器(Stopping sidecar containers);
  2. 支撑 Affinity Assistant StatefulSet(Affinity Assistant StatefulSet)。

设计上刻意保持镜像极小,原因有二:一是优化镜像拉取延迟(image pull latency),二是为潜在攻击者呈现最小攻击面(minimal surface)。由于该镜像会被批量注入到每个带有 Sidecar 的 TaskRun Pod 以及每个启用亲和性助手的 PipelineRun 中,镜像体积直接关系到集群调度与拉取开销;同时因为它可能被调度到任意命名空间运行,最小化攻击面同样是安全考量的一部分。

从构建配置可以看到它的真实身份:config/controller.yaml 中控制器通过-nop-image参数声明ko://github.com/tektoncd/pipeline/cmd/nop,并在ko resolve阶段替换为按 digest 引用的镜像地址。它和entrypoint、sidecarlogresults、workingdirinit一起,构成了 Tekton 在 Pod 内执行任务所需的全部内部镜像集合。

二、职能一:优雅终止 Sidecar 容器

2.1 为什么需要“终止 Sidecar”

在 Kubernetes 中,Sidecar 容器与主容器共享生命周期——只要 Sidecar 还在运行,Pod 就不会进入Completed状态。Tekton 的任务执行模型是“步骤串行、Sidecar 并行”,因此当 TaskRun 中所有 step 都执行完毕时,必须主动把仍在运行的 Sidecar 停下来,否则 TaskRun 永远不会结束。

Tekton 采用的做法(见 cmd/nop/README.md):把 Sidecar 容器的image替换成一个“无论传入什么参数都会立即以 0 退出”的镜像,从而实现优雅停止。

2.2 底层的 JSON Patch 实现

停止动作的源码实现在 pkg/pod/entrypoint.go:

  • buildSidecarStopPatch(pkg/pod/entrypoint.go#L274-L309)遍历 Pod 的Status.ContainerStatuses,凡是“不是 step 容器”且“状态为 Running”的容器,就在 spec 中找到对应索引,生成一条replace操作,把/spec/containers/{index}/image替换为nopImage;
  • StopSidecars(pkg/pod/entrypoint.go#L333-L367)负责拉取 Pod、仅在 Pod 处于Running阶段时才构造 Patch 并通过 K8s client 以JSONPatchType提交,无待停止 Sidecar 时直接返回。

值得注意的是 Patch 构建中的两个防御性细节:

  • 不能简单通过容器名是否带sidecar-前缀来判断(注入的 Sidecar 容器可能没有该前缀),因此代码用!IsContainerStep(s.Name)判断;
  • 当 feature flagResultExtractionMethod == sidecar logs时,由控制器注入的sidecar-log-results容器会被跳过,让它自然优雅退出,而不是被强杀(pkg/pod/entrypoint.go#L283-L285)。

2.3 调用链:从 TaskRun 完成到 Sidecar 停止

TaskRun 控制器在任务进入完成阶段时调用stopSidecars(pkg/reconciler/taskrun/taskrun.go#L466-L494):

  1. 若 TaskRun 尚无关联 Pod,或已处于Cancelled/TimedOut状态(此时 Pod 已在failTaskRun中被删除),则跳过;
  2. 调用podconvert.StopSidecars(ctx, c.Images.NopImage, c.KubeClientSet, tr.Namespace, tr.Status.PodName),即上一节描述的 Patch 流程;
  3. 若 Patch 后仍有 SidecarStatus 显示为 Running,则依据 Pod 的最新ContainerStatuses更新 TaskRun 的 Sidecar 状态。

单元测试 pkg/pod/entrypoint_test.go 中的TestStopSidecars对这一行为做了完整验证:构造包含普通 Sidecar 与注入 Sidecar 的 Pod,断言 Patch 之后两个容器的镜像均被替换为nop-image。

2.4 重要注意事项:指定了command的 Sidecar

原文档特别标注了一条NB警告:如果 Sidecar 容器自己指定了command,那么替换镜像后**nop二进制并不会被调用**(K8s 会执行容器的自定义 command),容器可能以非零退出码退出。Tekton 不会把这种情况判定为 TaskRun 失败,但可能产生多余的日志或指标噪音。这是用户在自定义 Sidecar 时应当规避的用法。

2.5nop二进制的退出行为

nop/main.go 的实现非常直白:

func main() { if len(os.Args) >= 2 && os.Args[1] == "tekton_run_indefinitely" { log.Println("Waiting indefinitely...") ch := make(chan os.Signal, 1) signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM) log.Println("received signal:", <-ch) } log.Println("Exiting...") os.Exit(0) }

除tekton_run_indefinitely之外的任何参数(甚至无参数)都会走完main直接os.Exit(0)立即退出——这正是 Sidecar 停止场景需要的“即插即止”语义。

三、职能二:Affinity Assistant StatefulSet 的常驻容器

3.1 Affinity Assistant 为什么需要“一直存在”的容器

Affinity Assistant 是 Tekton 实现 workspaces 的关键机制:它为 PipelineRun 创建一个 KubernetesStatefulSet,让 PV 被调度到同一个可用区,从而避免多节点间共享 PVC 的调度冲突。这个 StatefulSet 中的容器“不需要做任何事,只需要存在”——它的意义是占位并保持运行。

3.2 特殊参数tekton_run_indefinitely

为了让容器无限期运行,Affinity Assistant 向nop镜像传入唯一识别串tekton_run_indefinitely(cmd/nop/README.md)。nop主程序识别到该参数后进入“Waiting indefinitely...”状态,注册SIGINT/SIGTERM信号监听,收到信号后打印并退出。这意味着它的生命周期完全由 Kubernetes 或运维人员通过信号控制。

该参数的注入位置在 pkg/reconciler/pipelinerun/affinity_assistant.go#L375-L395:

containers := []corev1.Container{{ Name: "affinity-assistant", Image: containerConfig.Image, Args: []string{"tekton_run_indefinitely"}, // Set requests == limits to get QoS class _Guaranteed_. Resources: corev1.ResourceRequirements{ Limits: corev1.ResourceList{ "cpu": resource.MustParse("50m"), "memory": resource.MustParse("100Mi"), }, Requests: corev1.ResourceList{ "cpu": resource.MustParse("50m"), "memory": resource.MustParse("100Mi"), }, }, ... }}

容器名为affinity-assistant,replica 数固定为 1(单例),并把各 Workspace 的 PVC 以 VolumeMount 挂载进去;资源上刻意让requests == limits(50m CPU / 100Mi 内存),从而获得 Kubernetes 的GuaranteedQoS 等级,确保这个占位容器不会被轻易驱逐——从源码结构看,这是对“只存在”这一需求在调度层面的保障。

3.3 两种职能如何在一个镜像中共存

两个职能看似矛盾(一个要求“立即退出”,一个要求“永远运行”),nop通过参数判别将它们统一:默认路径立即以 0 退出,命中tekton_run_indefinitely则常驻等待信号。整份 main 函数不足 20 行,正是“最小化、单一职责”设计哲学的体现。

四、镜像的配置、注入与校验

nop镜像不是硬编码在业务代码里的字符串,而是作为控制器启动参数注入并纳入校验体系:

  • 启动参数:控制器 Deployment 通过-nop-image指定(config/controller.yaml#L80),使用ko://引用以便ko resolve按 digest 替换;
  • 统一管理:Images结构体把NopImage与EntrypointImage、ShellImage等一起管理(pkg/apis/pipeline/images.go#L26-L41),注释明确其语义为“用于终止 Sidecar 的容器镜像”;
  • 启动校验:Images.Validate()会在任一镜像为空时返回错误(pkg/apis/pipeline/images.go#L44-L64),防止控制器在镜像缺失的情况下带病启动。

由于镜像引用最终以 digest 形式固化在控制器镜像清单中,部署时无需额外拉取,Sidecar 停止阶段只需将 Pod spec 中的 image 字符串替换为已缓存的nop镜像即可,这进一步解释了为什么“极小体积”对整体性能如此重要。

五、小结

nop镜像虽然只承担“立即退出”与“无限期等待”两个简单行为,却是 Tekton 任务生命周期收尾(Sidecar 优雅停止)与 Workspace 亲和性调度(Affinity Assistant)得以成立的基础设施。理解它的关键在于三件事:

  1. Sidecar 停止本质是用 JSON Patch 替换镜像,让容器以 0 码即刻退出;
  2. tekton_run_indefinitely是一个信号驱动的常驻模式,服务于“只存在不做事”的占位容器;
  3. 镜像通过控制器-nop-image参数注入、统一校验,并保持最小体积以兼顾拉取延迟与攻击面。

相关核心源码与测试均可在仓库中继续深入:cmd/nop/main.go、pkg/pod/entrypoint.go、pkg/reconciler/taskrun/taskrun.go、pkg/reconciler/pipelinerun/affinity_assistant.go 与 pkg/pod/entrypoint_test.go。

  • 云原生
  • CI/CD
  • DevOps
  • 后端

【免费下载链接】pipeline

A cloud-native Pipeline resource.

项目地址:https://gitcode.com/gh_mirrors/pipelin/pipeline
点击查看免费下载
上一篇:QuickLook 插件终极指南:macOS 空格键快速预览文件的完整配置攻略
下一篇:抖音批量下载工具:从单条视频到整站主页的完整使用指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询