双栈 ClusterIP 配好了,IPv6 就是不通——少加载了一个内核模块

场景:集群升级双栈后,Service 的 IPv6 ClusterIP 永远连接超时、IPv4 正常——不是配置错了,是 kube-proxy 的 ip6tables 规则压根没写进去 路径:坐标(双栈 Service 创建 + IPv6 超时症状)→ 分层(Pod 内 DNS → Service 配置 → kube-proxy 规则 → Node 内核模块)→ 路径(3 条排查命令定乾坤)→ 定位(iptables 正常 ≠ ip6tables 正常)→ 标点(加载模块 + Check-list) K8s 版本:v1.28,kube-proxy iptables 模式,Calico v3.27

上篇讲了双栈 Service 的 ipFamilies 顺序会把一个协议族降级成"备选地址"——写错了顺序,IPv4 就成了二等公民。这次的问题方向不同:ipFamilies 写对了,两个 ClusterIP 都分配了,但 IPv6 的 ClusterIP 永远连接超时。

一个 Service 配了 ipFamilyPolicy: RequireDualStackkubectl get svc -o yaml 显示两个 ClusterIP 都已分配。从 Pod 里 curl IPv4 ClusterIP 秒回、curl IPv6 ClusterIP 卡到超时——"CNI(Container Network Interface,容器网络接口,负责为 Pod 分配网络地址)不支持双栈?""路由没配?"都不是。问题在 kube-proxy(K8s 集群网络代理组件,负责将 Service ClusterIP 的流量转发到后端 Pod)的规则同步:iptables -w 5 -t nat -L 有 KUBE-SERVICES 链,ip6tables -w 5 -t nat -L 直接报错。不是配置错了——是 ip6table_nat 内核模块压根没加载。


金句:双栈不只是改个 YAML 字段——是两个独立协议栈的规则同步,缺一个内核模块,kube-proxy 忙活 30 秒然后认输。


【坐标】IPv4 通、IPv6 不通

Layer 1 — 结论前置:双栈 Service 配完后 IPv6 ClusterIP 连接超时,不是 CNI 问题,是 kube-proxy 没生成 IPv6 方向的 DNAT 规则。

部署背景

部署项 配置
消费方 Pod web-frontend,Namespace prod10.244.1.15
目标服务 order-svc.prod:8080,HTTP
Service 类型 ClusterIP,ipFamilyPolicy: RequireDualStack
网络插件 Calico v3.27(双栈模式)
集群版本 K8s v1.28,kube-proxy iptables 模式
集群 IPv4 范围 --service-cluster-ip-range=10.96.0.0/12
集群 IPv6 范围 --service-cluster-ip-range=2001:db8:1::/48

Service YAML:

apiVersion: v1
kind: Service
metadata:
  name: order-svc
  namespace: prod
spec:
  ipFamilyPolicy: RequireDualStack
  ipFamilies:
  - IPv6
  - IPv4
  selector:
    app: order
  ports:
  - port: 8080
    targetPort: 8080

Layer 2 — 证据展开kubectl get svc -o yaml 确认两个 ClusterIP 都已分配。

$ kubectl get svc order-svc -n prod -o yaml | grep -E 'clusterIP:|clusterIPs:'
  clusterIP: 2001:db8:1::14
  clusterIPs:
  - 2001:db8:1::14
  - 10.96.1.20

kubectl get svc -o wide 只显示主 ClusterIP(IPv6),副 ClusterIP(IPv4)需要通过 -o yaml 查看。IPv4 ClusterIP 10.96.1.20 和 IPv6 ClusterIP 2001:db8:1::14 都已分配。从 Pod 里分别测试:

双栈 Service 症状和DNS解析结果

IPv4 秒回、IPv6 超时,DNS 解析都正常。

Layer 3 — 衔接:ClusterIP 已分配、DNS 已解析、就是连不上。问题不在 Pod 也不在 Service 配置——是流量到了 Node 之后,没人做 DNAT(目标网络地址转换,kube-proxy 通过 DNAT 将 ClusterIP 流量转发到后端 Pod)。


【分层】Pod → kube-proxy → 内核模块

排查顺序表:不跳层,从最上层逐级往下。

排查对象 命令
1 Pod 内 DNS kubectl exec <pod> -- dig +short order-svc.prod A order-svc.prod AAAA
2 Service 配置 kubectl describe svc order-svc -n prod
3 kube-proxy 规则 (IPv4) iptables -w 5 -t nat -L KUBE-SERVICES -n -v \| grep <clusterip>
4 kube-proxy 规则 (IPv6) ip6tables -w 5 -t nat -L KUBE-SERVICES -n -v 2>&1 \| grep <clusterip>
5 Node 内核模块 lsmod \| grep ip6

