故障处理
我来分享一个我在生产环境中遇到的印象深刻的、比较复杂的Kubernetes DNS故障排查经历。这不仅仅是一个技术问题,更是一次关于系统性思考、耐心与验证方法的重要实践。
背景概述:
那次故障发生在我们对一套正在运行的关键Kubernetes集群进行CNI插件替换时。我们计划将集群的网络插件从Flannel迁移到Calico。在完成Calico的部署并重启相关组件后,我们发现集群内部的DNS解析出现了严重问题,直接导致了微服务间通信中断,集群几乎处于“瘫痪”状态。
故障现象与初步影响:
最初的故障现象是,Pod内部的服务无法通过短域名或甚至FQDN(完全限定域名)互相访问,例如 ping kubernetes 失败,ping kube-dns.kube-system 也失败。这直接导致了所有依赖服务发现的应用层业务中断,用户受到影响。
我的排查过程,分为三个阶段:
第一阶段:定位 Pod /etc/resolv.conf 的诡异污染
-
异常发现: 进入任意Pod内部检查
/etc/resolv.conf文件,我们惊讶地发现,在search路径中竟然混入了114.114.114.114这样的公共DNS服务器IP。这显然是不应该出现的,它可能会导致内部域名解析请求被不必要地转发到外部,甚至在解析失败后尝试向其查询,引入延迟和错误。nameserver 10.200.0.10 search default.svc.oldboyedu.com svc.oldboyedu.com oldboyedu.com 114.114.114.114 options ndots:5 -
初步排查(标准路径): 我的第一反应是检查Kubelet是如何生成这个文件的。
- 检查了宿主机的
/etc/resolv.conf和/run/systemd/resolve/resolv.conf,它们都很干净,没有114的条目。 - 检查了Kubelet的
config.yaml(/var/lib/kubelet/config.yaml),其中的clusterDNS、clusterDomain和resolvConf配置都指向了正确且干净的上游。 - 确认了
kubeadm-configConfigMap中也没有异常配置。 - 这让我非常困惑,因为所有明面上的配置都显示是正确的。
- 检查了宿主机的
-
突破点:隐藏的宿主机网络配置污染: 经过深入研究Kubelet的行为和Linux网络配置原理,我最终定位到一个非常隐蔽的问题。我们使用的操作系统(Ubuntu,通过netplan管理网络)尽管
/etc/resolv.conf看起来干净,但在某些情况下,systemd-resolved或底层的网络接口配置可能隐式地从DHCP或其他配置源接收到了114.114.114.114作为DNS服务器,并在某些高级配置中将其误添加为search域。Kubelet在与这些底层系统服务交互时,可能会受到其影响,进而污染Pod的/etc/resolv.conf。 -
解决方案:
- 修正了宿主机底层的网络配置,确保不再有隐式的
search域污染。 - 为了彻底杜绝此类问题,我修改了Kubelet的Systemd Unit文件 (
/etc/systemd/system/kubelet.service.d/10-kubeadm.conf),显式地通过--resolv-conf=/etc/resolv.conf参数强制Kubelet使用一个我们确认干净的/etc/resolv.conf文件作为其DNS配置源。 - 重启Kubelet服务并验证后,Pod的
/etc/resolv.conf恢复了正常。
- 修正了宿主机底层的网络配置,确保不再有隐式的
第二阶段:CoreDNS拒绝服务与性能瓶颈
-
新的问题浮现: 虽然Pod的
resolv.conf恢复正常,但内部短域名解析(如kube-dns.kube-system)仍然失败,报错REFUSED或NXDOMAIN。外部域名解析有时也表现出延迟。 -
检查CoreDNS状态: CoreDNS Pod本身运行正常,但检查其日志,发现了关键的错误信息:
[ERROR] plugin/errors: 5 kube-dns.kube-system. AAAA: concurrent queries exceeded maximum 1000。这明确指出CoreDNS的并发查询达到了上限,拒绝了请求。 -
CoreDNS配置与版本分析:
- Corefile问题: 检查了CoreDNS的Corefile配置(
kubectl edit cm coredns -n kube-system),发现几个潜在的问题点:pods insecure:这个选项允许CoreDNS解析Pod的IP地址,在大多数情况下是不必要的,增加了CoreDNS的负担。forward . /etc/resolv.conf:CoreDNS将外部查询转发给宿主机的/etc/resolv.conf,而宿主机的/etc/resolv.conf可能指向127.0.0.53(systemd-resolved stub resolver),这增加了额外的解析层,引入了延迟和并发压力。
- 版本兼容性: 我们发现集群的Kubernetes版本是1.23.17,而CoreDNS版本是1.12.0。这两个版本之间存在一定的兼容性差距(Kubernetes 1.23.x 通常推荐搭配 CoreDNS 1.8.x 或 1.9.x)。版本不匹配很可能是导致并发问题的一个重要原因。
- Corefile问题: 检查了CoreDNS的Corefile配置(
-
解决方案:
- 优化CoreDNS Corefile:
- 移除了
pods insecure配置。 - 将
forward . /etc/resolv.conf直接修改为forward . 223.5.5.5(或其他生产环境可靠的公共DNS服务器IP),减少了中间转发环节。
- 移除了
- 降级CoreDNS版本: 将CoreDNS的镜像版本从
1.12.0降级到与Kubernetes 1.23更兼容的1.9.2。 - 应用配置并等待CoreDNS Pod重启后,CoreDNS日志中不再出现
concurrent queries exceeded错误,外部域名解析也恢复了正常。
- 优化CoreDNS Corefile:
第三阶段:nslookup的“欺骗”与最终验证
- 最后的疑惑: 尽管CoreDNS日志已无异常,外部域名解析也正常,但令人沮丧的是,在Pod内部使用
nslookup kube-dns.kube-system依然返回NXDOMAIN。这让我再次陷入困惑,难道还有别的问题? - 工具差异性验证: 我意识到
nslookup作为一个精简的BusyBox环境中的工具,其DNS解析行为可能与系统底层的DNS解析库(如getaddrinfo,被多数应用程序和ping命令使用)有所不同,甚至可能存在误报。 - 最终验证: 我决定使用
ping命令来验证实际的DNS解析功能。
这个结果振奋人心!它最终证实了:Pod内部的DNS解析实际上已经完全正常,之前/ # ping -c 1 kube-dns.kube-system PING kube-dns.kube-system (10.200.0.10): 56 data bytes 64 bytes from 10.200.0.10: seq=0 ttl=64 time=0.144 ms # ... 成功解析并通信! / # ping -c 1 kubernetes PING kubernetes (10.200.0.1): 56 data bytes 64 bytes from 10.200.0.1: seq=0 ttl=64 time=0.632 ms # ... 同样成功!nslookup报告的NXDOMAIN只是其工具本身的误报或行为缺陷,而非实际的DNS解析问题。应用程序使用的系统底层DNS解析库能够正常地通过短域名访问集群内部服务。
故障总结与我的收获:
这次复杂的DNS故障排查,让我获得了宝贵的生产经验:
- Kubernetes DNS是多层级的复杂系统: 它涉及Pod的
resolv.conf、Kubelet配置、宿主机网络配置、CoreDNS配置、CoreDNS版本以及应用程序自身的解析行为。排查时必须逐层深入,不放过任何看似不相关的环节。 - 警惕隐藏的污染源: 宿主机看似干净的配置,不代表其底层网络管理工具(如netplan、systemd-resolved)没有隐式地添加
search域或DNS服务器。对Kubelet显式指定resolvConf是一个重要的防御性措施。 - CoreDNS配置至关重要: CoreDNS的Corefile配置应简洁高效。
pods insecure等选项在非必要情况下应避免。forward插件应直接指向稳定、高效的上游DNS服务器,而不是通过宿主机的systemd-resolved中转,这会增加不必要的解析层和延迟。 - 版本兼容性不容忽视: Kubernetes各组件之间存在严格的版本兼容性要求。当遇到难以解释的问题时,考虑组件版本是否匹配是一个重要的排查方向。生产环境的升级和降级操作必须谨慎,并提前做好兼容性调研。
- 不完全信任单一诊断工具: 尤其是像BusyBox环境下的
nslookup这样功能精简的工具,其输出可能存在误导性。当怀疑工具本身问题时,应使用不同的工具(如ping、dig,或直接测试应用程序)进行交叉验证。ping命令在底层依赖操作系统解析器,因此在验证DNS解析时,它通常比nslookup更可靠。 - CNI插件选择和部署的最佳实践: 这次故障的初始触发点是CNI插件的切换。我深刻认识到,在生产环境中,应在集群搭建之初就选定并安装好稳定的CNI插件,并尽量避免在集群运行后进行大规模的CNI切换,因为这会带来巨大的网络配置复杂性和潜在风险。
为什么从flannel换到calico?
我们决定将集群的 CNI 插件从 Flannel 迁移到 Calico,这主要是基于我们生产环境对网络安全、控制粒度以及未来可扩展性的更高要求。
当初选择 Flannel,是因为它非常轻量级,部署简单,能够快速实现 Pod 间的基本网络连通性,对于集群初期搭建和测试来说是一个很好的选择。
然而,随着我们业务的增长和微服务架构的深入,我们发现 Flannel 在以下几个方面逐渐无法满足生产需求:
-
缺乏原生的网络策略(Network Policy)支持:
- 这是最核心的原因。Flannel 主要提供的是扁平化的三层网络,确保 Pod 之间能够互相通信。但是,它本身不提供 Kubernetes
NetworkPolicy的能力。在生产环境中,我们需要对微服务间的流量进行严格的隔离和访问控制,例如限制哪些服务可以访问数据库,或者不允许开发环境的服务访问生产环境的敏感数据。 - 缺乏网络策略意味着,一旦一个 Pod 被攻破,攻击者很容易就能在整个集群内部横向移动,访问所有其他 Pod。这在安全上是一个巨大的隐患。
- Calico 则以其强大的网络策略引擎而闻名。 它提供了丰富、灵活的策略定义语言,可以实现非常细粒度的Pod间流量控制,包括Ingress/Egress规则、基于Label的选择器等,极大地提升了集群的安全性和合规性。
- 这是最核心的原因。Flannel 主要提供的是扁平化的三层网络,确保 Pod 之间能够互相通信。但是,它本身不提供 Kubernetes
-
性能与路由模式的考量:
- Flannel 常用的是 VXLAN 封装模式,虽然解决了跨主机的 Pod 通信问题,但数据包的封装和解封装会带来一定的性能开销。
- Calico 支持多种路由模式,包括 IP-in-IP 和 BGP 等。 在我们的场景中,Calico 可以通过直接路由或 BGP 模式工作,避免了额外的封装,理论上能提供更好的网络性能。这对于高并发、低延迟的微服务应用来说非常重要。
-
高级网络功能与可扩展性:
- Calico 提供了比 Flannel 更丰富的网络功能,例如:
- 更灵活的 IP 地址管理 (IPAM): Calico 的 IPAM 可以提供更精细的 IP 地址分配和管理能力。
- BGP 路由集成: 对于混合云或内部数据中心部署的集群,Calico 能够与现有的 BGP 网络设备进行集成,方便地将 Pod IP 路由到集群外部,简化网络架构。
- 更强大的可观测性: Calico 提供了更多的工具和日志,方便我们监控网络流量和排查网络问题。
- 从长远来看,Calico 作为一个功能更全面、更成熟的 CNI 解决方案,能够更好地支持我们集群规模的扩展和更复杂的网络需求。
- Calico 提供了比 Flannel 更丰富的网络功能,例如:
总结来说,从 Flannel 迁移到 Calico,是我们为了从“能用”升级到“安全、高效、可控且可扩展”的生产级 Kubernetes 网络基础设施而做出的关键决策。 它帮助我们构建了一个更健壮、更符合企业级安全和运维标准的集群网络。
浙公网安备 33010602011771号