Atlantis
GitHub 切换暗/亮/自动模式 切换暗/亮/自动模式 切换暗/亮/自动模式 返回首页

9. 网络不通

Created At 2024-09-06 Updated on 2025-10-25

1. 前言

网络不通是比较玄学的问题,客户的反馈不一定准确,通常需要从头到尾排查整个链路。对于私有云这种部署在客户内部环境的产品,有时经验比技术更有效。能推断出问题点就不需要提交申请、联系客户、协调时间和远程排查等操作。这里针对过去客户在使用容器集群时反馈的网络不通问题,做一个汇总记录。

简单讲一下前司老架构容器集群产品的状态:

  1. Master 托管在底座上,以 Pod 形式运行。
  2. Worker 一般为 ECS,也支持裸金属和 VK,但主要还是使用 ECS。
  3. CNI 插件默认使用 vxlan 模式的 flannel,可选 Calico,以及接入 Underlay 网络的 VPC-CNI(也是基于 Calico 实现)。
  4. kube-proxy 的 ProxyMode 使用 ipvs。

2. 问题列表

2.1 端口冲突

某客户在内部环境部署了多套私有云,然后再由软件提供商适配环境,部署业务到私有云上,最终等于是我们这个大乙方对接客户的其他小乙方。

其中有一位比较离谱的用户,首先创建了一个容器集群,添加了 ECS 作为工作节点,其中一部分业务使用容器运行,一部分直接跑在 ECS 上,某日发现一个 Service 的 NodePort 无法访问,傍晚联系驻场同学提严重问题单,导致领导晚上在夕会上被拷问。

排查私有云问题的一大阻力是现场环境往往无法直接访问,需要联系客户申请权限,再由驻场同学配合打开远程桌面进行排查,当我定位到问题原因后,是有些无语的,最后以客户使用问题结束问题单。

问题本身是比较低级的,奈何发生过许多次,只有对客户的知识背景做好心理准备,才能更好地排查问题。

2.2 安全组配置

驻场同事接到客户反馈,某个一直可以正常访问的 NodePort 突然无法访问,驻场同学检查了宿主机端口占用列表,未发现端口冲突问题。

介入排查后发现该客户非常注重网络安全,他们创建了一个主账号用于管理资源,比如创建集群和增删节点,开发人员只能使用子账号使用集群部署业务,而且他们配置了专用跳板机访问业务机器,还创建了许多个安全组用于加固,既做了公网出向目标 IP 限制、内网入向来源 IP 限制,也做了内网互访限制,一台 ECS 绑定了至少三个自定义安全组。因此怀疑是安全组配置问题,告知客户自查后,确认 NodePort 异常出现在一次安全组变配后。

结论虽然还是客户安全组配置问题,但客户坚持是缺乏指导性文档导致的,最终以降级问题单,提供建议安全组配置文档收尾。

过于复杂的配置反而可能导致问题,我最终给出了三种默认配置方案,应对不通的用户:

  1. 完全开放所有端口:简单粗暴而有效,适用于一些需要直接暴露端口做测试的客户
  2. 开放三个内网网段:允许 192.168.0.0/16、172.16.0.0/12、10.0.0.0/8 三个内网网段来源 IP
  3. 仅限特定IP访问:同安全组内的 ECS 默认允许互访,无需配置,而 Master 的 IP 需手动查询后添加到安全组,仅允许访问 ECS 的 10250 端口

有需要加固网络的客户自然会做最优配置,而不关心网络问题的客户一般只会选择全部开放端口。

2.3 网段冲突

参考 CNCF笔记 / 问题排查 / 3. CIDR冲突,当初我发现集群的 CIDR 全部由前端校验并且可能遭遇 bug 时,客户现网早已经创建了无数存量集群,我们能做的只有祈祷存量集群不要发现网段冲突(要真发现了也祈祷是个好说话的客户,新建集群迁移业务)。

2.4 长连接中断

驻场同事接到客户反馈,业务部署后,可以正常运行一段时间,但隔天客户端就无法通过 Service 访问到服务端。

介入排查后逐步确认:

  1. 出现异常后,在宿主机上切换到 Pod 网络命名空间未检查到网络连接,ipvsadm -l -n 输出未见活跃连接
  2. 联系客户后确认客户端代码使用 JAVA 标准 HTTP 客户端,随后发现客户提供的示例代码中未开启 keepalive,而 ipvsadm -l --timeout 命令输出显示 TCP 连接超时时间为 900 秒
  3. 再次跟客户确认,该系统主要用于演示,平时没有长期使用的需求,最近由于需要验收才在部署后进行长时间测试

结论是客户的代码逻辑问题,初始化一个全局 HTTP 客户端使用一段时间后就不再做任何调用,没有发出网络请求,导致 TCP 连接被 ipvs 中断,但由于时间紧迫客户不愿修改代码。

解决方案是调整 kube-proxy 配置与节点内核参数,将 ipvs 的 TCP 连接超时设置为 2 小时,TCP 启动 keepalive 时间改为 900 秒,keepalive 间隔为 75 秒,帮助用户维护住长连接,撑过项目验收。

2.5 内核参数覆盖

驻场同学反馈某个使用信创处理器(ARM64)的客户环境出现异常,工作节点加入集群后运行正常,但若重启节点,节点上就会出现的 Pod 端口不通,Service 的 IP 无法访问。