Layer 1 — Pod 内 DNS 解析

$ kubectl exec -n prod web-frontend -- bash -c 'dig +short order-svc.prod A; dig +short order-svc.prod AAAA'
10.96.1.20
2001:db8:1::14

两个 ClusterIP 都解析到了。DNS 没问题。

Layer 2 — Service 配置

$ kubectl describe svc order-svc -n prod
Name:              order-svc
Namespace:         prod
Type:              ClusterIP
IP Families:       IPv6,IPv4
IP:                2001:db8:1::14
IPs:               2001:db8:1::14, 10.96.1.20
Port:              <unset>  8080/TCP
TargetPort:        8080/TCP
Endpoints:         10.244.2.10:8080

Service 配置正常,Endpoints(记录 Service 后端 Pod 地址的资源对象)有后端 Pod。

Layer 3 — kube-proxy IPv4 规则

在 Service 所在 Node 上执行:

$ iptables -w 5 -t nat -L KUBE-SERVICES -n -v | grep order-svc
    0     0 DNAT       tcp  --  *      *       0.0.0.0/0       10.96.1.20       /* prod/order-svc */ tcp dpt:8080 to:10.244.2.10:8080

IPv4 的 DNAT 规则正常——命中 10.96.1.20:8080 的 TCP 请求会被 DNAT 到后端 Pod 10.244.2.10:8080

Layer 4 — kube-proxy IPv6 规则【发现点】

$ ip6tables -w 5 -t nat -L -n -v 2>&1
ip6tables v1.8.10 (legacy): can't initialize ip6tables table `nat'
Table does not exist (do you need to insmod?)

不仅找不到 KUBE-SERVICES 链——ip6tables 命令本身就无法连接 nat 表。不是规则没写,是内核模块没加载,ip6tables 根本不能用。

衔接:kube-proxy 在 dual-stack 模式下通过 ip6tables-restore 管理 ip6tables 规则。如果所有 ip6tables 操作都报错——说明不是 kube-proxy 不写规则,是底层内核不支持。

双栈流量路径对比:IPv4 DNAT 正常 / IPv6 规则缺失


【路径】🔍 双栈 Service 排查先跑这三步

下次遇到双栈 Service 异常,按这个顺序排查

# 1. 确认 Service 双栈 ClusterIP
kubectl get svc <svc-name> -n <ns> -o yaml | grep clusterIPs

# 2. 对比两张规则表(加 -w 5 避免与 kube-proxy 锁冲突)
iptables -w 5 -t nat -L KUBE-SERVICES -n -v | grep <clusterip>
ip6tables -w 5 -t nat -L KUBE-SERVICES -n -v 2>&1 | grep <clusterip>
# 注意:ip6tables 若直接报错而非返回空规则 → 内核模块缺失
# 应在检查规则前先确认 ip6tables 命令本身可用

# 3. 检查 ip6tables 内核模块
lsmod | grep ip6
# 应有:ip6table_nat、ip6table_filter、ip6_tables、ip6table_mangle

判断标准:

现象 问题 方向
ip6tables 直接报错 内核模块缺失 lsmod \| grep ip6modprobe ip6table_nat
iptables 有规则,ip6tables 返回空表 kube-proxy IPv6 规则同步失败 查 kube-proxy 日志 + --cluster-cidr
ip6tables 有 KUBE-SERVICES,但缺特定规则 IP 族偏好/顺序问题 ipFamilies

双栈 Service 诊断三步排查命令


【定位】iptables 正常 ≠ ip6tables 正常

Layer 1 — 结论前置:双栈排查最大的盲区是"查了 iptables 就当查完了"——iptables 只看 IPv4,IPv6 的规则在 ip6tables 里。

❌ 大多数人会怎么查

看到 IPv6 不通: 1. 查 DNS → 解析正常 ✅ 2. 查 Service → ClusterIP 已分配 ✅ 3. 查 Pod → 运行正常 ✅ 4. iptables -t nat -L → KUBE-SERVICES 有规则 ✅

然后陷入 "CNI 不支持双栈?""Node 间路由不通?"的猜测中,开始抓包、查路由表,越查越远。

✅ 正确的排查思路

iptablesip6tables 是两套独立的规则表。kube-proxy 在 dual-stack 模式下必须同时写入这两张表。查完 iptables 必须查 ip6tables。

$ ip6tables -w 5 -t nat -L -n -v
ip6tables v1.8.10 (legacy): can't initialize ip6tables table `nat'
Table does not exist (do you need to insmod?)

根因:ip6table_nat 内核模块未加载。kube-proxy 调用 ip6tables-restore 时失败,日志里输出 Failed to execute iptables-restore,跳过本轮 IPv6 规则同步,30 秒后周期性重试。

