Kubernetes 测试策略实战指南:从测试金字塔到 Prow CI 作业设计
【免费下载链接】communityKubernetes Community Documentation项目地址: https://gitcode.com/GitHub_Trending/com/community
本文基于 Kubernetes Community 仓库中的 testing-strategy.md 编写,系统讲解 Kubernetes 社区在多年 CI 实践中沉淀出的测试策略方法论:如何用测试金字塔分配测试投入、如何设计 Prow 的 presubmit / postsubmit / periodic 作业、如何设置发布门禁与缓解 E2E 抖动,并给出可直接落地的 Prow CI 配置示例。读完本文,你将掌握为 Kubernetes 功能特性规划一套稳健、可维护、低抖动测试体系的核心方法,并能在自己的集群项目中复用它。
一、测试金字塔:测试投入的优先级框架
testing-strategy.md开篇即以测试金字塔作为组织测试的核心隐喻。它强调:金字塔不是死板的处方,而是一个帮助可视化的通用指南,用于构建"平衡且有效"的测试策略。金字塔自下而上分为三层,每一层都有明确的定位与取舍:
- 单元测试(Unit Tests)——金字塔的地基:要求快(一个包应当能在数秒内跑完其全部单元测试)、隔离(不依赖环境或非本地网络),覆盖单个组件。它们是成本最低、反馈最快的测试层级。
- 集成测试(Integration Tests):验证子系统内部各组件之间的交互。当需要以测试专属配置运行集群组件时,优先选择这一层。
- E2E 测试(End-to-End Tests):测试整个系统,包括与外部依赖的交互。这是最昂贵、最易抖动的层级;每一个集群组件配置变体都对应一个独立的 e2e 作业。
Kubernetes 社区在 testing.md 中为金字塔的各层提供了配套的执行入口,可以与本策略文档互为印证:
- 单元测试入口:
make test是运行全部单元测试的标准入口,它会正确设置GOPATH;遇到 timeout panic 时可通过make test KUBE_TIMEOUT="-timeout=300s"放宽超时;用WHAT限定包范围(如make test WHAT=./pkg/kubelet,追加...可覆盖子包),用KUBE_TEST_ARGS="-run ^TestValidatePod$"定位单个用例,用PARALLEL=2 ITERATION=5做压力重复运行以排查抖动,用KUBE_COVER=y生成 HTML 覆盖率报告。 - 集成测试入口:
make test-integration会通过test-integration.sh包装make test并拉起一个 etcd 实例供测试连接;依赖安装可用仓库提供的hack/install-etcd.sh,测试数据目录可用TEST_ETCD_DIR环境变量覆盖(见 integration-tests.md)。 - E2E 测试体系:基于 Ginkgo/Gomega 的 BDD 框架构建,细节见 e2e-tests.md。
注意:本仓库是 Kubernetes Community 的文档仓库,上述
make test等命令面向的是kubernetes/kubernetes主代码库。策略文档本身不限定具体工具,但给出了社区实践的权威参考。
二、Prow CI 作业类型全景
Kubernetes 的 CI 系统由Prow实现(社区文档 testing.md 中说明了 PR 提交后 Prow 会自动运行预提交测试)。testing-strategy.md将作业划分为四类,职责清晰:
| 作业类型 | 触发时机 | 核心用途 |
|---|---|---|
| Presubmit(预提交) | 代码合并前 | 把关待合入的变更 |
| Postsubmit(后提交) | 代码合并后 | 构建产物等后续任务 |
| Periodic(周期) | 按计划时间间隔 | 监控趋势、捕捉回归 |
其中Presubmit 又细分为两类,这是全文最关键的设计决策点:
- Blocking(阻塞式):测试失败即阻止合并。由于影响范围是项目级的,这类作业必须谨慎启用,社区对其稳定性、可靠性与性能要求"非常高的门槛"(high bar),并需要提供证据。
- Non-Blocking / Informational(非阻塞 / 告知式):只提供反馈,不阻止合并。
2.1 Presubmit Blocking 作业为何必须"永远运行"
文档给出了一个非常具体的理由链:阻塞式 presubmit 作业必须始终运行(must always run)。如果某次 PR 未触发该作业就合并,一旦它引入了回归,那么该作业在后续其它 PR 上运行时就会失败,导致后续 PR 即使本身值得合入也无法合并,直到回归被修复。也就是说,作业的"缺席"会演变成全项目的合并瓶颈。
2.2 Non-Blocking 作业:更早发现问题,但需约束
非阻塞作业默认不运行,通常需要显式使用/test命令触发。不过也可以配置为当特定路径被修改时自动运行(testing-strategy.md中给出了 sig-node 预提交作业配置的实例)。
为什么仍然需要非阻塞作业?因为非阻塞作业无法发现所有回归:
- 一个测试抖动(flake)在 presubmit 阶段只运行一次时可能恰好通过;
- 定义路径触发器时,不可能穷举所有可能需要跑测试的原因(例如工具链变化、依赖包升级)。
因此必须配套一个定期运行相同测试的 periodic 作业作为兜底。
非阻塞作业的核心价值在于更早暴露问题:如果没有它,维护者只能等到 periodic 作业发现回归,再回头 ping 造成问题的贡献者;若贡献者不响应,维护者被迫亲自修复。而有了非阻塞作业,负担落在 PR 失败的贡献者身上——如果他们不响应,变更就不会被合并,也就不会产生回归。
此外,对某些变更自动运行非阻塞作业还有一个收益:审查者无需记忆合并前该跑哪些作业,而且贡献者作为社区成员提交后测试会自动运行,审查者首次查看 PR 时结果已就绪。
⚠️警告:失败的非阻塞作业会迷惑不熟悉该作业或其失败模式的贡献者;若触发过于频繁,则会浪费 CI 资源。
为此,社区为"自动触发的非阻塞 presubmit 作业"制定了四条硬性准则:
- 作业负责人响应迅速,能及时处理作业问题;
- 作业必须有低失败率,避免在路过式(drive-by)PR 中制造困惑;
- 特性的重要性必须足以 justify 额外的 CI 资源消耗(取决于触发频率);
run_if_changed正则表达式必须足够窄,避免为无关变更运行作业。好的实践是将其限定在代码变更上,例如:
/(directory_a|directory_b)/.*\.go三、发布门禁:SIG Release 的 Blocking 与 Informing 作业
除了 presubmit 层面的阻塞,SIG Release 还维护着两套决定发布健康度的作业集合:Blocking与Informing。
如果你的特性或领域对发布至关重要,社区要求按照sig-release仓库中release-blocking-jobs.md的指引,将你的 periodic 作业提升为 Blocking 或 Informing。testing-strategy.md给出了两条重要的进阶约束:
- Presubmit Blocking 作业的前提条件:它本身必须同时是 Release Blocking 作业;
- 速度要求:Presubmit Blocking 作业应比 Release Blocking 作业更快——控制在 1 小时以内,最好 30 分钟以内。
文档特别强调了一个务实的升级标准:只有当我们"频繁地"在发布阻塞作业被破坏之后才识别出 bug 时,才值得把作业提升为 presubmit blocking。每个发布周期只发生几次并不构成充足理由——此时更经济的做法是使用 Testgrid 对通过/失败之间的合并提交做 bisect,然后 revert 或修复相关 PR。
原因也很直白:presubmit blocking 作业比 release blocking 作业昂贵得多——它们必须在每次推送的 PR 上运行(而不仅是已合并代码),一旦出问题会干扰所有贡献者,所以社区不会轻易新增 presubmit blocking 作业。
四、缓解 E2E 测试抖动(Flakiness)的四个手段
E2E 测试,尤其在 Kubernetes 这种复杂项目中,极易因外部依赖而抖动。testing-strategy.md给出了四个缓解方向:
- 识别模式(Identify Patterns):利用 periodic 作业与 Testgrid 监控测试结果,识别失败模式;
- 隔离失败(Isolate Failures):改进测试隔离,最小化外部因素影响;
- 重试机制(Retry Mechanisms):在 CI 作业中实现重试以处理瞬时失败。但必须清晰区分哪些是可重试错误——否则有掩盖真实 bug 的风险,而这些 bug 最终会出现在生产集群中;
- 健壮的基础设施(Robust Infrastructure):确保测试基础设施本身可靠稳定。
4.1 仓库中对"抖动治理"的更深层支撑
在同一个目录下的 flaky-tests.md 进一步定义了社区的"零抖动"(zero-flake)政策:自 2019 年起,测试作业不得对测试失败自动重试(例如 e2e 测试中不再允许ginkgo.flakeAttempts=2)。这与策略文档"明确区分可重试错误"的告诫相互呼应——自动重试会掩盖真实回归。
具体到隔离手段,e2e-tests.md 描述了测试标签体系:[Slow](超过 5 分钟)、[Serial](不能并行)、[Disruptive](可能影响无关负载,隐含串行)、[Flaky](已知抖动、默认不运行)、[Feature:.+]、[FeatureGate:.+]、[Conformance]等。在策略层面,对单个抖动用例的隔离做法是给测试名加上[Flaky],绝大多数 CI 作业会排除这些用例;对整组测试的隔离则是打上[Feature:Foo]标签并为其单独创建聚焦作业。
五、面向特定功能/领域的测试策略
如果你的关注点是 Kubernetes 内某个特定功能或领域,你有责任确保这些测试 a) 在 CI 中运行,b) 保持健康。策略文档明确划清了责任边界:
SIG Testing 提供的是运行测试的工具(tooling),但不负责运行具体的测试——运行与维护特定测试的职责在功能所有者身上。
这在本仓库的 OWNERS 中也有迹可循:sig-testing目录由sig-testing-leads作为 reviewers / approvers 把关,体现了"工具治理与功能所有权分离"的组织模式。
针对功能所有者的推荐策略分两步:
1. Periodic 作业(承担主要回归检测)
- 周期性运行昂贵的 E2E 测试(例如每 6 小时一次);
- 使用 Testgrid 监控趋势并订阅告警——Testgrid 能在 Kubernetes 其他部分的变更破坏你的特性时提供早期信号;
- 告警订阅帮助你在问题扩散前定位模式并有效排查。
2. 非阻塞 Presubmit 作业(提供软门禁)
- 通过
run_if_changed(Prow 的"基于变更触发作业"机制)配置作业仅在特定文件或目录被修改时运行; - 使用OWNERS 文件要求相关代码库维护者审批——这构成一个"软阻塞"(soft block),在不冒全项目停摆风险的前提下确保审查与问责;
- 这种方式鼓励维护者对自己代码的质量与稳定性负责。
关于 Testgrid 的进一步用法(dashboards、告警响应流程、flaky 问题上报规范),可参阅同目录的 monitoring.md。
六、可复制的 CI 配置示例:presubmit + periodic 双作业
testing-strategy.md在结尾给出了一个基础的 Prow CI 配置示例。文档提醒:实际 CI 作业配置数量庞大且依赖多个因素(原文拼写为 "facvtos",即 factors),此示例必须按你的具体需求调整。以下完整保留原文配置,并逐字段注解:
presubmits: kubernetes/my-feature: - name: my-feature-e2e-tests always_run: false # 非默认运行:仅在命中 run_if_changed 时触发 run_if_changed: 'my-feature/**/*' # 仅当 my-feature 目录下文件变更时触发 optional: true # 非阻塞(optional)作业,失败不阻止合并 decorate: true # 使用 Prow 的 decorate 模式接管容器编排 path_alias: 'kubernetes/my-feature' # 导入路径别名,保持 go import 路径一致 spec: containers: - image: gcr.io/k8s-testimages/kubekins-e2e:v20241104-master-5917669-master command: - runner.sh - ./test/e2e.sh args: # 运行带有 "MyFeature" 标签的测试,且只运行这些 -ginkgo.label-filter='Feature: containsAny MyFeature && Feature: isSubsetOf MyFeature && !Flaky' annotations: testgrid-dashboards: sig-my-feature # 结果聚合到 Testgrid 的 sig-my-feature 面板 testgrid-tab-name: My Feature E2E Tests # Testgrid 中的标签页名称 periodics: kubernetes/my-feature: - name: my-feature-periodic-e2e-tests interval: 6h # 每 6 小时运行一次,覆盖 presubmit 无法发现的回归 decorate: true path_alias: 'kubernetes/my-feature' spec: containers: - image: gcr.io/k8s-testimages/kubekins-e2e:v20241104-master-5917669-master command: - runner.sh - ./test/e2e.sh args: - -ginkgo.label-filter='Feature: containsAny MyFeature && Feature: isSubsetOf MyFeature && !Flaky' annotations: testgrid-dashboards: sig-my-feature testgrid-tab-name: My Feature Periodic E2E Tests6.1 配置要点解读
always_run: false+run_if_changed:两者组合实现了"按路径触发"的非阻塞预提交作业,对应本文第五节第 2 条策略。run_if_changed的正则必须窄到不会为无关变更触发(第五节中的/(directory_a|directory_b)/.*\.go是社区推荐形态)。optional: true:与"Blocking"相对,失败不影响合并,同时配合 periodic 兜底,正是"非阻塞作业 + 周期作业"组合的落地形态。ginkgo.label-filter:使用 Ginkgo 标签过滤,Feature: containsAny MyFeature选取包含该特性标签的用例,Feature: isSubsetOf MyFeature进一步限定只属于该特性,!Flaky排除已知抖动用例——这是社区从--ginkgo.focus/skip正则迁移到标签过滤(Kubernetes v1.29 起 Ginkgo v2 标签)后的标准写法。annotations(testgrid-dashboards / testgrid-tab-name):把作业结果投射到 Testgrid 面板,使功能所有者能持续监控趋势、订阅告警——对应第四节"识别模式"与第五节"periodic 作业监控"的要求。
从本仓库 testing.md 可以看到这一配置思想的端到端闭环:PR 提交后 Prow 自动运行预提交测试 → 测试产物(test results、junit xml、集群日志等)经Details链接可回溯排查 → Testgrid 将结果可视化为网格供持续监控 → 抖动问题按 flaky-tests.md 的规范上报与治理。
七、总结:从策略到落地的完整链路
testing-strategy.md的核心理念可浓缩为一条决策链:
- 按金字塔分配投入:单元测试求快求稳、集成测试验证子系统交互、E2E 测试作为最终信号但必须为每种集群配置变体单独建作业;
- 作业分层控制风险:presubmit blocking 必须始终运行且需高门槛审批;non-blocking 用
run_if_changed按需触发并配套 periodic 兜底;postsubmit 负责产物构建; - 发布门禁与 CI 门禁分离:Presubmit Blocking 需同时是 Release Blocking,且要更快(<1 小时,最好 <30 分钟),升级须基于"频繁"暴露 bug 的证据;
- 抖动治理靠监控与隔离而非重试:Testgrid 识别模式、明确可重试错误边界、必要时以
[Flaky]标签隔离; - 职责下沉:SIG Testing 提供工具,功能所有者负责自己测试的运行与健康,并通过 OWNERS 形成软门禁。
该文档与其配套的 testing.md、integration-tests.md、e2e-tests.md、flaky-tests.md、monitoring.md 与 verify-tests.md 共同构成了 Kubernetes 社区完整的测试知识体系,是规划任何中大型云原生项目测试策略时的优质参考。
【免费下载链接】communityKubernetes Community Documentation项目地址: https://gitcode.com/GitHub_Trending/com/community
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考