Kubernetes 集群长期稳定性验证:serve_hostnames Soak Test 源码级解析与实战指南 Kubernetes 集群长期稳定性验证serve_hostnames Soak Test 源码级解析与实战指南【免费下载链接】kubernetesProduction-Grade Container Scheduling and Management项目地址: https://gitcode.com/GitHub_Trending/kuber/kubernetes导读本文聚焦 Kubernetes 官方仓库中面向「持续健康巡检」场景的浸泡测试Soak Test工具serve_hostnames完整解析其测试原理、参数体系与输出解读。读者可以基于本文在 GCE 集群上复现该测试理解它如何通过每节点放置 Pod 经由 Service 代理反复查询 校验所有 Pod 均响应的机制暴露集群中失联节点并掌握区分性能测试与存活浸泡测试的关键界限。一、serve_hostnames 是什么为集群还活着吗而生的持续测试serve_hostnames位于 test/soak/serve_hostnames其源码文件为 serve_hostnames.go配套 Makefile 与本文所述的 README.md。从源码注释可以看出它的设计初衷非常直白This soak tests places a specified number of pods on each node and then repeatedly sends queries to a service running on these pods.也就是说它不是一个功能回归测试也不是基准测试而是一种持续运行的健康证明只要集群还活着、调度还正常、Service 与端点还能联通这个测试就能反复通过一旦某个节点或链路失效就会立刻在日志中暴露出来。在 Kubernetes 的测试体系中它归属于test/soak/目录与test/e2e、test/integration等并列代表官方认可的长时间浸泡型验证手段与一次性执行的端到端测试形成互补。二、测试工作原理七个连续动作拆解当配合 GCE provider 运行时serve_hostnames会顺序执行以下动作参见 README.md 与源码 serve_hostnames.go 的main()建立连接基于当前 kubeconfig 上下文$HOME/.kube/config与集群 master 建立连接枚举节点列出集群中所有节点假设数量为N逐个放置 Pod在每个节点上创建M个 Pod默认M1。Pod 内运行serve_hostnames镜像——对一次GET请求会回传自身 Pod 名作为响应。注意这些 Pod 是逐个单独创建的不通过 ReplicationController/Deployment 管理命名带有N-M后缀标识第N个节点上的第M个 Pod创建 Service创建一个映射到这些 Pod 的 Serviceselector 为name: serve-hostname端口 9376/TCP并发查询程序执行I轮默认 1 轮每轮经由 master 上的Service 代理接口services/proxy发起Q×N×M次查询Q默认 10结果校验验证每个 Pod进而每个节点至少响应了一次查询平均应约为Q次报告与重试输出各项操作的耗时失败操作会按超时窗口重试。2.1 关键源码印证一镜像与返回 Pod 名的机制README 中pod 对 GET 请求返回 pod 名的说法可在仓库镜像源码中得到精确印证。测试实际使用的镜像并非专门单独构建的旧镜像而是 e2e 通用镜像agnhost版本2.66.1见 test/utils/image/manifest.go 中configs[Agnhost]启动其子命令serve-hostname。真正响应 HTTP 请求的实现位于 test/images/agnhost/serve-hostname/serve_hostname.gohttp.HandleFunc(/, func(w http.ResponseWriter, r *http.Request) { ... fmt.Fprintf(w, %s, hostname) })其中hostname来自os.Hostname()在 Kubernetes 中默认即为 Pod 名。服务默认监听 9376 端口flag port, 9376与 soak 测试代码中 Service 的Port/TargetPort: 9376、容器端口 9376 一一对应见 serve_hostnames.go。这就形成了一条验证链查询响应内容 Pod 主机名 Pod 名 → 若某 Pod 名从未出现即说明该 Pod 或它所在节点/网络路径存在问题。2.2 关键源码印证二查询如何经由 Service 代理发出源码中构造代理请求的入口是 test/e2e/framework/service/resource.go 的GetServicesProxyRequestfunc GetServicesProxyRequest(c clientset.Interface, request *restclient.Request) (*restclient.Request, error) { return request.Resource(services).SubResource(proxy), nil }随后 soak 测试对每个查询执行Namespace(ns).Name(serve-hostnames).DoRaw(...)即访问 REST 路径services/ns/serve-hostnames/proxy。这一设计意味着请求流量真实地贯穿了 API Server → Service 代理 → 后端 Pod 端点 → 容器进程的整条链路而不只是直连 Pod IP因此对 kube-proxy/端点分发/网络联通性的覆盖远高于普通探测。2.3 每轮查询量的计算每个循环中产生的总查询数在源码中是这样计算的queries : *queriesAverage * len(nodes.Items) * *podsPerNode即每个 Pod 平均查询次数 × 节点数 × 每节点 Pod 数。例如 4 节点 × 每节点 1 Pod ×--queries10一轮就是 40 次查询若用--pods_per_node2则一轮为 80 次对应下面示例日志中的 40/80 queries。三、编译与运行先确认当前 kubeconfig 指向目标集群且当前上下文正确README 说明连接取自$HOME/.kube/.kubeconfig源码实现为$HOME/.kube/config见 serve_hostnames.go通过 clientcmd 加载若为 GKE 集群可用--gke_context指定gke_{project}_{zone}_{cluster-name}并使用 gcloud 的 kubeconfig。按 Makefile 构建并直接运行go build serve_hostnames.go ./serve_hostnames四、命令行参数一览以下参数定义于源码 serve_hostnames.go参数默认值含义--queries100每一轮中每个 Pod 平均发起的 hostname 查询次数--pods_per_node1每个节点上部署的 serve-hostname Pod 个数--up_to1迭代轮数设为-1表示无限循环用于长时间浸泡--max_par500同一时刻在途请求的最大数量并发上限--gke_context目标 GKE 集群的 context 名称gke_{project}_{zone}_{cluster-name}--v0klog 日志详细级别--v4输出每步操作耗时与响应分布注意README 示例中的queries10来自当时示例运行场景的参数默认值历史上为 10当前源码默认已是 100以你实际--queries传入值为准。另外源码中还定义了一组内部超时常量serve_hostnames.go它们决定了各阶段重试与放弃的节奏可作为运行超时预期的参考常量值适用阶段nodeListTimeout2 min枚举节点serviceCreateTimeout2 min创建 ServicepodCreateTimeout2 min创建单个 PodpodStartTimeout30 min等待 Pod 进入 RunningendpointTimeout5 min等待端点传播后首次代理请求成功deleteTimeout2 min删除 Pod / ServicenamespaceDeleteTimeout5 min删除测试命名空间并等待消失五、运行输出解读5.1 单轮默认运行./serve_hostnames的典型输出如下README 原例。它先打印启动参数与节点清单随后创建命名空间serve-hostnames-XXXX随机后缀源码用GenerateName生成结束时自动删除创建 Service 与命名形如serve-hostname-N-M的 PodPod 直接指定NodeName精确绑定节点然后等待所有 Pod RunningI0326 14:21:04.179893 11434 serve_hostnames.go:60] Starting serve_hostnames soak test with queries10 and podsPerNode1 upTo1 I0326 14:21:04.507252 11434 serve_hostnames.go:85] Nodes found on this cluster: I0326 14:21:04.507282 11434 serve_hostnames.go:87] 0: kubernetes-node-5h4m.c.kubernetes-satnam.internal I0326 14:21:04.507297 11434 serve_hostnames.go:87] 1: kubernetes-node-9i4n.c.kubernetes-satnam.internal I0326 14:21:04.507309 11434 serve_hostnames.go:87] 2: kubernetes-node-d0yo.c.kubernetes-satnam.internal I0326 14:21:04.507320 11434 serve_hostnames.go:87] 3: kubernetes-node-jay1.c.kubernetes-satnam.internal I0326 14:21:04.507347 11434 serve_hostnames.go:95] Using namespace serve-hostnames-8145 for this test. I0326 14:21:04.507363 11434 serve_hostnames.go:98] Creating service serve-hostnames-8145/serve-hostnames I0326 14:21:04.559849 11434 serve_hostnames.go:148] Creating pod serve-hostnames-8145/serve-hostname-0-0 on node kubernetes-node-5h4m.c.kubernetes-satnam.internal I0326 14:21:04.605603 11434 serve_hostnames.go:148] Creating pod serve-hostnames-8145/serve-hostname-1-0 on node kubernetes-node-9i4n.c.kubernetes-satnam.internal I0326 14:21:04.662099 11434 serve_hostnames.go:148] Creating pod serve-hostnames-8145/serve-hostname-2-0 on node kubernetes-node-d0yo.c.kubernetes-satnam.internal I0326 14:21:04.707179 11434 serve_hostnames.go:148] Creating pod serve-hostnames-8145/serve-hostname-3-0 on node kubernetes-node-jay1.c.kubernetes-satnam.internal I0326 14:21:04.757646 11434 serve_hostnames.go:194] Waiting for the serve-hostname pods to be ready I0326 14:23:31.125188 11434 serve_hostnames.go:211] serve-hostnames-8145/serve-hostname-0-0 is running I0326 14:23:31.165984 11434 serve_hostnames.go:211] serve-hostnames-8145/serve-hostname-1-0 is running I0326 14:25:22.213751 11434 serve_hostnames.go:211] serve-hostnames-8145/serve-hostname-2-0 is running I0326 14:25:37.387257 11434 serve_hostnames.go:211] serve-hostnames-8145/serve-hostname-3-0 is running W0326 14:25:39.243813 11434 serve_hostnames.go:265] No response from pod serve-hostname-3-0 on node kubernetes-node-jay1.c.kubernetes-satnam.internal at iteration 0 I0326 14:25:39.243844 11434 serve_hostnames.go:269] Iteration 0 took 1.814483599s for 40 queries (22.04 QPS) I0326 14:25:39.243871 11434 serve_hostnames.go:182] Cleaning up pods I0326 14:25:39.434619 11434 serve_hostnames.go:130] Cleaning up service serve-hostnames-8145/server-hostnames输出关键点Pod 名后缀N-M用来标识第N号节点上的第M个 Pod该次运行中位于 node 3 上的 Pod 没有响应任何查询于是日志出现W0326 ... No response from pod serve-hostname-3-0 ...警告每轮结束时打印Iteration N took ... for X queries (Y QPS)例如 40 次查询耗时约 1.81s、约 22 QPS。5.2 多轮与多 Pod 场景通过--up_to与--pods_per_node提高强度./serve_hostnames --up_to3 --pods_per_node2输出中可以看到每个节点上创建了 2 个 Podserve-hostname-N-0与-N-1且每轮 80 次查询。README 示例展示了一个很有意思的现象node 2 的两个 Pod 在第 0、1 轮均未响应但到第 2 轮恢复正常。这说明浸泡测试的价值——单次测试容易受瞬时抖动影响误判多轮重复才能区分瞬时故障与持续故障。5.3 无限循环真正的 Soak 模式要让测试无限持续运行使用./serve_hostnames --up_to-1源码中循环条件为for iteration : 0; iteration ! *upTo; iteration当upTo -1时该条件永不为假从而无限循环直到进程被手动终止。这正是浸泡测试的典型用法让它长时间跑着集群任何时刻出问题都会以 WARNING/日志形式被记录。5.4 详细报告模式--v4./serve_hostnames --v4--v4会输出两类额外信息对应源码中klog.V(4)的埋点各操作耗时如Service create ... took 58.473361ms、Pod create ... request took 56.178906ms、每条Proxy call in namespace ... took 43.6ms等各 Pod 的响应分布源码 serve_hostnames.go 中以responses[r]统计响应计数最后逐 Pod 打印I0326 14:35:05.607164 12099 serve_hostnames.go:258] serve-hostname-3-0: 12 I0326 14:35:05.607176 12099 serve_hostnames.go:258] serve-hostname-1-0: 10 I0326 14:35:05.607186 12099 serve_hostnames.go:258] serve-hostname-0-0: 18 W0326 14:35:05.607199 12099 serve_hostnames.go:265] No response from pod serve-hostname-2-0 on node kubernetes-node-d0yo.c.kubernetes-satnam.internal at iteration 0 I0326 14:35:05.607211 12099 serve_hostnames.go:269] Iteration 0 took 1.774856469s for 40 queries (22.54 QPS)该次运行中node 0 的 Pod 响应 18 次、node 1 的 10 次、node 3 的 12 次而 node 2 完全没响应。总响应 40 次恰好等于一轮的 40 次查询说明分发基本均匀理论平均每 Pod 约 10 次由于并发与分发随机性会有波动。六、错误处理与缺失响应的判定机制为了在故障场景下仍能给出可诊断的日志源码对失败查询做了特殊编码serve_hostnames.go查询失败时向结果通道写入一个以!开头不可能出现在合法主机名中的字符串主循环统计时若响应首字符为!则计入missing。随后missing 0时打印Missing %d responses out of %d对每个 Pod 名逐一比对响应 map未出现则打印No response from pod ... at iteration N每轮汇总行也改为... for %d queries (%.2f QPS) with %d missing源码中的统计口径日志正文同样可见。同时几乎所有 API 操作节点枚举、Service 创建/删除、Pod 创建/删除、等待 Running、端点传播探测都带有超时窗口内的自动重试例如创建 Service 在serviceCreateTimeout内每 2 秒重试一次。整个程序也通过defer保证退出前清理 Pod、Service 与命名空间。七、重要定位这不是性能测试README 明确强调serve_hostnames不是性能测试。其目标是为无限期运行、持续验证集群健康提供一种简便手段——通过反复锻炼足够多的 Kubernetes 功能节点调度、Pod 生命周期、Service 端点、API 代理链路等来确认集群仍处于健康可用状态。报告的 QPS 数字主要反映的是master 的延迟原因在于所有代理请求是刻意串行serial发起的即上一请求完成才发起下一个。这一点从并发上限参数--max_par默认 500也能侧面印证虽然源码用 channel 对在途请求做了节流但它关心的不是吞吐上限而是每一次都能通。7.1 QPS 低不代表集群有问题结合 5.1 与 5.2 节的示例40 次查询约 22 QPS、80 次查询约 21~22 QPS数值几乎不随规模上升。如果把它当成性能基准会得到集群只有 20 多 QPS的错误结论。正确的读法是QPS 在这里约等于 API Server 到后端 Pod 的单次往返延迟的倒数用于观察链路延迟是否劣化而非衡量集群吞吐能力。八、何时使用 serve_hostnames适用场景与局限适用场景集群长期健康巡检用--up_to-1在测试/预发集群上常驻运行配合日志采集即可形成持续的健康信号节点失联排查输出能精确定位哪个节点的哪个 Pod 没有响应适合验证节点网络、kube-proxy、Service 端点同步是否正常变更后的回归浸泡集群升级、网络插件调整后持续运行一段时间比一次性 e2e 更能暴露间歇性问题。已知局限依赖 master 上services/proxy代理路径因此它验证的是控制面代理链路的健康不是纯数据面负载均衡的基准仅针对 GCE provider 场景描述README 开头限定 when used with the GCE provider在其它云上需要自行保证 kubeconfig 可达创建 Pod 时显式指定NodeName见 serve_hostnames.go即绕过调度器直接绑定节点因此它不覆盖调度器行为——如需覆盖调度可考虑结合 e2e 框架中的相关用例。九、从源码理解运行全流程一图梳理结合 serve_hostnames.go 的main()可将完整生命周期归纳为如下阶段准备解析 flagqueries/pods_per_node/up_to/max_par/gke_context→ 加载 kubeconfig → 构建 clientset发现Nodes().List()枚举节点带 2 min 重试无节点则 Fatal基建GenerateName: serve-hostnames-创建随机命名空间defer 删除→ 重试创建 Service9376/TCPselectorname: serve-hostname放置工作负载双层循环for i, node : range nodes.Items { for j : 0; j *podsPerNode; j }逐个创建serve-hostname-i-jPod镜像agnhost容器端口 9376NodeName固定就绪等待轮询 Pod 直至Phase Running上限 30 min端点等待通过代理发起首个请求直到成功或非StatusFailure上限 5 min确保 Service 端点已传播浸泡循环每轮queries次并发代理查询max_par节流→ 统计响应 map 与 missing → 输出缺失 Pod 警告与 QPS 汇总 →--up_to-1时无限重复清理defer 链删除全部 Pod、Service删除命名空间并等待其消失上限 5 min。十、延伸阅读测试主体源码serve_hostnames.go构建入口Makefile响应后端镜像实现test/images/agnhost/serve-hostname/serve_hostname.goHTTP/TCP/UDP 三种模式、--port默认 9376代理请求构造工具test/e2e/framework/service/resource.go镜像版本配置test/utils/image/manifest.go本目录 READMEtest/soak/serve_hostnames/README.md若需要了解 Kubernetes e2e 测试体系中与之互补的一次性功能测试可参考仓库 test/e2e 与 test/e2e_kubeadm 目录的框架文档若关心测试镜像的构建与使用约束可阅读 test/images/agnhost/README.md。【免费下载链接】kubernetesProduction-Grade Container Scheduling and Management项目地址: https://gitcode.com/GitHub_Trending/kuber/kubernetes创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考