分享
获课:999it.top/15769/
Kubernetes进阶实战:源码视角下的调优与故障排查思维构建
当我第一次通过Kubernetes源码来调试一个看似诡异的调度问题时,我意识到真正的系统理解不是来自文档或教程,而是来自与代码的直接对话。作为开发者,我们常把Kubernetes当作黑盒使用,但当你需要优化生产集群性能或排查深层次故障时,源码视角能给你完全不同的洞察力。这不是关于背诵代码行数,而是关于建立一种与系统深度对话的思维方式。
一、调度器:从现象到源码的逆向工程
有一次,我们生产环境中几个关键Pod总被调度到同一节点,即使其他节点资源充足。文档和常规排查都指向节点亲和性配置,但检查后一无所获。
突破来自源码视角:我直接定位到Kubernetes调度器源码的优选阶段(priority functions)。在pkg/scheduler/framework/plugins/目录下,我发现除了常见的NodeAffinity、InterPodAffinity,还有一个不太被注意的NodeResourcesBalancedAllocation插件。它的目标是平衡各节点的资源使用率,防止某些节点过早填满。
但这里有个关键细节:该插件主要平衡CPU和内存,但对GPU这类扩展资源处理不同。我们的Pod恰好请求了GPU。源码中的这段逻辑解释了现象:
text
复制
下载
实际上,调度器对扩展资源的平衡考虑有限,这导致了一个隐性的调度偏好——一旦某个节点被标记为"有GPU",后续请求GPU的Pod会更倾向于它,即使从全局看这不平衡。
解决方法不是修改Kubernetes源码,而是通过自定义调度器插件,或者简单调整Pod的资源请求方式。但如果没有源码视角,我可能还在YAML文件里徒劳地检查亲和性规则。
二、控制器模式:理解系统行为的钥匙
Kubernetes的核心设计模式是控制器模式(Controller Pattern),几乎所有的高级功能都基于此。理解这个模式对故障排查至关重要。
最近遇到一个 Deployment 滚动更新卡住的问题。kubectl describe 显示"等待 Pod 就绪"。通常步骤是检查 Pod 事件、探针配置,但这次一切正常。
通过源码,我追踪到 Deployment 控制器在 kkg/controller/deployment/deployment_controller.go 中的 syncDeployment 方法。关键逻辑是:
控制器计算当前状态与期望状态的差异
创建新的 ReplicaSet
逐步缩放新旧 ReplicaSet
但在我们的案例中,问题出现在第3步。深入代码后发现,滚动更新进度不仅取决于Pod就绪,还受maxSurge和maxUnavailable参数影响。我们的配置是maxUnavailable: 0,意味着更新时必须始终有100%的Pod就绪。
而其中一个Pod所在的节点出现了短暂的网络分区(但很快恢复),导致就绪状态更新延迟了几秒。就是这几秒,让整个滚动更新"暂停"了——因为控制器严格遵循maxUnavailable: 0的约束,不敢终止任何旧Pod。
从源码中我理解了这不是bug,而是设计如此。控制器被设计为在不确定时选择安全路径:宁愿等待也不冒险违反用户定义的策略。解决方案是调整更新策略,接受短暂的服务降级(如设置maxUnavailable: 1),或改善节点间网络可靠性。
三、ETCD性能:集群的"记忆体"如何影响一切
我们曾有一个集群,随着资源对象数量增长,API服务器响应明显变慢。监控显示etcd磁盘I/O持续高企。
表面看是etcd需要优化或扩容。但通过研究kube-apiserver源码,特别是与etcd交互的部分,我发现了更微妙的关联。
在 staging/src/k8s.io/apiserver/pkg/storage/etcd3/store.go 中,每个资源对象的读写都会转换为对etcd的操作。但重要的是 List操作:当客户端(包括控制器)列出资源时,默认会从etcd获取完整对象。
随着集群中Pod数量突破5000,一个简单的kubectl get pods -A(或任何控制器的全量List)都会成为性能杀手。更糟的是,许多控制器定期执行全量List来同步状态。
解决方案来自源码的两个启示:
使用资源版本(ResourceVersion)进行增量同步:聪明的控制器会记录最后看到的资源版本,只请求此后的变化
优化客户端筛选:在List时使用字段选择器减少传输数据量
我们审计了集群中所有自定义控制器,将其中三个从全量List改为增量Watch后,etcd负载下降了40%。这个优化不需要修改Kubernetes本身,但需要对API服务器如何与etcd交互有深入理解——而这理解最直接的来源就是源码。
四、网络疑难杂症:穿越CNI的迷雾
网络问题可能是Kubernetes中最棘手的。有一次,某些Pod间通信延迟异常高,但跨节点通信反而正常。
通常的网络排查会检查CNI插件配置、iptables规则、节点路由。但当我们进入kube-proxy源码,特别是iptables模式下的数据包流向时,发现了有趣的设计。
在pkg/proxy/iptables/proxier.go中,syncProxyRules方法生成所有iptables规则。重要的是服务访问路径:
同一节点上的Pod访问Service,经过LOCALOUTPUT链
跨节点访问,经过FORWARD链
我们的延迟问题出现在第一种情况。进一步追踪LOCALOUTPUT链的规则,发现当Pod通过Service域名访问同一节点的另一个Pod时,数据包会经过完整的DNAT、conntrack记录和Kube-Proxy过滤。
关键在于:iptables的conntrack模块在连接数多时有性能开销。虽然我们的连接数不算极高,但Pod生命周期短(频繁创建销毁),导致conntrack表中有大量"僵死"条目。
源码没有直接给出解决方案,但理解了数据路径后,我们可以做出针对性优化:
为短生命周期Pod使用HostNetwork模式(不总是可行)
调整conntrack超时参数
考虑使用IPVS模式替代iptables
最终,我们结合了2和3,延迟问题得到显著改善。
有疑问加站长微信联系(非本文作者))
入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889
关注微信28 次点击
0 回复
暂无回复
添加一条新回复
(您需要 后才能回复 没有账号 ?)
- 请尽量让自己的回复能够对别人有帮助
- 支持 Markdown 格式, **粗体**、~~删除线~~、
`单行代码` - 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
- 图片支持拖拽、截图粘贴等方式上传