一、问题背景
某金融科技公司的 Kubernetes 生产集群原规划 120 个节点,CNI 采用 Calico + IPAM 模式,初始分配了 3 个 /16 的 Pod CIDR 池(共约 19.6 万地址)。随着业务扩容,节点数已接近 200,集群中运行着大量短生命周期的 Job 与 CronJob。某天凌晨开始,发布流水线频繁失败,报错均为「CNI failed to allocate IP: no IP addresses available in pool」。调度器显示 Pod 处于 ContainerCreating,事件日志里反复出现 failed to allocate for range 0: no IP addresses available in range set。
二、故障现象
- 新增 Pod 大量卡在
ContainerCreating,kubectl describe事件显示Failed create pod sandbox: rpc error: code = Unknown desc = failed to setup network for sandbox ... no IP addresses available。 - 已有 Running 的 Pod 网络正常,说明问题仅影响「新分配 IP」的请求。
calicoctl get ippool显示所有池的blockSize仍为默认/26,但calicoctl get block列出已分配的 IP 块数量远超预期,部分节点出现「借用其他节点 block」的现象。calicoctl get ipam show报告「Total IPs: 196608,Allocated: 196607,Unallocated: 1」,几乎耗尽。- 新节点加入集群后,kubelet 日志出现
cni.go:237] error adding container to network: failed to allocate for range 0,节点整体 NotReady。
三、排查过程
第一步:确认 CNI 插件状态。kubectl get pods -n kube-system -l k8s-app=calico-node 显示所有 calico-node Pod 均 Running,calicoctl node status 也正常,排除插件本身 crash。
第二步:检查 IPAM 配置。calicoctl get ippool -o yaml 发现默认池配置了 ipipMode: Always、natOutgoing: true,但关键字段 allowedUses: ["Workload","Tunnel"] 正常。最可疑的是 blockSize: 26(默认 64 地址/块),而集群规模已从 120 节点涨到 200 节点,理论上需要更多 block。
第三步:查看 IP 分配详情。用 calicoctl get block --output wide 发现大量 block 被「借用」——一个节点的 IP 块被其他节点上的 Pod 占用。Calico 的 IPAM 默认会尽量把同一节点的 Pod 分配到同一 block 以减少路由表膨胀,但当池即将耗尽时,会跨节点借用,导致 block 碎片化。
第四步:定位自动扩容上限。Calico 支持 IP 池自动扩容(strictAffinity: false + autoAllocateBlocks: true),但在 IPPool CRD 中发现:
|
|
缺少 maxBlocksPerHost 限制,且 allowedUses 只包含 Workload/Tunnel,没有为「新节点自动分配 block」预留空间。更致命的是,集群升级时曾手动把 blockSize 从默认 26 改成 28(16 地址/块)以节省地址,却忘记同步更新 ippool 的 cidr 范围,导致实际可用地址比规划少 75%。
第五步:确认碎片与借用风暴。当池中仅剩 1 个地址时,任何新 Pod 都必须「从已有 block 里再挤」,但因为 strictAffinity 为 false,分配器会尝试在全集群范围内找可用 block,造成大量「跨节点借用」请求。calico-node 的 felix 组件在处理这些借用请求时出现延迟,进一步放大分配失败率。
四、解决方案
立即止血:
- 紧急扩容 IP 池:新增一个
/16的新池,blockSize设为 26,并设置allowedUses: ["Workload"]。1 2 3 4 5 6 7 8 9 10 11 12 13calicoctl apply -f - <<EOF apiVersion: projectcalico.org/v3 kind: IPPool metadata: name: pool-prod-v2 spec: cidr: 10.245.0.0/16 blockSize: 26 ipipMode: Always natOutgoing: true disabled: false allowedUses: ["Workload"] EOF - 对已分配 IP 的 Pod 不做处理,仅让新 Pod 落在新池。Calico 的分配器会优先使用未禁用的池,因此新 Pod 立即能拿到 IP。
- 对已处于
ContainerCreating的 Pod 执行kubectl delete pod xxx --grace-period=0 --force,让它们重新调度到新池。
根治:
- 把默认池的
blockSize改回 26(16→64 地址),并开启strictAffinity: true减少跨节点借用。 - 设置
maxBlocksPerHost: 8,限制单节点最多借用 8 个 block,避免单点耗尽过多地址。 - 在集群规模规划阶段即预留 2~3 个
/16池,并设置告警:当池利用率 > 70% 时自动扩容或告警。 - 升级 Calico 版本到 v3.26+,利用其「IP 池自动扩容」特性(
autoAllocateBlocks: true+autoCreateBlocks: true)。
五、根因分析
Calico IPAM 的核心设计是「按需分配 block」,默认 blockSize=/26 意味着每 64 个 Pod 地址占用一个路由条目。当节点数从 120 涨到 200,且业务以短生命周期 Job 为主时,Pod 创建/销毁频率极高,block 分配/释放操作频繁。若池大小规划不足(仅 19.6 万地址对应 3000 Pod 左右),加上 blockSize 曾被误改小,实际可用地址进一步缩水,最终在凌晨高频 Job 集中创建时耗尽。
六、预防措施
- 容量规划:节点数 ×(平均 Pod/节点)× 1.5(冗余)作为 IP 池大小,并预留 2~3 个独立池做滚动扩容。
- 监控告警:在 Prometheus 中增加
calico_ipam_blocks_total/calico_ipam_allocations_total指标,当利用率 > 70% 立即告警。 - 配置收敛:禁止随意修改
blockSize,统一使用 26;设置maxBlocksPerHost防止单点借用过多。 - 自动扩容演练:定期在 staging 环境演练「池耗尽 → 新增池 → Pod 自动落新池」的全流程,确保生产环境不会翻车。
七、总结
CNI 的 IP 池耗尽是「规模化集群的隐形杀手」——它不影响已有 Pod,只在「新增 Pod」时才暴露,且往往在凌晨高频任务集中爆发。防御的关键是「规划时多留一倍地址 + 监控利用率 + 限制单节点 block 借用」,把 IPAM 当成「另一种形式的资源配额」来治理。