先说一个我亲身经历的场景。去年中旬,某项目集群因为业务线扩张,容器数量直接翻了一倍,当初图省事规划的 /16 Pod 网段眼看就要耗尽,更麻烦的是,另一个新成立的团队在规划网络时,直接把新的业务网段定在了和集群节点网段冲突的位置。两边一碰,问题全堆到我这里:集群网络怎么改?Pod CIDR 和 Service CIDR 到底能不能动?动的话风险有多大?
说实话,这两个网段是 Kubernetes 集群里最“动一发牵全身”的配置。Service CIDR 本质上是 kube-apiserver 里--service-cluster-ip-range参数控制的,Pod CIDR 则由 CNI 插件和 kube-controller-manager 共同决定,看起来只是几个参数的事,实际改起来涉及 kubelet、kube-proxy、CoreDNS、网络插件数据面,甚至所有存量 Pod 和 Service 的重建。我这篇就围绕 Kubernetes 修改 Pod CIDR 以及 Service CIDR 的完整流程展开,把原理、步骤、坑位和排查思路都捋一遍。适合自己维护集群的运维、SRE,以及被网络规划问题逼到头大的平台工程师参考。
1. 动 CIDR 这件事,真实场景与整体思路
1.1 什么情况下不得不动 CIDR
大部分团队一开始部署集群时,用的都是安装工具默认的网段,比如 Pod 用 10.244.0.0/16,Service 用 10.96.0.0/12。这套默认值在测试环境够用,到了生产环境就经常出事。
最常见的三类触发场景:
网段冲突。公司和其它业务系统之间做直接路由打通,或者上混合云、多集群联邦,结果两边网段撞了。比如集群 Pod 网段是 10.244.0.0/16,而某个内部系统的服务器网段也是 10.244.0.0/16,一打通路由就全乱套,业务方隔三差五报“访问超时”“连接被重置”。
容量不够。一个 /16 的 Pod 网段最多 65534 个可用 IP(还得去掉网络号和广播地址)。看起来很多,但 Kubernetes 分配 Pod IP 是按节点划分的,比如每个节点默认 /24,最多 254 个 Pod 地址。如果单节点 Pod 密度高、Node 数量多,很快就会出现节点有资源但分配不到 IP 的情况。Service 网段也一样,集群里 Service 动辄上千个,再加上 NodePort、LoadBalancer 类型的,数量涨得飞快。
架构调整。公司网络策略要求统一规划内网网段,例如所有容器网段必须在 172.16.0.0/12 范围内,方便防火墙统一管控。这时候无论 Pod 还是 Service 的 CIDR 都得跟着改。
1.2 先搞清楚这两个网段到底谁说了算
很多人容易把 Pod CIDR 和 Service CIDR 混为一谈,修改之前必须把责任组件分清楚。
Pod CIDR 是分配给每个 Node 的子网段,再由节点上的 CNI 插件(比如 Calico、Flannel)按这个子网段给 Pod 分配 IP。核心控制点有两个:
- kube-controller-manager 的
--allocate-node-cidrs和--cluster-cidr参数 - CNI 插件自己维护的 IP 池,例如 Calico 的 IPPool,Flannel 的 Network 配置
需要注意,--cluster-cidr控制的是“节点从哪里取块”和“调度器判断 Pod 是否可达”的逻辑,而真正下发 Pod IP 的是网络插件数据面。所以只改 kube-controller-manager 而不改 CNI 配置,Pod 是不会真正走到新网段的。
Service CIDR 则基本完全由 kube-apiserver 的--service-cluster-ip-range决定,分配逻辑在 apiserver 内部完成,不经过 CNI 插件。Service 一旦创建,它的 ClusterIP 就固定不变了,kube-proxy 据此生成转发规则。所以 Service CIDR 的真实改动范围是:apiserver 重启 + 全部存量 Service 重建 + kube-proxy 规则刷新。
1.3 修改难度的核心差异:Pod 可以重建,Service 也能重建,但关联方太多
这里要先给一个整体结论:Pod CIDR 和 Service CIDR 都可以改,但都不存在“改一个参数就自动迁移”的优雅方案。两个本质的区别在于:
Pod CIDR 修改,需要把每个节点的 CNI 配置和节点上存量 Pod 全部换掉。如果把集群当成一个小区,Pod 网段就是门牌号的分配规则——规则改了,所有门牌号都得重新贴。这就意味着,要么重建集群节点,要么迁移节点,要么把存量工作负载全部滚动重建。涉及的连带操作非常多。
Service CIDR 修改,本质上只影响 ClusterIP 的分配,存量 Service 删掉重建即可,Pod 自身的网络不用动。但同样麻烦的是,Service 删除重建会导致 ClusterIP 变化,期间服务会中断,依赖于固定 ClusterIP 的配置(比如白名单、外部 DNS 记录、跨 namespace 调用)全部要同步调整。
所以整体思路就一句话:先评估业务能不能接受服务抖动和 IP 漂移,再决定用哪种方案,最后才是动手改配置。如果业务完全不能接受重启,那这类变更根本不该进入实施阶段,而是要重新规划一个新的集群然后把业务迁移过去。
2. 动手前,把集群状态摸清楚
2.1 需要收集的信息清单
无论你打算改哪个 CIDR,第一步一定是完整收集集群现状。我建议至少整理下面这张表,按实际环境填好再继续。
| 项目 | 需要确认的内容 | 获取方式 |
|---|---|---|
| 集群版本 | kube-apiserver / kubelet / kube-controller-manager 的具体版本 | kubectl version |
| 网络插件 | Calico、Flannel、Cilium 等;数据面类型(VXLAN、BGP、IPIP) | kubectl get ds -n kube-system |
| Pod CIDR 现状 | 配置在 kube-controller-manager 和 CNI 里的实际网段 | 查看静态 Pod manifest 或kubectl -n kube-system get cm |
| Service CIDR 现状 | apiserver 的启动参数 | 查看 apiserver manifest 或kubectl get cm -n kube-system kubeadm-config |
| 节点子网划分 | 每个 Node 当前拿到的 PodCIDR | kubectl get node -o custom-columns=NAME:.metadata.name,POD_CIDR:.spec.podCIDR |
| Service 数量与依赖 | ClusterIP 是否被外部系统记录;是否依赖固定 IP 的白名单 | kubectl get svc -A -o wide |
| 工作负载类型 | 是否有 StatefulSet、DaemonSet、Job;是否有 PodDisruptionBudget | kubectl get pdb -A、kubectl get sts -A |
我遇到比较典型的情况是,集群用了 kubeadm 部署,各组件都是静态 Pod 方式运行,修改配置时需要直接改/etc/kubernetes/manifests/下的 YAML 文件。如果是二进制方式部署,就要改 systemd unit 文件里的 ExecStart 参数。这两种方式在操作手法上有细节差异,但整个流程脉络是通用的。
2.2 影响面评估:哪些组件会参与这次变更
改之前给所有组件做一个“影响面清单”,这一步能直接决定变更方案的风险等级。
kube-apiserver:涉及 Service CIDR 修改时,apiserver 是唯一真正的分配者。重启后旧的 ClusterIP 分配记录还在 etcd 里,但新创建的 Service 会按新网段分配,新旧网段不会自动融合。需要删除旧 Service 重新创建,让它们拿新网段的 IP。
kube-controller-manager:涉及 Pod CIDR 修改时,它负责给节点分配 Pod 子网。如果改了--cluster-cidr但节点上spec.podCIDR还是旧值,CNI 插件会用旧值继续工作,结果就是新旧网段同时存在,路由混乱。所以需要把节点上的 PodCIDR 也一起换掉,通常通过删除节点再重新加入来实现,或者手动编辑节点对象。
kubelet:kubelet 启动时会向 apiserver 注册节点和 PodCIDR,并且使用 CNI 配置给 Pod 建网络。节点重启或重新加入集群时,它会按新的配置设置网络。如果节点不重启,上面的 CNI 配置不会自动重载,需要手动清理旧 Pod 并重建。
kube-proxy:默认使用 iptables 或 IPVS 模式。Service 的 ClusterIP 对它而言是转发目标,Service 重建后规则随之更新,但老的规则条目往往会残留一小段时间,特别是在 IPVS 模式下,需要清理。
CoreDNS:它自己也是一个 Deployment,背后有 Service。改动 Service CIDR 后,CoreDNS 的 Service ClusterIP 会变,如果 kubelet 的--cluster-dns参数还指向旧地址,集群内所有 Pod 的 DNS 解析就全断了。这一点在步骤里必须被单独拎出来处理。
CNI 插件:对 Pod CIDR 的改动,插件是第一关键。Flannel 用的是后端网络配置里的 NetConf;Calico 则把 IP 池信息存在 etcd 或 Kubernetes 自定义资源里。改完 apiserver 侧还不够,插件侧的地址池也必须同步,否则 Pod 拿到的 IP 完全不在路由表里,节点之间互相不通。
2.3 备份和回滚方案:没人希望做到一半发现回不去
改 CIDR 属于高风险控制面变更,备份不是可选项,而是必须项。
最重要的备份是 etcd。我一般建议在生产变更前做一次 etcd 快照,并且把快照文件放到集群外部存储,防止集群挂掉以后连备份都取不出来。快照命令很简单:
etcdctl snapshot save /data/etcd-backup-$(date +%F).db \ --endpoints=https://127.0.0.1:2379 \ --cacert=/etc/kubernetes/pki/etcd/ca.crt \ --cert=/etc/kubernetes/pki/etcd/server.crt \ --key=/etc/kubernetes/pki/etcd/server.key除了 etcd,还要把/etc/kubernetes/manifests/整个目录复制一份,里面是所有控制面组件的静态 Pod 配置。改坏了可以立刻恢复原参数。
第三份备份是集群全网段的导出清单。把所有 Service 的 name、namespace、ClusterIP 记录下来,万一要回滚,至少知道原来哪些 IP 被哪些 Service 占着,不至于恢复时撞地址。
回滚方案也要提前想清楚。假如 Service CIDR 改到一半,apiserver 起不来了,最快的回滚方式是直接把备份的 manifest 覆盖回去,重启 kubelet 让它重新拉起 apiserver。如果已经删掉了一堆 Service,那就从备份清单里一个个重建。所以备份清单里最好把 Service 的 selector、port、type 也一起记录下来,重建时直接套用。
2.4 关于演练环境的一点建议
我在实际操作中吃过亏,曾经在只有单个节点的测试集群上验证修改流程,结果测试集群网络配置天天出问题,生产上完全不是一回事。原因是单节点集群没有跨节点的路由问题,很多坑根本暴露不出来。
建议至少在三个节点以上的测试集群上演练一遍,并且最好把网络插件的模式也保持一致。例如生产用 Calico BGP 模式,测试就不能用 Flannel VXLAN,不同数据面的行为差别很大。演练时要刻意验证跨节点 Pod 通信、Service 访问、CoreDNS 解析三件事,这三条通路都通了,才说明新 CIDR 真正生效。
3. 修改 Pod CIDR 的实操要点:两种路线与完整步骤
3.1 路线选择:新集群迁移还是原地重建
Pod CIDR 的修改方案,本质上取决于你愿不愿意接受节点重建。我的经验是,绝大多数生产集群应该选择“不原地修改,而是迁移”。
原因很简单:原地修改 Pod CIDR 意味着每个节点上的所有 Pod 都要删除重建,Node 的 PodCIDR 也要变,而许多 CNI 插件对已分配的子网信息是有持久化状态的,清理不干净就会残留旧路由。与其在刀尖上跳舞,不如把集群当“积木”重新搭一遍。
迁移路线的大框架是:创建一个新的集群(或者扩大原有集群并接入新的节点池),在新网段上运行业务,验证稳定后把流量切过来,最后淘汰旧节点。这个过程中的业务中断窗口取决于应用本身能不能滚动重建。对无状态服务来说非常平滑,对有状态服务来说仍需要专门设计数据迁移方案。
3.2 原地修改的实操步骤(高风险但不可不知)
虽然迁移更稳,但有些场景确实没法新建集群,比如资源有限、域名和证书绑定太深,这时候就得走原地修改路线。下面这套流程我在多个环境验证过,可以缩小风险范围:
第一步:停止调度并驱逐存量 Pod。用 kubectl cordon 把所有 worker 节点标记为不可调度,然后 drain 掉上面的 Pod。这样保证修改过程中不会有新 Pod 被调度到旧网段上:
kubectl cordon node-a node-b node-c kubectl drain node-a --ignore-daemonsets --delete-emptydir-data这里注意,--ignore-daemonsets必须带上,否则 DaemonSet 的 Pod 永远驱逐不掉,命令会卡在那里。
第二步:修改 kube-controller-manager 参数。在控制面节点上编辑/etc/kubernetes/manifests/kube-controller-manager.yaml,把--cluster-cidr改成目标网段,比如从 10.244.0.0/16 改成 10.200.0.0/16。如果之前没启用--allocate-node-cidrs,这一步要同步加上。
第三步:修改 CNI 插件的 IP 池配置。以 Calico 为例,需要编辑 IPPool 对象:
kubectl edit ippool default-ipv4-ippool把里面的cidr字段改成和--cluster-cidr一致。Flannel 的情况则要编辑kube-flannel-cfgConfigMap,修改Network字段。这一步做完,网络插件的数据面才知道新网段长什么样。
第四步:重置节点并重新加入。逐个节点执行kubeadm reset,然后重新kubeadm join。这个操作会清空节点上的 kubelet 配置、CNI 配置和旧 Pod 网络,让节点重新从新的 Pod CIDR 里获取子网。注意 join 之后节点会处于 NotReady 状态,直到网络插件在新网段上把 Pod 网络建起来。
第五步:重新安装网络插件(如果有必要)。部分网络插件在节点重置后需要重新部署 DaemonSet 才能恢复数据面。我当时用的 Calico,重置节点后需要重新 apply 一遍 Calico 的 manifest,确保新节点上有最新的 Felix 和 BIRD 配置。
第六步:验证全部工作负载。先把节点 untaint 并取消 cordon:
kubectl uncordon node-a然后观察 Pod 是否被重新调度,重点关注跨节点通信是不是正常,比如任意两个节点上的 Pod 能否互相 ping 通。
这套流程的整体风险在于:在第四步和第五步之间集群处于破碎状态,旧节点被重置,新节点还没完全就绪,如果中间任何一个组件配置不对,整个集群可能面临长时间不可用。所以必须选择业务低峰期执行,并且把每一步的验证都做扎实,不要一口气把所有节点全部重置。
3.3 Calico 和 Flannel 在 Pod CIDR 修改上的差异
很多人在这一步翻车,核心原因是没搞清“控制器参数”和“数据面 IP 池”是两个独立系统。
Calico 强依赖IPPool对象。如果只改 kube-controller-manager 的--cluster-cidr而没有同步修改 Calico 的 IPPool,节点上每个 Pod 会接到一个“节点认为合法但路由表里不存在”的 IP,现象是 Pod 能创建、能启动,但跨节点完全不通。检查办法是用calicoctl get ipPool或kubectl get ippool看当前池的 CIDR 是否和新配置一致。
Flannel 的做法是把整个集群当做一个大二层网络来设计,它读取的是 ConfigMap 里Network字段,并且通过 VXLAN 或 host-gw 模式维护路由。Flannel 最头疼的地方在于它的数据面状态存在/var/lib/cni/和/var/lib/flannel/目录下,节点重置时如果不彻底清理这两个目录,重新加入后会出现新旧网段的路由残留。重置后我建议手动删除这两个目录再重新初始化。
Cilium 的机制又不一样,它把 IP 分配放在CiliumNode自定义资源里,修改时需要更新 Cilium 的配置和相应节点资源。但总的原则一致:控制面和数据面必须同步修改。
3.4 原地方案里几个容易被忽略的连带操作
Pod CIDR 改动时,光盯着节点和 CNI 还不够,下面这几个连带操作很容易被忽略,但漏掉任何一个都会导致后续一堆问题。
集群内的 NetworkPolicy。如果集群用了 Calico 的 NetworkPolicy,网络策略里的 CIDR 规则往往是建立在旧网段上的。网段变化后,策略不会自动更新,需要逐个检查并修改所有涉及网段的策略,否则有些 Pod 会莫名变成“完全隔离”状态。
监控和日志采集器。Prometheus、Fluentd 这些组件如果是通过 Kubernetes 发现目标,可能没问题,但如果有手动配置的静态采集目标(例如直接把 Pod IP 写进了采集配置),网段一变,所有静态目标全部失效。
节点防火墙规则。有些节点上可能有手工配置的 iptables 规则,用来放行某些 Pod 子网的流量。这里说的不是 kube-proxy 纳管的规则,而是运维自己加的。网段修改后,旧规则里的源地址段就再也匹配不到新流量,需要同步调整。
3.5 实操记录参考:一次典型的迁移式变更
我这边去年做过一次类似的调整,当时集群规模大概是 30 个节点、800 个 Pod 左右。最终采用的是“扩新缩旧”的迁移方案:
先扩容加入一批新节点,新节点和旧节点在同一个集群里,但预先在新节点上打上了标签。通过节点亲和性把关键业务优先调度到新节点,然后逐个cordon旧节点并drain,把旧节点的 Pod 全部赶走。旧节点清空后从集群移除。整个过程中,Pod 的 IP 确实变了,但由于所有 Pod 都是重建的,应用只需要处理一次重启和短暂的流量中断,不存在“改了一半”的状态。
这个方案的好处是,如果新网段上出了问题,随时可以重新启用旧节点,把业务调度回去,整个过程可以反复来回切换。代价是需要多准备一批节点的资源,适合资源不那么紧张的团队。
4. 修改 Service CIDR 的实操细节:为什么必须重建全部 Service
4.1 Service ClusterIP 的“一次分配、终身不变”特性
Service CIDR 的修改比 Pod CIDR 更隐蔽,因为它的根因在 kube-apiserver 内部,外面看不出来。apiserver 在启动时加载--service-cluster-ip-range,之后所有 Service 的 ClusterIP 都从这个池子里分配,并且一旦分配就写入 etcd,后续不会再变更。
这意味着,如果你只是把 apiserver 的--service-cluster-ip-range从一个网段改成另一个网段,而存量 Service 还占着旧网段的 IP,那么新旧两个网段的 IP 会在集群内部同时存在。kube-proxy 会同时维护新旧规则,看起来好像也通了,但一旦旧 Service 删除或节点重启,旧网段的规则就会逐渐蒸发,业务衰竭的过程非常难以捉摸。
正确的思路就是:要么接受全部存量 Service 重建,要么别轻易动这个参数。
4.2 Service CIDR 修改的完整流程
下面这套流程是我认为在生产环境下最稳妥的顺序:
第一步:备份所有 Service 定义。先做一次全量导出:
kubectl get svc -A -o yaml > svc-backup-$(date +%F).yaml这个备份文件不仅用于回滚,后面的重建也可以直接从这个文件批量生成清单。
第二步:记录外部依赖。把 Service 的 ClusterIP 和端口信息整理成清单,给所有可能引用这些地址的同事或配置项确认一遍。如果外部数据库白名单写死了某个 ClusterIP,你需要提前让他们准备好新 IP 的放行。
第三步:修改 kube-apiserver 参数。编辑/etc/kubernetes/manifests/kube-apiserver.yaml,把--service-cluster-ip-range改掉。例如 10.96.0.0/12 → 10.112.0.0/12。修改后 kubelet 会自动重新拉起 apiserver 容器。可以用如下命令确认 apiserver 的启动参数已经变成新值:
cat /etc/kubernetes/manifests/kube-apiserver.yaml | grep service-cluster第四步:更新 kubelet 的 DNS 配置。这一步非常容易被忽略。CoreDNS 的 Service ClusterIP 在新网段分配后会改变,而 kubelet 需要通过--cluster-dns参数把 DNS 地址注入到每个 Pod 的/etc/resolv.conf。如果这里不更新,所有新建 Pod 的 DNS 都会指向旧 IP,解析直接挂掉。
如果你用 kubeadm,可以在kubeadm-configConfigMap 里修改,然后逐个节点更新 kubelet 配置并重启 kubelet。二进制方式部署的集群则直接改/var/lib/kubelet/kubeadm-flags.env或 systemd unit。
第五步:重建全部存量 Service。这是整个操作里最耗时、最容易出错的一环。直接kubectl delete svc --all -A会对所有 namespace 生效,但这种方式会把一些系统组件(比如 kubernetes 这个默认 Service)也删掉,存在一定风险。
我建议的做法是:先重建集群核心 Service(kubernetes 默认的 443 端口 Service、CoreDNS 的 Service),确认 apiserver 对内的访问恢复正常,再分批重建业务 Service。批量重建时可以直接基于备份清单改掉 IP 字段(实际上不用改,重新创建就会自动拿新地址),但要特别检查 Service selector 是否一致。
第六步:清理 kube-proxy 的残留规则。全部 Service 重建后,用以下命令检查节点上的 iptables 规则,看是否还有旧网段的残留:
iptables -t nat -L -n | grep <old-service-cidr>如果有残留,可以重启所有节点的 kube-proxy,让规则从新 Service 对象重新生成。IPVS 模式下需要清理 ipvs 表:
ipvsadm -L -n | grep <old-service-cidr>残留规则通常不会造成致命问题,但会一直占用系统资源,长时间不清理还会干扰问题排查。
4.3 重建 Service 时的顺序与依赖问题
重建 Service 并不是简单删了再建就完事,里面有一层依赖关系很容易被忽略:Deployment/StatefulSet 的 Pod 环境变量里如果注入了其他 Service 的 ClusterIP(比如通过 Downward API 或 env 方式引用了某个 Service 的地址),那么这些 Pod 必须在目标 Service 重建之后重新创建,才能拿到新的 ClusterIP。否则环境变量里还是旧地址。
对于通过 DNS 访问的 Service,由于 Pod 的 /etc/resolv.conf 指向 CoreDNS,而 CoreDNS 会动态解析 Service 名到最新的 ClusterIP,这种依赖通常不存在问题。真正值得担心的是那些“绕开 DNS、直接用 ClusterIP”的存量配置,例如应用配置文件里写死了某个内网地址。
我当时在处理某项目的订单同步服务时,就碰到过一个外部系统把集群内某个 Service 的 ClusterIP 写死到连接池配置里的情况。当时没提前告知,结果 Service 一重建,外部系统的所有请求全部超时。从那以后,我都会在变更前把“谁在引用 ClusterIP”这件事查得干干净净。
4.4 关于 Service CIDR 的容量预估
顺带说一个 Netfilter 相关的问题。Service CIDR 选择过大也会带来隐患,因为 kube-proxy 的 iptables 模式会为每个 Service 生成大量规则,网段越大不代表性能越好;IPVS 模式下则是靠哈希表维护,规则量与 Service 数量呈线性关系。修改 Service CIDR 时,我建议顺便评估一下当前 Service 的数量和增长速度,避免走到另一个极端——网段规划得空前巨大,但规则数最终拖垮节点性能。
理论上--service-cluster-ip-range支持多个 CIDR 列表(逗号分隔),但多备网段会增加管理复杂度,不建议生产环境这么做。宁可规划一个“够用但不过分大”的网段,比如把 10.96.0.0/12 改成 10.112.0.0/12,这个 /12 大概能提供 104 万个地址,除非集团级超大规模集群,否则完全够了。
5. 常见问题与排查技巧实录
5.1 节点一直 NotReady
改完 Pod CIDR 后,节点长时间处于 NotReady,最常见的原因是 CNI 插件在新节点上没有正常初始化。第一步先查 kubelet 日志:
journalctl -u kubelet -f如果日志里出现 CNI 配置加载失败之类的信息,确认节点的/etc/cni/net.d/目录下有没有配置文件,以及网络插件 DaemonSet 的 Pod 是否正常运行。节点kubeadm reset之后,这些目录往往被清理,需要重新 apply 网络插件 manifest 让 DaemonSet 把配置下发下来。
第二个常见原因是旧节点上的 PodCIDR 与新控制器分配的不一致。可以通过下面的命令确认:
kubectl get node <node-name> -o yaml | grep podCIDR如果这个值和 CNI 插件的 IP 池不一致,则需要把节点上的 PodCIDR 清掉,让控制器在新网段上重新分配。这通常也要通过重置节点和重新 join 实现。
5.2 Pod 能创建,但跨节点网络不通
症状非常典型:Pod 都在 Running 状态,但从 A 节点 ping 不通 B 节点的 Pod IP。这种问题 90% 出在 CNI 数据面和路由表不一致。
查看节点的路由表:
ip route如果发现老网段的路由条目还在,就需要清理。Flannel 场景下可以把/var/lib/flannel/下的 subnet 文件删掉再重启 kube-flannel Pod。Calico 场景下则重点检查 BGP peer 是否正常,Felix 日志里有没有路由同步失败的记录:
kubectl logs -n kube-system <calico-node-pod> -c calico-node这里补充一个我的经验:如果改动很大,建议直接删除 CNI DaemonSet 并重新安装,让所有节点的数据面重建,比在一个个节点上手动清路由靠谱得多。
5.3 Service 重建后访问不通
Service 重建后外部访问不通,先分两条线排查:一是看 ClusterIP 有没有正确分配,二是看 kube-proxy 的转发规则有没有生成。
kubectl get svc -A kubectl get endpoints -A如果 Service 有 ClusterIP 但 Endpoints 列表为空,说明 selector 匹配不到 Pod,这是配置问题,和 CIDR 改动没有直接关系。如果 Endpoints 正常,但端口不通,查节点上的 iptables 规则:
iptables -t nat -L KUBE-SERVICES -n | grep <cluster-ip>没有对应规则的话,大概率是 kube-proxy 没有同步。重启 kube-proxy 的 DaemonSet:
kubectl rollout restart -n kube-system daemonset/kube-proxy这招能解决大多数规则没刷新的问题。
5.4 CoreDNS 解析失效
改完 Service CIDR 后,集群内所有域名解析失败,首先想到的是 kubelet 的--cluster-dns是否还是旧地址。那个地址写入在每个 Pod 的 resolv.conf 里,也可以直接在 Pod 里检查:
kubectl exec -it <pod> -- cat /etc/resolv.conf如果 nameserver 是旧网段地址,说明 kubelet 参数没改或者改了没重启。如果 nameserver 已经是新地址,但解析还是失败,那就看 CoreDNS Pod 本身是否正常:
kubectl logs -n kube-system -l k8s-app=kube-dns有时候 CoreDNS 的 Deployment 被重建了,但是 Service 还没创建,或者创建的 Service selector 变了,只要<service-name>.<namespace>.svc.cluster.local解析不出来,问题基本就定位在这几处。
5.5 问题速查表
在实际运维里,遇到问题先对照自己改了什么配置,能省掉大量盲查时间。下面这张表是我后来整理给自己团队用的,直接写在这里:
| 现象 | 可能原因 | 排查优先级 | 常见处理 |
|---|---|---|---|
| 节点 NotReady | CNI 配置未加载 / PodCIDR 不一致 | 1. kubelet 日志 2. podCIDR 字段 | 重启 kubelet / 重置节点重新 join |
| Pod 创建失败,提示 IP 分配超时 | CNI 池没有同步新网段 | 1. Calico IPPool 2. Flannel Network | 修改 IP 池配置并重启插件 |
| 跨节点 Pod 不通 | 路由表残留旧网段 | 1. ip route 2. CNI 日志 | 清理路由 / 重新安装网络插件 |
| Service 访问超时 | Endpoints 为空或 kube-proxy 规则未更新 | 1. Endpoints 2. iptables -t nat | 修正 selector / 重启 kube-proxy |
| 域名解析失效 | kubelet cluster-dns 还是旧 IP | 1. resolv.conf 2. CoreDNS 日志 | 修改 kubelet 参数并重启 |
| 外部系统访问不了 Service | 对方白名单还在旧 ClusterIP | 1. 备份清单核对该 Service 新 IP | 更新外部白名单或 DNS 记录 |
5.6 关于回滚的补充提示
很多人忽略了“改完以后没问题”和“改完以后运行一周没问题”的区别。CIDR 变更的影响范围可能延迟暴露,因为存量连接池、缓存、DNS TTL 都是慢慢失效的。所以我建议,变更结束后至少保留一周左右的旧配置备份,不要立刻删除旧节点的路由信息或旧网络插件的镜像。万一一周后还有异常,你还有机会回退。
另外,etcd 快照保留的时间也尽量长一点。虽然快照不能直接解决所有回滚场景(因为 etcd 里的数据可能已经包含了新网段的记录),但它仍然是最底线的救命绳。真有紧急情况,至少能恢复到变更前的状态。
6. 几个关键心得:这样操作更稳妥
改动 Pod CIDR 和 Service CIDR 这件事,并不适合把它当成“改一行配置”来做。我踩过不少坑之后,形成了下面几条个人经验,愿意在这里多写几句。
第一,把 CIDR 规划纳入集群创建初期的评审项。很多集群说翻车就翻车,根本原因是初期规划偷懒,直接用了安装工具的默认值。如果你现在还能做主,强烈建议在创建集群时就把 Pod 和 Service 网段都规划得宽裕一些,并和公司整体内网规划对齐。宁可初期多占一点地址空间,也不要等到业务跑起来了再去改。
第二,变更前一定先在小集群演练,而且演练环境要尽量贴合生产。我在单节点集群上验证通过的方案,放到多节点生产环境就崩,多数都是因为网络插件在跨节点通信路径上的行为和单节点完全不同。多花一天时间搭一个三节点测试环境,比在生产上折腾一整夜划算得多。
第三,当作一次“服务重建演习”来管理业务预期。不管是 Pod 网段还是 Service 网段,变更几乎必然导致存量工作负载重启或 Service IP 漂移。提前和业务方对齐“哪类服务可以断多久、哪类服务必须通过外部负载均衡过渡”这些信息,比任何一个技术方案都重要。我在某次变更中能够比较平稳地完成切换,很大程度上是因为前一天已经把应用团队的预期都对齐了。
最后再分享一个小技巧:如果你有多个集群需要做同样的网段调整,一定不要一个集群一个集群手工点,把整个变更流程写成脚本,用 CI 流水线或运维平台来串。脚本里至少要包含“备份、检查、执行、验证、回滚触发”这五个环节,这样任何一个集群出现问题,处理路径都是统一的,不会因为人工操作产生差异。毕竟,网络变更这种东西,最怕的不是出问题,而是出问题时每个人都凭着记忆在猜。