节点 IPv6 前缀漂移:一次「只是告警」背后的跨节点 Pod 网络静默失效
前言
之前国庆 homelab 升级为 ipv4+ipv6 双栈,近期又对 4个节点进行了更新和重启。结果发现:一条 kubelet 告警,每 10 秒重复一次。
failed to validate secondaryNodeIP
第一反应通常是配置没对齐,重启一下 k3s 就完事。
实际取证的结果比这个严重得多:跨节点 IPv6 pod 通信已经断了,只是业务侧没报出来。
告警和真实影响面之间,差了整整一层。
现象
时间点:2026-10-29。
同一轮重启里,ISP 重新委派了 IPv6 前缀:
- 旧:
240e:xxxxxxxx:ae43::/64 - 新:
240e:xxxxxxxx:6440::/64
集群是 4 节点 k3s 双栈,Ubuntu 26.04,内核 7.0.0-42。
/etc/rancher/k3s/config.yaml 的 node-ip 里写的是旧 GUA 的字面量。
新前缀下发后,那个地址不再存在于任何接口上。
kubelet 每 10 秒做一次 secondaryNodeIP 校验,失败,相关字段就不写入 Node 状态。
Node 对象的 IPv6 InternalIP 停在旧地址上。
根因
节点 IPv6 身份绑定在了 ISP 下发的 GUA 字面量上。
GUA 由运营商给,前缀什么时候轮换由 ISP 说了算。
家庭宽带的场景下,前缀轮换是常态,不是异常。
把节点身份写死在一个运营商随时会改的地址上,这个设计本身就有问题。
取证:告警之外的真实影响
告警只是入口,真正要看的是网络 Cilium 那边。
Cilium agent 启动时读取节点地址,写入 CiliumNode 对象。
旧地址写进去之后,auto-direct-node-routes 生成不出跨节点 IPv6 直连路由。
修复前 ip -6 route 里只有本机 pod 的 /64,没有任何一条跨节点路由。
跨节点 IPv6 pod 通信实际中断。
集群上 pod(semaphore、mysql)的 IPv6 EndpointSlice 指向一个不存在的地址。
📝Notes:
这类失效不触发业务告警。pod 本身是 Running 的,断的是跨节点路径。
方案取舍
四个方向摆出来对比:
| 方案 | 做法 | 代价 |
|---|---|---|
| A | 节点 IPv6 改用站点 ULA,GUA 只留接口做出站 / NAT | 一次性改动,之后不随 ISP 漂移 |
| B | systemd ExecStartPre 每次重算 GUA |
每次漂移都要重启,Node / Endpoint 全变 |
| C | 向 ISP 申请静态前缀 | 最干净,家庭宽带通常拿不到 |
| D | 退回单栈 IPv4 | 与既有双栈规格冲突,要重建 pod |
最终方案选 A。
我的判断是:节点身份只需要在站点内稳定可达,不需要绑定运营商前缀。
GUA 保留在接口上,出站和 NAT 照常用。
Pod 的 /56 和 Service 的 /112 网段不变,业务侧无感知。
选定的 ULA 是 fdb5:xxxxxxxx:1::/64,主机位 ::11 到 ::14:
::11→ n100-jumper-0::12→ n100-jumper-1::13→ n100-jumper-2::14→ n100-cheshi-0
实现:几处改动
- 修改
/etc/netplan/60-homelab-ula.yaml写入固定的规划好的 ULA IPv6 地址并netplan apply
YAML 内容如下:
# 由 Ansible 管理,手工改动会在下次运行时被覆盖。
#
# 稳定节点 IPv6:站点 ULA(RFC 4193),与 ISP 下发的 GUA 前缀解耦。
# 只写 ULA 这一条:netplan 按文件名顺序合并多个文件,addresses 是追加语义,
# 因此 cloud-init 的 50-cloud-init.yaml 里的 IPv4 地址与 dhcp6 不受影响。
# 依据与实测见 specs/012-stable-node-ula-ipv6/plan.md。
network:
version: 2
ethernets:
enp2s0:
addresses:
- fdb5:xxxxxxxx:1::14/64
IPv4 地址和默认路由都保留,不需要重写 cloud-init 生成的 50-cloud-init.yaml。该文件 YAML 内容示例如下:
# This file is generated from information provided by the datasource. Changes
# to it will not persist across an instance reboot. To disable cloud-init's
# network configuration capabilities, write a file
# /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg with the following:
# network: {config: disabled}
network:
ethernets:
enp2s0:
addresses:
- 192.168.3.154/24
dhcp4: false
dhcp6: true
gateway4: 192.168.3.1
nameservers:
addresses:
- 223.5.5.5
wakeonlan: true
version: 2
📝Notes:
netplan 多文件按文件名排序合并,
60-homelab-ula.yaml排在50-cloud-init.yaml之后。
生效路径:只重启 k3s 不够
只重启 k3s, 不重启 cilium agent: 这是整个处置里最容易漏的一步。
改完 config.yaml 重启 k3s,Node 对象的 InternalIP 会更新。但 cilium-dbg node list 里还是旧地址。跨节点路由还是缺的。Cilium agent 只在启动时读取 Node 地址,写进 CiliumNode。Node 更新了它不会自己重新读。必须逐台重启 cilium agent。
重启后 cilium-dbg node list 的 IPv6 列变成各自 ULA,ip -6 route 里出现 via <对方 ULA> 的直连路由。
📝Notes:
逐台重启,别一把全停。agent 停掉期间该节点的 pod 网络会短暂中断。
验证
线上验收覆盖 6 个维度:
- 4 台 Node 的 IPv6 InternalIP 均为规划 ULA,状态 Ready
secondaryNodeIP计数为 0cilium-dbg node list的 IPv6 列是各自 ULA- 出现 3 条跨节点直连路由
- 跨节点 pod IPv6 连通,三个业务命名空间 Running 且 HTTP 可用
TestNodeAddressingPASS
路由器侧也做了只读核查:LAN 是 br-lan 桥,没有 ULA 配置,没有该 /64 路由与邻居表项。节点间 IPv6 走二层转发,路由器不需要改动。
总结
本次的经验教训:
- 节点 IPv6 身份不该绑定 ISP 下发的 GUA,站点内可达就够了
- 告警的严重程度和实际影响面不一定对得上,要自己取证
- Cilium agent 只在启动时读 Node 地址,Node 更新不会自动传导过去
- netplan 多文件对
addresses是追加语义,独立文件可以只写 ULA
千磨万击还坚劲,任尔东西南北风。
IPv6 GUA 地址前缀会漂,架构不该跟着漂。
以上。
浙公网安备 33010602011771号