这个问题前后排查了三四天才定位到异常位置,中间有太多烟雾弹:

  1. 第一天
    1. 现场反馈集群中有 Pod 出现 CrashLoopBackoff,初步排查后发现客户对节点做了一些修改,如:在节点上直接运行服务、修改 iptables、修改 docker 默认网段、修改网卡自启动配置,导致无法继续排查问题,联系现场剔除异常节点再添加新节点,并禁止修改节点的操作
    2. 现场反馈新节点无法加入集群,远程排查后发现 VPC 内的 DNS 服务异常,kubelet 通过域名连接 kube-apiserver,于是联系网络团队处理
    3. 继续添加新节点,在新节点上通过控制变量测试出异常产生时,执行 systemctl stop iptables 可修复问题,选一个存量节点验证通过后,确认有效
  2. 第二天
    1. 对存量集群的所有节点执行 systemctl stop iptables 修复问题
  3. 第三天
    1. 客户部署 kubesphere 时,发现某集群 metrics 服务异常
    2. 登录 ECS 执行 systemctl stop iptables 无法修复问题,重启 ECS 后执行 systemctl stop iptables 恢复正常
  4. 第四天
    1. 在内部环境模拟复现问题,确认客户同时使用 centos8 与麒麟操作系统,两个系统存在一些差异

经过几天的排查工作,基本将怀疑点锁定在 iptables,并确认了以下情况:

  1. 同一个集群的节点,centos8 与麒麟操作系统执行 iptables-save 输出不同,确认麒麟上的 iptables 包版本太老(1.8.4 vs 1.8.1)
  2. 执行 systemctl stop iptables 等于清空 mangle、nat 与 filter 表,并将三个表的链都设置为 ACCEPT,但麒麟重启后 filter 表的 forward 链仍为 DROP
  3. 排查修改 forward 链的程序,检查 kubelet、docker、flannel 与 kube-proxy 的代码后,确认只有 docker 会执行此操作

进一步查阅 docker 文档可知,docker 可能会设置 FORWARD 链为 DROP,然后添加自己的规则,但是提供了一个开关,可修改配置文件来禁止修改 iptables:

alt text

现在可以肯定导致网络不通的直接原因是 filter 表的 forward 链被设置成 DROP,确认 ARM64 麒麟系统上的 docker 版本为 v20.10.8,分析代码中配置 forward 的位置,如下:

github.com/moby/moby/blob/v20.10.8/vendor/github.com/docker/libnetwork/drivers/bridge/setup_ip_forwarding.go

alt text

可见 net.ipv4.ip_forward 为 0 是触发修改的必要条件,其次要求 enableIPTable 为 true(因此设置 daemon.json 中 iptables 字为 false 也可规避问题)。

接下来分析为什么 ARM64 麒麟系统重启后 net.ipv4.ip_forward 总是为 0,通过检查内核参数配置,确认系统上存在一个 /etc/sysctl.d/99-sysctl.conf 文件,里面包含关闭 IPv4 转发的配置,且 /etc/sysctl.conf 软连接到此文件。

Linux 系统重启会后按 /etc/sysctl.d 下的文件名字典序应用配置,导致添加节点时创建的 /etc/sysctl.d/k8s.conf 的 net.ipv4.ip_forward = 1 被 /etc/sysctl.d/99-sysctl.conf 的 net.ipv4.ip_forward = 0 覆盖。

结论是节点操作系统镜像存在问题,信创操作系统都有类似的操作,防止 net.ipv4.ip_forward 被用户错误开启,当初适配麒麟操作系统的哥们没有注意到这一点,只验证了添加节点,未验证重启节点后配置是否依旧生效。

2.6 Service的错误更新方式

驻场同事接到客户反馈,客户先 NodePort 暴露集群内服务,然后使用 ingress 暴露通过域名暴露服务,隔天早上发现 NodePort 不通,curl 可以通。

介入排查后发现:

  1. 从集群外部访问 节点外网:NodePort,无法连接
  2. 容器组内访问 svc,遇到 no route to host
  3. 客户重新部署业务后,短暂恢复正常

尝试了所有以往使用过的排查手段,未定位问题,怀疑是本地某个服务持续修改 iptables,排查了 flannel 与 kube-proxy 仍未发现问题,只看到 flannel 在反复刷新 iptables。

时间拖延到晚上,问题升级,领导找来网络团队的资深开发协助排查,步骤如下:

  1. 安装 tcpdump 工具:现场环境断网,联系了驻场同事上传 rpm 包安装才搞定工具
  2. 在集群外部去 ping 出现问题 ECS 的 EIP,同时使用 tcpdump 捕捉 ping 请求,获取到来源 IP 的 host
  3. 反复尝试 curl 访问用户提供的有问题的 http 连接,触发连接异常问题,同时使用 tcpdump 捕捉来源 host 和 NodePort 端口请求
  4. 发现 ipvs 转发异常,触发问题时会转发到一个不存在的后端 Pod
  5. 打开 kube-proxy 日志,检测日志发现异常 Service 存在两个 Endpoint,但是实际上只有一个 Endpoint 正常
  6. 排查 kube-proxy 代码,确认 kube-proxy 使用 EndpointSlice 而非 Endpoint 生成 iptables 规则与 ipvs 转发规则

结论是系统中出现了大量无效的 EndpointSlice,来源未知,直接删除 EndpointSlice 来触发重新生成 EndpointSlice后,ipvs 转发规则恢复正常。

该问题的根本原因一直无法定位,集群中未发现能产生无效 EndpointSlice 的组件,在几个月后一次偶然翻看后台接口时,发现了端倪:

alt text

看起来是控制台上为了防止用户更改 Service 导致网络不通,因此屏蔽掉 Service 更新接口中 ServiceSpec 的 selector 字段。

但当时的开发人员在调用 kube-apiserver 接口时,直接使用了 Update 方法,执行了全量更新导致 selector 字段丢失,正确做法应当是使用 Patch。

而客户部署业务后,会在控制台上手动修改 Service 的类型为 NodePort,当业务 Pod 重启或者重建后,就会遗留无效的 EndpointSlice。