9. 网络不通
网络不通是比较玄学的问题,客户的反馈不一定准确,通常需要从头到尾排查整个链路。对于私有云这种部署在客户内部环境的产品,有时经验比技术更有效。能推断出问题点就不需要提交申请、联系客户、协调时间和远程排查等操作。这里针对过去客户在使用容器集群时反馈的网络不通问题,做一个汇总记录。
简单讲一下前司老架构容器集群产品的状态:
- Master 托管在底座上,以 Pod 形式运行。
- Worker 一般为 ECS,也支持裸金属和 VK,但主要还是使用 ECS。
- CNI 插件默认使用 vxlan 模式的 flannel,可选 Calico,以及接入 Underlay 网络的 VPC-CNI(也是基于 Calico 实现)。
- kube-proxy 的 ProxyMode 使用 ipvs。
某客户在内部环境部署了多套私有云,然后再由软件提供商适配环境,部署业务到私有云上,最终等于是我们这个大乙方对接客户的其他小乙方。
其中有一位比较离谱的用户,首先创建了一个容器集群,添加了 ECS 作为工作节点,其中一部分业务使用容器运行,一部分直接跑在 ECS 上,某日发现一个 Service 的 NodePort 无法访问,傍晚联系驻场同学提严重问题单,导致领导晚上在夕会上被拷问。
排查私有云问题的一大阻力是现场环境往往无法直接访问,需要联系客户申请权限,再由驻场同学配合打开远程桌面进行排查,当我定位到问题原因后,是有些无语的,最后以客户使用问题结束问题单。
问题本身是比较低级的,奈何发生过许多次,只有对客户的知识背景做好心理准备,才能更好地排查问题。
驻场同事接到客户反馈,某个一直可以正常访问的 NodePort 突然无法访问,驻场同学检查了宿主机端口占用列表,未发现端口冲突问题。
介入排查后发现该客户非常注重网络安全,他们创建了一个主账号用于管理资源,比如创建集群和增删节点,开发人员只能使用子账号使用集群部署业务,而且他们配置了专用跳板机访问业务机器,还创建了许多个安全组用于加固,既做了公网出向目标 IP 限制、内网入向来源 IP 限制,也做了内网互访限制,一台 ECS 绑定了至少三个自定义安全组。因此怀疑是安全组配置问题,告知客户自查后,确认 NodePort 异常出现在一次安全组变配后。
结论虽然还是客户安全组配置问题,但客户坚持是缺乏指导性文档导致的,最终以降级问题单,提供建议安全组配置文档收尾。
过于复杂的配置反而可能导致问题,我最终给出了三种默认配置方案,应对不通的用户:
- 完全开放所有端口:简单粗暴而有效,适用于一些需要直接暴露端口做测试的客户
- 开放三个内网网段:允许 192.168.0.0/16、172.16.0.0/12、10.0.0.0/8 三个内网网段来源 IP
- 仅限特定IP访问:同安全组内的 ECS 默认允许互访,无需配置,而 Master 的 IP 需手动查询后添加到安全组,仅允许访问 ECS 的 10250 端口
有需要加固网络的客户自然会做最优配置,而不关心网络问题的客户一般只会选择全部开放端口。
参考 CNCF笔记 / 问题排查 / 3. CIDR冲突,当初我发现集群的 CIDR 全部由前端校验并且可能遭遇 bug 时,客户现网早已经创建了无数存量集群,我们能做的只有祈祷存量集群不要发现网段冲突(要真发现了也祈祷是个好说话的客户,新建集群迁移业务)。
驻场同事接到客户反馈,业务部署后,可以正常运行一段时间,但隔天客户端就无法通过 Service 访问到服务端。
介入排查后逐步确认:
- 出现异常后,在宿主机上切换到 Pod 网络命名空间未检查到网络连接,
ipvsadm -l -n输出未见活跃连接 - 联系客户后确认客户端代码使用 JAVA 标准 HTTP 客户端,随后发现客户提供的示例代码中未开启 keepalive,而
ipvsadm -l --timeout命令输出显示 TCP 连接超时时间为 900 秒 - 再次跟客户确认,该系统主要用于演示,平时没有长期使用的需求,最近由于需要验收才在部署后进行长时间测试
结论是客户的代码逻辑问题,初始化一个全局 HTTP 客户端使用一段时间后就不再做任何调用,没有发出网络请求,导致 TCP 连接被 ipvs 中断,但由于时间紧迫客户不愿修改代码。
解决方案是调整 kube-proxy 配置与节点内核参数,将 ipvs 的 TCP 连接超时设置为 2 小时,TCP 启动 keepalive 时间改为 900 秒,keepalive 间隔为 75 秒,帮助用户维护住长连接,撑过项目验收。
驻场同学反馈某个使用信创处理器(ARM64)的客户环境出现异常,工作节点加入集群后运行正常,但若重启节点,节点上就会出现的 Pod 端口不通,Service 的 IP 无法访问。
这个问题前后排查了三四天才定位到异常位置,中间有太多烟雾弹:
- 第一天
- 现场反馈集群中有 Pod 出现 CrashLoopBackoff,初步排查后发现客户对节点做了一些修改,如:在节点上直接运行服务、修改 iptables、修改 docker 默认网段、修改网卡自启动配置,导致无法继续排查问题,联系现场剔除异常节点再添加新节点,并禁止修改节点的操作
- 现场反馈新节点无法加入集群,远程排查后发现 VPC 内的 DNS 服务异常,kubelet 通过域名连接 kube-apiserver,于是联系网络团队处理
- 继续添加新节点,在新节点上通过控制变量测试出异常产生时,执行
systemctl stop iptables可修复问题,选一个存量节点验证通过后,确认有效
- 第二天
- 对存量集群的所有节点执行
systemctl stop iptables修复问题
- 对存量集群的所有节点执行
- 第三天
- 客户部署 kubesphere 时,发现某集群 metrics 服务异常
- 登录 ECS 执行
systemctl stop iptables无法修复问题,重启 ECS 后执行systemctl stop iptables恢复正常
- 第四天
- 在内部环境模拟复现问题,确认客户同时使用 centos8 与麒麟操作系统,两个系统存在一些差异
经过几天的排查工作,基本将怀疑点锁定在 iptables,并确认了以下情况:
- 同一个集群的节点,centos8 与麒麟操作系统执行 iptables-save 输出不同,确认麒麟上的 iptables 包版本太老(1.8.4 vs 1.8.1)
- 执行
systemctl stop iptables等于清空 mangle、nat 与 filter 表,并将三个表的链都设置为 ACCEPT,但麒麟重启后 filter 表的 forward 链仍为 DROP - 排查修改 forward 链的程序,检查 kubelet、docker、flannel 与 kube-proxy 的代码后,确认只有 docker 会执行此操作
进一步查阅 docker 文档可知,docker 可能会设置 FORWARD 链为 DROP,然后添加自己的规则,但是提供了一个开关,可修改配置文件来禁止修改 iptables:

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

