集群从 IPv4 单栈迁移到 IPv6/IPv4 双栈,绝对不是修改一个地址前缀就能完成的工作。节点需要同时维护两套邻居发现和路由表,容器网络接口插件需要为每个 Pod 分配两族地址,kube-proxy 要分别维护 IPv4 和 IPv6 的转发规则,DNS 组件也要能同时返回 A 和 AAAA 记录。任何一个环节出现配置偏差,都会表现为部分协议栈可以通信、另一栈完全不通,或者 Pod 获取地址后仍然无法建立连接。本文以 Kubernetes 集群为对象,完整梳理双栈网络的规划、部署和排障路径。

一、双栈网络规划与架构选择
地址规划是双栈部署的第一道门槛。Kubernetes 对 Pod 和 Service CIDR 支持同时配置 IPv4 与 IPv6 网段,中间用英文逗号隔开,顺序通常保持 IPv4 在前、IPv6 在后。Service CIDR 的 IPv6 段建议使用独立的 ULA 前缀,例如 fd00:10:96::/112,Pod CIDR 则使用 fd00:10:244::/56,这样既能避免与现网全局地址冲突,也能满足集群内部互通。节点自身必须具备可路由的全局 IPv6 地址或至少具备 ULA 地址,不能只保留链路本地 fe80 地址,否则跨节点 Pod 流量会因为找不到下一跳而被丢弃。
双栈部署对 CNI 插件有明确要求。Calico 和 Cilium 都支持 IPv4/IPv6 双栈,但配置入口完全不同。Calico 需要为 IPv6 单独声明 IPPool,并配置 blockSize 和 NAT 出站策略;Cilium 则通过 enable-ipv6 和 IPv6 Pod CIDR 参数开启。若使用 overlay 模式,还需要确认封装的隧道协议是否支持 IPv6。物理网络允许的情况下,BGP 或 host-gateway 模式可以避免额外的 IPv6 封装开销,但对节点间的 IPv6 路由连通性要求更高。
内核参数和 kubeadm 配置同样不能忽略。节点必须打开 IPv4 和 IPv6 转发,关闭 disable_ipv6。kubeadm 的 ClusterConfiguration 中需要启用 IPv6DualStack 特性门控,并同时向 api-server 和 controller-manager 传入双栈 service-cluster-ip-range。下面是一个最小化的 kubeadm 配置示例。
apiVersion: kubeadm.k8s.io/v1beta3
kind: ClusterConfiguration
kubernetesVersion: v1.28.0
featureGates:
IPv6DualStack: true
networking:
podSubnet: 10.244.0.0/16,fd00:10:244::/56
serviceSubnet: 10.96.0.0/12,fd00:10:96::/112
apiServer:
extraArgs:
service-cluster-ip-range: 10.96.0.0/12,fd00:10:96::/112
controllerManager:
extraArgs:
node-cidr-mask-size-ipv4: "24"
node-cidr-mask-size-ipv6: "64"
service-cluster-ip-range: 10.96.0.0/12,fd00:10:96::/112
---
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
featureGates:
IPv6DualStack: true
节点侧还需要通过 sysctl 固化参数。很多情况下集群已经运行了一段时间,IPv6 被默认关闭,导致 CNI 插件无法创建 IPv6 地址。执行以下配置并重启 NetworkManager 或 systemd-sysctl 服务后,节点才能稳定承载双栈流量。
# 写入 /etc/sysctl.d/99-ipv6-dualstack.conf net.ipv4.ip_forward = 1 net.ipv6.conf.all.forwarding = 1 net.ipv6.conf.all.disable_ipv6 = 0 net.ipv6.conf.default.disable_ipv6 = 0 net.ipv6.conf.lo.disable_ipv6 = 0 net.ipv6.conf.all.accept_ra = 2 net.ipv6.conf.default.accept_ra = 2 sysctl --system
二、启用 CNI 插件与双栈 Service
以 Calico 为例,安装完成后默认通常只会创建 IPv4 IPPool,IPv6 地址段不会被分配。需要手动创建 IPv6 池,指定与 kubeadm 配置一致的 Pod CIDR。如果使用 Operator 安装,可以直接在 Installation 资源中追加 ipPools;如果使用 manifest 安装,则通过 calicoctl 或 kubectl apply 创建 IPPool 对象。下面的示例创建了一个不启用 IPIP、允许 NAT 出站的 IPv6 池。
apiVersion: crd.projectcalico.org/v1 kind: IPPool metadata: name: ipv6-ip-pool spec: cidr: fd00:10:244::/56 blockSize: 64 ipipMode: Never natOutgoing: true nodeSelector: all()
Pod 网络准备好之后,Service 也需要显式声明双栈能力。默认 Service 采用单栈策略,只会使用集群默认地址族。要让 Service 同时获得 IPv4 和 IPv6 ClusterIP,必须将 ipFamilyPolicy 设置为 RequireDualStack,并通过 ipFamilies 指定两族顺序。若使用 PreferDualStack,则会在双栈能力不可用时退化为单栈,适合需要渐进升级的集群。
apiVersion: v1
kind: Service
metadata:
name: dualstack-svc
spec:
ipFamilyPolicy: RequireDualStack
ipFamilies:
- IPv4
- IPv6
selector:
app: web
ports:
- port: 80
targetPort: 8080
创建之后可以在控制节点执行 kubectl get svc dualstack-svc 查看是否同时分配了两个 CLUSTER-IP。进入任意 Pod 内,使用 curl -6 http://[fd00:10:96::1] 访问服务时,kube-proxy 需要能够根据 IPv6 目的地址正确挑选后端。如果 Pod 内只有 IPv4 路由,curl 会直接返回网络不可达,此时问题通常不在 Service,而在 Pod 的 IPv6 路由或 CNI 分配。
DNS 是双栈验证中容易被忽视的环节。当客户端通过域名访问双栈 Service 时,CoreDNS 会同时返回 A 和 AAAA 记录。若 CoreDNS 自身没有配置 IPv6 上游或没有监听 IPv6 地址,AAAA 查询可能超时,客户端会回退到 IPv4,导致双栈看似没有生效。可以通过 dig AAAA +short svc-name.namespace.svc.cluster.local 检查返回结果。
三、故障排查:从地址分配到转发链路
遇到 Pod 只有 IPv4 地址的故障,首先要确认节点 IPv6 是否开启、CNI 是否创建了 IPv6 IPPool,以及节点上是否存在对应 Pod CIDR 的 IPv6 路由。可以执行 ip -6 addr show scope global 查看节点全局 IPv6 地址,再执行 ip -6 route get fd00:10:244::10 测试到 Pod 地址的路径。如果返回 Network is unreachable,说明节点上的 IPv6 路由表不完整,需要检查 CNI 的节点路由注入过程,而不是继续修改 Pod 配置。
Service IPv6 访问异常时,最好的方法是缩小问题范围。先分别测试 Pod 到 Service ClusterIP 和节点到 Service ClusterIP 的连通性。如果节点能通但 Pod 不通,问题多半在 Pod 的 IPv6 默认路由或 CNI 策略;如果节点也不通,需要检查 kube-proxy。查看 ip6tables -t nat -L KUBE-SERVICES -n 中是否存在该 ClusterIP 对应的规则,ipvs 模式下还可以使用 ipvsadm -Ln 查看 IPv6 虚拟服务。kube-proxy 的双栈支持要求其配置中同时包含 IPv4 和 IPv6 的 cluster-cidr,只有单栈时无法生成 IPv6 规则。
对于外部访问 NodePort 失败的问题,必须先区分是 IPv4 还是 IPv6 链路异常。安全组或防火墙经常默认拒绝 IPv6 新端口,节点虽然监听了 IPv6 地址,外部仍无法建立连接。抓包是最直接的定位手段,例如在节点上执行 tcpdump -i any -nn 'ip6 and tcp port 80',观察 SYN 是否到达节点、节点是否回包。如果 SYN 被直接丢弃,需要检查 iptables 或 ip6tables 的 INPUT 链以及节点安全组;如果回包发出但客户端收不到,则要检查上游路由器或交换机的 IPv6 路由。
下面汇总了双栈排障中经常用到的一组命令,建议按照地址、路由、转发规则、抓包的顺序逐层执行。
kubectl get pod -n kube-system -l k8s-app=calico-node -o wide kubectl logs -n kube-system calico-node-xxxxx -c calico-node --tail=100 ip -6 addr show scope global ip -6 route get fd00:10:96::1 ip6tables -t nat -L KUBE-SERVICES -n | grep fd00:10:96 ipvsadm -Ln | grep fd00:10:96 tcpdump -i any -nn 'ip6 and tcp port 80'
双栈集群排障的核心思路是先确认地址是否分配,再确认路由是否可达,最后检查转发规则和链路层丢包。只要按这个顺序排查,大多数问题都能在较短时间内定位到具体组件。