原理点睛:ClusterIP 不在网络层,在 iptables 层——你抓包永远看不到 ClusterIP 报文,因为它到 Node 就被 DNAT 转走了。查双栈必须先确认 DNAT 规则在对应协议栈上存在。

ip6tables 空表与 modprobe 修复


【标点】加载模块 + 重启 kube-proxy

Layer 1 — 结论前置:双栈需要 Node 内核加载 ip6table_nat 等 ip6tables 模块,kube-proxy 才能写入 IPv6 NAT 规则。

修复:Node 加载内核模块

在所有 Node 上执行:

# 1. 加载 ip6tables 相关模块
sudo modprobe ip6_tables
sudo modprobe ip6table_filter
sudo modprobe ip6table_nat
sudo modprobe ip6table_mangle

# 2. 持久化(不同发行版路径略有不同,需要 root)
sudo tee /etc/modules-load.d/ip6tables.conf > /dev/null << 'EOF'
ip6_tables
ip6table_filter
ip6table_nat
ip6table_mangle
EOF

验证加载:

$ lsmod | grep ip6
ip6table_mangle         16384  0
ip6table_nat            16384  0
nf_nat                  81920  2 ip6table_nat,nft_chain_nat
ip6table_filter         16384  0
ip6_tables              40960  4 ip6table_nat,ip6table_filter,ip6table_mangle

验证:kube-proxy 重新同步规则

# 重启 kube-proxy DaemonSet(确保集群中每个 Node 运行一个 Pod 副本的控制器,重启会触发所有节点重新同步规则)
kubectl rollout restart -n kube-system daemonset kube-proxy

# 等待 30 秒后验证 IPv6 规则
$ ip6tables -w 5 -t nat -L KUBE-SERVICES -n -v | grep 2001:db8:1::14
    0     0 DNAT       tcp  --  *      *       ::/0    2001:db8:1::14    /* prod/order-svc */ tcp dpt:8080 to:10.244.2.10:8080

可以测试连通性:

$ kubectl exec -n prod web-frontend -- curl -sI http://[2001:db8:1::14]:8080
HTTP/1.1 200 OK    # ✅ IPv6 通了

Check-list:双栈 Service 排查清单

每条对应一个命令:

# □ Service 双栈配置确认
kubectl get svc <svc-name> -n <ns> -o yaml | grep -E 'ipFamily|clusterIP'

# □ 确认 Node 已开启 IPv6 转发(不仅仅是 Pod 网络)
sysctl net.ipv6.conf.all.forwarding

# □ iptables 规则确认(IPv4)
iptables -w 5 -t nat -L KUBE-SERVICES -n -v | grep <clusterip>

# □ ip6tables 规则确认(IPv6)- 大部分人漏这步
ip6tables -w 5 -t nat -L KUBE-SERVICES -n -v 2>&1 | grep <clusterip>

# □ 确认 ip6tables 内核模块已加载
lsmod | grep ip6

# □ 检查 kube-proxy 日志是否有 ip6tables 报错
kubectl logs -n kube-system -l k8s-app=kube-proxy | grep -i 'ip6\|error\|fail'

# □ 双栈 Pod 内连通性验证
kubectl exec -it <pod> -- curl -sI http://<ipv6-clusterip>:<port>

修复命令:加载模块 + 重启 kube-proxy


附:完整命令清单

# === 诊断阶段 ===

# Service 双栈 ClusterIP
kubectl get svc <svc> -n <ns> -o yaml | grep clusterIPs

# iptables DNAT 规则(IPv4)
iptables -w 5 -t nat -L KUBE-SERVICES -n -v | grep <clusterip>

# ip6tables DNAT 规则(IPv6)
ip6tables -w 5 -t nat -L KUBE-SERVICES -n -v 2>&1 | grep <clusterip>

# 内核模块检查
lsmod | grep ip6

# kube-proxy 日志
kubectl logs -n kube-system -l k8s-app=kube-proxy | grep -i 'ip6\|error\|fail'

# === 修复(需要 root) ===

# 加载模块
sudo modprobe ip6_tables ip6table_filter ip6table_nat ip6table_mangle

# 持久化
echo -e "ip6_tables\nip6table_filter\nip6table_nat\nip6table_mangle" | sudo tee /etc/modules-load.d/ip6tables.conf

# 重启 kube-proxy
kubectl rollout restart -n kube-system daemonset kube-proxy

# 验证
ip6tables -w 5 -t nat -L KUBE-SERVICES -n -v

下篇我们聊:DNS 解析到了 IPv6 地址但连不上——AAAA 记录与 NetworkPolicy 的冲突。

核心洞察:ClusterIP 不在网络层,在 iptables 层。查双栈 Service 别只看 iptables——IPv6 的规则在另一张表里,而那张表可能因为缺一个内核模块根本写不进去。