可见 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 被用户错误开启,当初适配麒麟操作系统的哥们没有注意到这一点,只验证了添加节点,未验证重启节点后配置是否依旧生效。
驻场同事接到客户反馈,客户先 NodePort 暴露集群内服务,然后使用 ingress 暴露通过域名暴露服务,隔天早上发现 NodePort 不通,curl 可以通。
介入排查后发现:
- 从集群外部访问 节点外网:NodePort,无法连接
- 容器组内访问 svc,遇到 no route to host
- 客户重新部署业务后,短暂恢复正常
尝试了所有以往使用过的排查手段,未定位问题,怀疑是本地某个服务持续修改 iptables,排查了 flannel 与 kube-proxy 仍未发现问题,只看到 flannel 在反复刷新 iptables。
时间拖延到晚上,问题升级,领导找来网络团队的资深开发协助排查,步骤如下:
- 安装 tcpdump 工具:现场环境断网,联系了驻场同事上传 rpm 包安装才搞定工具
- 在集群外部去 ping 出现问题 ECS 的 EIP,同时使用 tcpdump 捕捉 ping 请求,获取到来源 IP 的 host
- 反复尝试 curl 访问用户提供的有问题的 http 连接,触发连接异常问题,同时使用 tcpdump 捕捉来源 host 和 NodePort 端口请求
- 发现 ipvs 转发异常,触发问题时会转发到一个不存在的后端 Pod
- 打开 kube-proxy 日志,检测日志发现异常 Service 存在两个 Endpoint,但是实际上只有一个 Endpoint 正常
- 排查 kube-proxy 代码,确认 kube-proxy 使用 EndpointSlice 而非 Endpoint 生成 iptables 规则与 ipvs 转发规则
结论是系统中出现了大量无效的 EndpointSlice,来源未知,直接删除 EndpointSlice 来触发重新生成 EndpointSlice后,ipvs 转发规则恢复正常。
该问题的根本原因一直无法定位,集群中未发现能产生无效 EndpointSlice 的组件,在几个月后一次偶然翻看后台接口时,发现了端倪:

看起来是控制台上为了防止用户更改 Service 导致网络不通,因此屏蔽掉 Service 更新接口中 ServiceSpec 的 selector 字段。
但当时的开发人员在调用 kube-apiserver 接口时,直接使用了 Update 方法,执行了全量更新导致 selector 字段丢失,正确做法应当是使用 Patch。
而客户部署业务后,会在控制台上手动修改 Service 的类型为 NodePort,当业务 Pod 重启或者重建后,就会遗留无效的 EndpointSlice。