ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Kubernetes kubectl 命令行集成测试套件(test/cmd)完全指南:运行、筛选与扩展

2026/9/8 23:29:29 拓冰建站 浏览量
Kubernetes kubectl 命令行集成测试套件(test/cmd)完全指南:运行、筛选与扩展 Kubernetes kubectl 命令行集成测试套件test/cmd完全指南运行、筛选与扩展【免费下载链接】kubernetesProduction-Grade Container Scheduling and Management项目地址: https://gitcode.com/GitHub_Trending/kuber/kubernetes导读本文围绕 Kubernetes 仓库中 test/cmd/README.md 所述的kubectl 命令行集成测试套件command-line integration test-suite展开。该套件不依赖 Docker在本地拉起一套最小的 kube-apiserver kube-controller-manager etcd 环境用真实构建出的二进制对kubectl的各类命令做端到端行为验证。读完本文你将掌握如何用make test-cmd一键跑完全量命令测试、如何用WHAT参数只跑 Deployment/Impersonation 等单个功能用例、测试背后源码级的编排与断言机制以及如何按项目约定新增一条run_*_tests测试并把它挂接到套件中。一、这是一套什么样的测试test/cmd目录存放的是 Kubernetes 对kubectl 命令行做集成测试的 Shell 套件。它的定位在 hack/make-rules/test-cmd.sh 的注释里写得很清楚This command checks that the built commands can function together for simple scenarios. It does not require Docker.也就是说这套测试的目标是验证“构建出来的 kubectl、kube-apiserver、kube-controller-manager 能协同工作完成简单场景”它不要求 Docker也不需要真实集群而是通过一个脚本在当前机器上临时拉起最小控制面。从目录结构看test/cmd下除README.md、OWNERS外是 35 个按功能划分的 Shell 测试文件 1 个总装配文件legacy-script.sh文件覆盖的功能域从其命名可推断apply.sh、create.sh、delete.sh、run.sh、scale.sh、wait.sh、save-config.shkubectl 的对象生命周期与运维命令get.sh、diff.sh、results.sh、template-output.sh、version.sh查询、输出格式化与模板渲染apps.sh、batch.sh、core.sh、storage.sh、node-management.shDeployment/RS、Job/CronJob、Pod 等各类资源authorization.sh、authentication.sh、auth_whoami.sh、rbac.sh、certificate.sh认证、授权、证书类场景crd.sh、discovery.sh、proxy.sh、exec.sh、debug.sh、plugins.sh、events.sh、kuberc.sh、kubeconfig.sh、convert.sh、request-timeout.sh、generic-resources.sh、help.shkubectl 其余子命令与配置体系其中core.sh是最典型的样例它的run_pod_tests()见 test/cmd/core.sh完整演示了“前置条件断言 → 执行命令 → 后置条件断言”的写法# Runs all pod related tests. run_pod_tests() { set -o nounset set -o errexit kube::log::status Testing kubectl(v1:pods) ### Create POD valid-pod from JSON # Pre-condition: no POD exists create_and_use_new_namespace kube::test::get_object_assert pods {{range.items}}{{$id_field}}:{{end}} # Command kubectl create ${kube_flags[]} -f test/fixtures/doc-yaml/admin/limitrange/valid-pod.yaml # Post-condition: valid-pod POD is created kubectl get ${kube_flags[]} pods -o json kube::test::get_object_assert pods {{range.items}}{{$id_field}}:{{end}} valid-pod: ... }二、运行测试1. 全量运行make test-cmd在仓库顶层执行make test-cmdMakefile 中定义# make test-cmd # make test-cmd WHATdeployment impersonation .PHONY: test-cmd test-cmd: hack/make-rules/test-cmd.shmake test-cmd实际只是对hack/make-rules/test-cmd.sh的薄封装。按 test/cmd/README.md 的说法运行该目标会导入source每一个包含测试函数的文件即依次加载legacy-script.sh中列出的全部 35 个功能测试文件然后顺序执行其中的每一个run_*_tests。整个脚本的编排分为三段见 hack/make-rules/test-cmd.sh拉起最小控制面依次执行setup构建 kubectl 并配置 kubeconfig、run_kube_apiserver构建并启动 kube-apiserver绑定127.0.0.1的安全端口启用LimitRanger,ResourceQuota准入插件、关闭一批如ServiceAccount/DefaultStorageClass的插件鉴权模式为RBAC,AlwaysAllowetcd 走本机http://${ETCD_HOST}:${ETCD_PORT}、run_kube_controller_manager、create_node手动创建一个名为127.0.0.1的 Node 对象——因为测试环境并不真正运行 kubelet。启动前还会通过openssl genrsa生成 ServiceAccount 签名密钥并设置KUBE_CACHE_MUTATION_DETECTOR、KUBE_PANIC_WATCH_DECODE_ERROR等调试开关。声明资源全集export SUPPORTED_RESOURCES(*)表示 apiserver 支持全部资源测试不受资源过滤约束。执行测试调用runTests由legacy-script.sh里的record_command逐个运行测试函数。脚本内置的多级健康检查如kube::util::wait_for_url_with_bearer_token .../healthz会等待 apiserver 真正就绪后才开始跑用例全部通过后打印TESTS PASSED。备注脚本有严格的 shell 选项set -o errexit/nounset/pipefail。文件 test/cmd/legacy-script.sh 中特别注释不要把runTests放进子 Shell 捕获输出例如output$(runTests)否则会抑制errexit在内部的生效导致断言失败无法正确传导。2. 只跑部分用例make test-cmd WHAT...当只想调试某几个功能时用WHAT参数make test-cmd WHATdeployment impersonation这条命令会精确执行run_deployment_tests与run_impersonation_tests二者分别定义在 apps / authorization 相关测试文件中。其实现机制在 test/cmd/legacy-script.shif [[ -n ${WHAT-} ]]; then for pkg in ${WHAT} do # running of kubeadm is captured in hack/make-targets/test-cmd.sh if [[ ${pkg} ! kubeadm ]]; then record_command run_${pkg}_tests fi done cleanup_tests return fi值得注意的两点WHAT的取值会被拼成run_${pkg}_tests这正是 README 要求“测试函数必须命名为run_*_tests”的原因。README 明确指出运行指定用例时不会再去校验被测资源是否在服务端可用。对比全量模式中大量出现的kube::test::if_supports_resource ${replicasets}守卫见后文WHAT模式下这些存在性校验会被跳过直接执行目标函数。例外项kubeadm不会经由该循环执行其测试由 hack/make-rules/test-cmd.sh 体系外的hack/make-targets/test-cmd.sh另行捕获这是代码中的既有约定。三、测试执行引擎与断言机制源码级解读1.runTests与SUPPORTED_RESOURCESrunTests()见 test/cmd/legacy-script.sh是套件的主调度器。它首先检查环境变量if [ -z ${SUPPORTED_RESOURCES:-} ]; then echo Need to set SUPPORTED_RESOURCES env var. It is a list of resources that are supported and hence should be tested. Set it to (*) to test all resources exit 1 fi随后准备全局 kubectl 调用参数数组kube_flags默认带上-s https://127.0.0.1:${SECURE_API_PORT}、--insecure-skip-tls-verify并在启用ALLOW_SKEW之前附加--match-server-version用于强制客户端与服务端版本一致防止版本偏移造成误判。之后还会以只读导出的形式定义一批 go-template 字段变量如id_field.metadata.name、rc_replicas_field.spec.replicas供各个测试函数引用避免字符串拼写错误。2.record_command一次调用 一个 JUnit 用例单个测试函数不是被裸调用的而是通过record_command包装见 test/cmd/legacy-script.sh# record_command runs the command and records its output/error messages in junit format # it expects the first to be the name of the command function record_command() { set o nounset set o errexit local name$1 local output${KUBE_JUNIT_REPORT_DIR:-/tmp/junit-results} echo Recording: ${name} echo Running command: $* juLog -output${output} -classtest-cmd -name${name} $ local exitCode$? if [[ ${exitCode} -ne 0 ]]; then if [ ${name} ! record_command_canary ]; then echo Error when running ${name} foundError${foundError}${name}, fi ... }关键点JUnit 报告它调用第三方库third_party/forked/shell2junit/sh2ju.sh中的juLog把每个测试函数包装成一条-classtest-cmd、名为函数名的 JUnit 用例输出目录取KUBE_JUNIT_REPORT_DIR默认/tmp/junit-results。而 legacy-script.sh 中还约定若KUBE_JUNIT_REPORT_DIR未设置但ARTIFACTS已设置CI 常见则把报告目录重定向到ARTIFACTS。失败聚合任何函数返回非零都会被记入foundError字符串测试全部跑完后由cleanup_tests()统一echo FAILED TESTS: ...并exit 1。副作用提示record_command在子 Shell 中执行命令所以命令内部的变量改动在返回后不会生效——这是函数头注释里明确提醒的。3.record_command_canary金丝雀自检在真正跑测试前legacy-script.sh会先执行一次自检见 test/cmd/legacy-script.shfoundError function record_command_canary() { set -o nounset set -o errexit bogus-expected-to-fail ... } KUBE_JUNIT_REPORT_DIR$(mktemp -d /tmp/record_command_canary.XXXXX) record_command record_command_canary if [[ -n ${foundError} ]]; then echo FAILED TESTS: record_command_canary exit 1 fi这是一个回归守护因为record_command会临时关闭errexit/nounset容易掩盖被调用函数内部真实的失败。金丝雀用例故意执行一个不存在的命令bogus-expected-to-fail如果框架能正确感知这次失败并把名字记入foundError则说明错误传播链路完好参见代码注释引用的 k8s issue 84871反之如果金丝雀意外成功或失败未被捕获脚本立即中止。4. 资源能力守卫kube::test::if_supports_resource全量模式中每个功能块执行前都会做一次资源能力判断。该函数定义在 hack/lib/test.sh# Returns true if the required resource is part of supported resources. # Expects env vars: # SUPPORTED_RESOURCES: Array of all resources supported by the apiserver. * # means it supports all resources. For ex: (*) or (rc *) both mean that # all resources are supported. # $1: Name of the resource to be tested. kube::test::if_supports_resource() { SUPPORTED_RESOURCES${SUPPORTED_RESOURCES:-} REQUIRED_RESOURCE${1:-} for r in ${SUPPORTED_RESOURCES[]}; do if [[ ${r} * || ${r} ${REQUIRED_RESOURCE} ]]; then return 0 fi done return 1 }其语义是只要SUPPORTED_RESOURCES数组中包含*或与传入资源同名就返回 0支持。默认全量模式下该数组为(*)因此所有分支都会执行当通过环境变量收缩SUPPORTED_RESOURCES模拟受限 apiserver时不支持的资源对应测试组会被自动跳过这是 README 强调“新增用例必须用if_supports_resource做守卫”的根源。5. 丰富的断言助手hack/lib/test.sh为用例提供了一整套断言函数是run_*_tests内部的主要“武器”kube::test::get_object_asserthack/lib/test.sh对对象执行一次kubectl get go-template断言输出与期望完全一致。kube::test::wait_object_asserthack/lib/test.sh同样的断言但会重试最多 10 次用于等待异步收敛如副本数就绪。kube::test::get_object_jsonpath_assert/kube::test::describe_object_assert等分别用 jsonpath 模板、describe输出做断言。kube::test::if_has_string/if_has_not_stringhack/lib/test.sh断言输出字符串包含/不包含某子串成功打印绿色Successful、失败打印红色FAIL!并回溯调用点。kube::test::clear_allhack/lib/test.sh清理所有 RC/Pod--grace-period0 --force在每个 WHAT 批次结束后被cleanup_tests调用保证用例之间互不污染。此外test/cmd/legacy-script.sh中还内置了wait-for-pods-with-label带标签轮询等待 Pod 名字完全匹配、kubectl-with-retry对 “the object has been modified” 冲突做最多 4 次指数退避重试对应 issue #15333、start-proxy/stop-proxy/check-curl-proxy-code对kubectl proxy端到端验证等工具函数这些都在各用例文件里被高频复用。四、新增一个测试用例README 给出了新增用例的标准流程这里结合源码把每一步拆开讲透。1. 按约定命名测试函数所有测试函数必须形如run_*_tests否则无法被make test-cmd WHAT...单独寻址。全量模式下函数实际由 legacy-script.sh 里runTests的record_command run_xxx_tests逐行调用。2. 在legacy-script.sh中插入调用段假设新增的是 ReplicaSet 相关测试run_rs_tests按照 README 示例在 test/cmd/legacy-script.sh 的runTests函数体全量执行序列位于WHAT分支之后中找到对应的资源分组位置插入如下片段###################### # Replica Sets # ###################### if kube::test::if_supports_resource ${replicasets} ; then record_command run_rs_tests fi注意两点约定必须用kube::test::if_supports_resource守卫校验该测试依赖的资源如replicasets是否被服务端支持。这样即便 apiserver 不支持该资源类型也不会让整条流水线失败。legacy-script.sh顶部已为常见资源定义了防拼写错误的变量replicasetsreplicasets等建议复用而非手写字面量。必须用record_command包装而非裸调函数这样失败才能被foundError聚合、并被juLog写入 JUnit 报告。全量序列的分组粒度可参考既有组织方式Cluster Role、Role、Assert short name、Assert singular name、Ambiguous short name、Explain crd等见 test/cmd/legacy-script.sh每组前都有####注释分隔条。一个测试组内也可根据能力组合放行多个用例例如短名相关测试需要同时支持 CRD 与 Pod 才执行if kube::test::if_supports_resource ${customresourcedefinitions} kube::test::if_supports_resource ${pods} kube::test::if_supports_resource ${configmaps} ; then record_command run_assert_short_name_tests fi3. 如果测试放在新文件中若用例写在一个全新的文件里例如foo.sh必须在 legacy-script.sh 顶部的 source 列表中新增一行且保持字母序source ${KUBE_ROOT}/test/cmd/apply.sh source ${KUBE_ROOT}/test/cmd/apps.sh source ${KUBE_ROOT}/test/cmd/authorization.sh source ${KUBE_ROOT}/test/cmd/batch.sh ... source ${KUBE_ROOT}/test/cmd/foo.sh # 按字母序插入例如在 exec.sh 与 generic-resources.sh 之间该 source 列表决定文件加载顺序README 明确要求source 顺序保持字母序Please keep the order of the source list alphabetical便于 review 与维护。4. 用例文件内部需要遵守的既有约定从core.sh、legacy-script.sh等文件可以归纳出新增用例时应遵循的硬性约束文件头部包含 Kubernetes Apache License 版权头项目内有 boilerplate 校验脚本强制检查。测试函数体首行恢复set -o nounset/set -o errexit确保函数内部命令失败能被即时捕获见 core.sh。顶层设置export LANGC见 legacy-script.sh保证 kubectl 返回英文提示断言不受本机 locale 影响。涉及命名空间隔离的用例先调用create_and_use_new_namespace见 legacy-script.sh生成带时间戳的随机命名空间并切换到当前 context避免用例间相互踩踏。所有 kubectl 调用统一带${kube_flags[]}以及需要认证时加 token 的${kube_flags_with_token[]}把“连到哪个 apiserver、是否校验服务端版本”等公共参数收敛在调度器里。尽量复用hack/lib/test.sh提供的断言助手与重试语义不要手写脆弱的grep裸判断。五、理解测试结果与常见运行细节1. JUnit 报告与失败排查全量运行时每次record_command都会把该函数的标准输出/错误按 JUnit 格式记录到KUBE_JUNIT_REPORT_DIR默认/tmp/junit-results若设置了 CI 常用的ARTIFACTS则自动跟随。这使套件可以直接被 GitLab/Jenkins 等 CI 的 JUnit 解析器消费。某条用例失败时终端会打印FAIL!、断言的消息与期望值以及调用点kube::test::if_has_string内部通过caller回溯整轮结束后汇总为一行FAILED TESTS: xxx, yyy,并以非零码退出。2. 可调环境变量速查结合源码套件暴露给调用者的常用环境变量有变量作用默认值源码内WHAT指定要运行的测试名不含run_前缀与_tests后缀空格分隔多个空 全量SUPPORTED_RESOURCES服务端支持资源集合(*)表示全部()runTests要求必须显式设置KUBE_JUNIT_REPORT_DIRJUnit 报告输出目录/tmp/junit-resultsARTIFACTSCI 构件目录在未设上述变量时充当报告目录未设置ALLOW_SKEW非空时跳过--match-server-version版本一致性校验空SERVICE_ACCOUNT_KEYServiceAccount 签名私钥路径/tmp/kube-serviceaccount.keySECURE_API_PORTapiserver 安全端口6443ETCD_HOST/ETCD_PORT本机 etcd 地址127.0.0.1/23793. 两个容易被踩的坑资源清理是强制的每批WHAT测试结束都会走cleanup_tests→kube::test::clear_all强删 RC/Pod若某用例结束后foundError非空则直接失败退出。因此新增用例应自建命名空间并在断言中等待删除完成避免把脏数据留给后续用例。不要在子 Shell 中包装runTests如第一节所述hack/make-rules/test-cmd.sh第 182-183 行的注释以# WARNING强调此事否则errexit被抑制会导致失败静默吞掉。六、小结从文档到源码的完整脉络test/cmd/README.md虽然简短但它描述的是 Kubernetes 质量保障体系里承上启下的关键一环上层由 Makefile 的test-cmd目标与WHAT参数提供入口中层由 hack/make-rules/test-cmd.sh 负责“构建并拉起最小控制面 设置环境变量 调用runTests”执行层由 test/cmd/legacy-script.sh 完成 35 个测试文件的字母序 source、record_command的 JUnit 包装与金丝雀自检断言底座则落在 hack/lib/test.sh 的if_supports_resource、object_assert、if_has_string等函数上。对想要贡献 kubectl 测试的开发者而言只要遵循“函数命名run_*_tests→ 用if_supports_resource守卫 → 用record_command包装 → 新文件按字母序 source”这套约定新增的用例就能无缝并入make test-cmd流水线并在本地无需 Docker 的最小控制面上得到验证。【免费下载链接】kubernetesProduction-Grade Container Scheduling and Management项目地址: https://gitcode.com/GitHub_Trending/kuber/kubernetes创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考