第 14 章:从卡住的对象找到失败环节
先区分对象层与进程层
一个网关没有响应,可能是控制器未处理 spec,也可能是 Deployment 未就绪、Service 选择器错误、集群网络异常或 Envoy 配置失败。直接重启控制器会改变现场,却未必解决问题。诊断第一步应按照契约分层收集事实。
先检查主对象的 generation 和 observedGeneration。两者不同,说明当前意图尚未被确认;相同且 Reconciled 为 True,只说明所属资源已应用。再检查 Deployment 的 generation 与其 status.observedGeneration、更新和可用副本,以及 Pod 事件。最后用集群内客户端验证 Service 请求。每个观察量回答不同问题。
删除卡住则从 deletionTimestamp、finalizers、CleanupBlocked 条件和模拟外部登记入手。对象已经消失时,status 不能再提供历史,必须依靠日志和保留证据。诊断流程应先保存现场,再决定是否做不可逆的强制删除。
日志要连接一次对象生命
生产日志至少应包含命名空间、名称、UID、本轮处理 generation、动作和错误类型。只有名称很难区分删除重建前后的两次生命;只有 request ID 又难以串联多轮协调。对外部调用还应记录不包含秘密的幂等键或资源引用,帮助判断结果未知窗口。
当前教学控制器没有完整结构化日志和指标服务,主要证据来自真实 API 对象、测试输出和原始 JSON 测试流。本章给出的日志字段是明确的生产演进建议,不能据此把现有代码描述为已提供成熟运维监控平台。
加入日志时也要避免每秒为已收敛对象打印大段详情。周期复查放大日志量,容易让关键失败淹没。成功路径可采用采样或低详细级别,状态变化和错误保留清晰记录;敏感字段则应在进入日志接口前完成过滤。
指标必须与行动对应
队列长度反映待处理工作,但无法单独解释原因。处理时延升高可能来自 API 限流,也可能是某个外部调用阻塞。冲突计数持续增加可能意味着合法竞争,也可能是两个控制器争夺同一字段。指标需要结合对象和错误类别,才能支持行动。
值得关注的信号包括协调耗时、成功与错误次数、重试次数、队列等待时间、未观察新 generation 的时长和删除阻塞时长。对高基数的实例名称不宜直接作为所有指标标签,否则监控存储成本会随对象数量失控。对象级细节更适合日志和查询接口。
本项目没有把指标阈值写成统一答案。十个实例每秒扫描与十万实例事件驱动的基线差异很大。阈值应根据处理预算和服务目标制定,并通过实际负载验证,而不是复制一组看起来专业的数字。
保留原始失败证据
CI 每次保留 Kubernetes 版本、节点镜像 digest、Go 版本、原始 go test JSON、HTTP 响应和 kind 日志。成功摘要便于阅读,原始流则允许重新核对哪些场景真正执行、是否跳过,以及哪个断言先失败。没有原始证据的通过计数很难独立审计。
失败运行也要上传 artifact。若只在成功后收集证据,最需要诊断的一次运行反而没有现场。测试退出后清理自己的 kind 集群,但先导出日志与资源列表,使资源释放和故障分析可以同时完成。
一次可操作的排查演练
故意把 Service targetPort 改为错误值,然后先停止控制器,保存对象和失败请求。主对象的旧 Reconciled 可能仍为 True,因为控制器尚未重新观察漂移;这不代表状态撒谎,它描述的是最近一次成功观察。重新启动后,周期复查恢复端口,实际请求重新成功。
练习是为这段过程写一份故障记录:触发操作、受影响层、观察证据、恢复动作和证据局限。不要只写“重启后恢复”。如果无法解释恢复来自重启重发现还是后续周期检查,下一次故障仍只能依赖偶然操作。
证据保留要有时间范围
诊断记录应包含触发时间、采集时间和实际源码提交。对象截图只代表某一刻,稍后自动自愈可能已经改变现场。原始命令输出比手工概述更容易复核,但同样需要过滤秘密和设置保留期限。
将故障时间线按用户动作、API接受、控制器处理、数据面响应四个阶段排列,可以看出延迟出现在哪个环节。总恢复时间适合服务目标,分段时间更适合定位下一步该优化哪里。