首页 / 资讯中心 / 文章详情

Kubernetes节点池分层与Pod IP耗尽治理实践

Kubernetes节点池分层与Pod IP耗尽治理实践 ★ FEATURED ARTICLE
上个月和一个小团队的架构师聊到扩容他说已经不敢轻易加节点了。不是没钱买机器是再往上加就得给 Pod 找新网段。听起来有点反常识但这个问题在 Kubernetes 集群规模做大的过程中几乎避不开节点池越分越细Pod IP 地址反而先耗尽了。这篇文章就围绕这个真实痛点展开聊聊 Node Pool 分层设计的一些思路以及如何从网络方案和容量规划两个方向解决 Pod IP 不足的问题。适合正在维护中型以上集群、或者准备把多个业务线迁到同一套 Kubernetes 底座上的团队参考无论托管集群还是自建集群思路基本通用。1. Node Pool 分层架构的设计初衷1.1 为什么必须做节点池分层最早部署 Kubernetes 的时候很多团队的做法是一个集群打天下。所有业务、所有中间件、所有定时任务全部混在一批节点上节点规格统一标签简单调度全靠默认策略。这种模式在几十个节点、十几个应用的阶段问题不大但到了上百节点、几十个业务线同时在线的时候痛点就开始扎手了某个吃内存的推荐服务可能把宿主机打满导致同节点的其他 Pod 频繁被驱逐。Node Pool 分层本质上就是把不同运行特征的工作负载放到不同配置和网络策略的节点组里面。简单说节点池是对节点进行逻辑分组组内的节点拥有相同的规格、镜像、磁盘类型、标签和伸缩策略。而分层则是在这个基础上进一步按业务重要性、资源特征或网络位置做隔离。比如在线业务放在高规格 CPU 实例池离线任务放在低成本竞价实例池数据库类应用放在本地 SSD 的高磁盘性能池。我在实际管理中的一个体会分层越早做越好不要等技术债务堆起来再重构。曾经见过某团队在集群规模到 500 节点的时候才想拆节点池结果节点标签、调度规则、网络策略到处是硬编码迁移过程整整拖了两个迭代周期。如果在一开始就预留好节点池的划分维度后面业务接入成本会低很多。1.2 分层的维度选择与常见误区节点池分层最怕的是维度混乱。常见的划分维度有三种按资源规格CPU 密集型、内存型、通用型、按业务优先级核心链路、非核心任务、可容忍弹性抢占、按网络位置内网入口层、应用层、数据层。很多团队会习惯性地按业务团队来分节点池比如 A 团队一个池、B 团队一个池。这种方式看似管理清晰但实际上非常浪费资源。因为不同团队的业务可能具有相同的资源特征拆分后反而导致节点池碎片化每个池的节点数量都不多弹性伸缩的效果大打折扣。更合理的做法是先定义资源规格维度再叠加业务标签。例如把核心在线业务-内存型作为一个池把离线计算-竞价实例作为一个池。调度的时候通过 nodeSelector 或者 nodeAffinity 把工作负载精准绑定到对应池内。池与池之间通过 taint 和 toleration 来隔离防止低优先级的任务不小心调度到高优资源池。1.3 Node Pool 分层与网络设计的联动节点池分层不只是资源和调度层面的变化它对网络设计也有直接影响。尤其是当集群使用 VPC-CNI 模式即每个 Pod 直接分配 VPC 内的 IP 地址时节点池的划分会直接决定一个节点的 Pod 密度上限和 IP 消耗速率。举个例子一个节点池如果配置了较大的 Pod 密度比如单节点 128 个 Pod那这个池子每加入一个节点就会吞噬一大段 VPC 网段。业务高峰期弹性扩容 10 个节点可能瞬间消耗掉 1280 个 Pod IP如果事先没规划好网段很快就发现某个可用区的 Pod IP 段见底了。所以严谨的架构设计顺序应该是先规划网段和 IP 容量再划分节点池最后才配置调度规则。现实中很多团队反着来先把节点池分好等 IP 不够了才回头改 VPC 网络改动成本成倍上升。后面我会专门展开 Pod IP 耗尽的具体表现和解决路径。2. Pod IP 不足的根因剖析与定位方法2.1 IP 耗尽不是 Kubernetes 本身的问题先把一个概念理清楚Pod IP 不足绝大多数情况下不是 Kubernetes 控制面出了问题而是底层的网络地址规划跟不上集群的扩张速度。以最常见的云厂商 VPC-CNI 方案为例Pod 使用的 IP 地址是从 VPC 的子网中动态分配的。每个节点上运行着一个类似 IP 分配器的组件它会先从所在可用区的子网中申请一段 IP然后逐个分配给节点上的 Pod。当某个子网的可用 IP 数量接近耗尽时新节点或者新 Pod 就无法获得地址表现为 Pod 一直处于 ContainerCreating 状态事件里刷着类似 Failed to allocate ip 的错误。自建集群使用 Flannel 或 Calico 的 Overlay 模式时情况类似但位置不同。此时 Pod 的 IP 来自集群初始化时指定的 PodCIDR比如 10.244.0.0/16每个节点会默认分配一个 /24 的子网。集群规模一旦超过 256 个节点或者某些节点上的 Pod 数量超过 254 个地址空间就会明显吃紧。很多人初始化集群的时候随手填了一个 /16 的网段以为 6 万多个 IP 够用了结果业务增长到几百个节点后才发现捉襟见肘。2.2 如何准确判断是哪种 IP 不足排查 IP 不足的问题第一步不是去改配置而是先确认到底是哪一层的地址不够。我列一个简单的对照判断表大家可以结合自己的集群现状来定位。现象特征可能原因确认手段新节点加入集群成功但 Pod 一直 Pending 或 Creating节点所在子网可用 IP 耗尽查看 VPC 子网的可用 IP 计数或查看节点的事件日志集群总节点数未超限但某个节点池内新增节点失败该节点池绑定的子网网段太小对比不同节点池的 CIDR 范围和已分配 IP 数量集群控制面日志有能够分配子网失败的错误集群级 PodCIDR 预留不足查看节点的 spec.podCIDR 是否被留空部分节点上可创建的 Pod 数远低于预期节点级 maxPods 配置或辅助 IP 数量限制检查 kubelet 的 maxPods 参数和 CNI 配置在定位过程中我最常被问到的一个问题是为什么明明 VPC 里还有 IPPod 却拿不到这种情况大概率出在节点级 IP 池申请量上。很多 CNI 组件会做 IP 预热的优化一次性向 VPC 申请一批 IP 缓存到节点上。如果申请时子网余量不足或者申请次数超过了云厂商的流控阈值一样会显示出分配失败。此时看节点上 CNI 组件的日志比看 Pod 事件更直观。2.3 一个典型的耗尽过程还原我举个实际操练过的场景来说明。某团队有一个核心业务节点池池内节点规格统一单节点 maxPods 设置为 64。刚开始这个池子绑定了一个 /24 的子网总共 256 个 IP其中还需要扣除云厂商保留的 4 个地址实际可用约 252 个。假设池内有 4 个节点每个节点预热 60 个 IP那总共就需要 240 个 IP。表面上看还有富余但到了高峰期节点数被扩容到 6 个需求瞬间变成 360 个子网直接打满。此时扩容出来的节点能注册到集群却因为拿不到足够的 IP一个 Pod 都调度不上去。如果监控没有提前覆盖子网 IP 使用率这个问题往往要等业务报警大量 Pod 启动失败才会被发现。所以我的建议是无论使用什么网络方案都必须为节点池关联子网配置独立的 IP 使用率监控至少做到 70% 告警、85% 紧急告警两级阈值。3. 从网络方案入手扩大 Pod 地址空间的关键路径3.1 VPC-CNI 模式下的大网段规划改造面对 Pod IP 不足最直接的办法是扩展地址空间。但怎么扩是有讲究的直接改 VPC 主网段风险极大大多数云厂商不支持和存量资源关联的主网段做无级调整。如果你的集群还有余量优先考虑的方式是为节点池新增独立子网。具体步骤大致是在 VPC 中规划一个新的子网段比如把原来的 /24 扩展为 /22或者新增一个完全没有交集的新子网然后将这个子网绑定到某个节点池。节点池内后续新增的节点都会从新子网中分配 IP。这个过程不需要重建集群但需要注意一点存量节点不会自动切换需要做节点滚动替换才能让存量节点也使用新子网。大网段规划时有一个长期被别人忽略的点可用区维度。VPC 的子网通常是绑定在单个可用区上的如果业务有跨可用区容灾需求必须保证每个可用区都有足够大的子网独立支撑一个节点池。假如你只在可用区 A 规划了一个大网段可用区 B 的节点池还是小网段那单可用区故障演练时Pod 可能因为 IP 不够而无法在可用区 B 重建容灾等于空谈。某团队就吃过这个亏日常流量双可用区分摊看起来每个可用区都健康结果真正做容灾切流的时候才发现备可用区的子网 IP 早就被预热的节点池占满了切过去 20 分钟都起不来业务。3.2 自建集群的 PodCIDR 与双栈方案自建集群的场景下如果你用的是 Flannel 等 Overlay 网络而集群刚创建时 PodCIDR 填得太小一般情况下只能重建集群。这也是我一直强调初始化规划要留足余量的原因。但如果你还没走到那一步有两个方向可以缓解。第一个方向是调整单个节点的 Pod 子网大小。Kubernetes 支持通过 kube-controller-manager 的参数 --node-cidr-mask-size 来控制每个节点分配到的 PodCIDR 大小。默认情况下是 /24即每个节点最多 254 个 Pod去掉网络开销实际更少。如果确认单节点不会跑到 254 个 Pod可以改成 /25 或 /26从而让同一个集群级 PodCIDR 能容纳更多节点。以 10.244.0.0/16 为例如果给每个节点分配 /24最多 256 个节点如果改成 /25就能容纳 512 个节点。这个参数可以在集群运行期间平滑调整但需要滚动重启 controller-manager 和节点上的 kubelet改动前务必在测试环境验证。第二个方向是引入 IPv6 双栈。严格说这不是扩展 IPv4 地址而是在保留 IPv4 通信的同时让新业务走 IPv6 地址。Kubernetes 从 1.23 开始已经将 IPv4/IPv6 双栈提升为正式特性。如果你的业务形态是面向外部用户的公网服务双栈的价值非常大因为公网 IPv4 地址本身就是稀缺资源。但内网集群使用双栈的收益相对有限而且对网络组件Calico、CoreDNS、ingress-nginx的版本有较严格的要求未经验证不建议生上。个人看法双栈适合作为长期演进方向不适合用来救火。救火还是要靠网段规划和 IP 复用率的优化。3.3 提高 IP 复用率降低预热与密度控制除了扩容网段另一个容易被忽略的方向是降低浪费。VPC-CNI 模式下最常见的浪费就是节点预热 IP 过多。很多 CNI 组件为了让 Pod 启动更快会为每个节点额外缓存一部分 IP。如果缓存数设置太大比如单节点实际只有 20 个 Pod却缓存了 60 个 IP那 40 个 IP 就是纯浪费。调整方向有两个一是减小缓存池上限让节点按实际负载动态申请 IP二是缩小单节点 maxPods。后者需要权衡如果业务是短时高并发拉起大量 PodmaxPods 太小会导致 Pod 启动时排队。我一般喜欢把 maxPods 设为一个实际峰值和缓存余量之间的折中值比如峰值 30那就设 40 左右而不是直接给到 64 甚至 128。另外IP 复用率还牵涉到 Pod 生命周期管理。有的业务使用 Deployment 滚动发布每次发布都会创建新 Pod 并销毁旧 Pod如果旧 Pod 的 IP 释放不及时也会造成短暂的空洞。可以通过 CNI 配置主动释放已停止 Pod 的 IP不同云厂商的配置入口不完全一致但总体思路是缩短 IP 从Pod 销毁到可重新分配的时间窗口。4. Node Pool 容量规划与分层限流机制4.1 节点池维度的 IP 预算模型要彻底解决 Pod IP 不足网络配置只是第一层容量规划才是治本的手段。具体做法是为每个节点池建立独立的 IP 预算模型。预算模型包含四个关键数字节点规格、单节点 Pod 密度上限、节点池最大节点数、子网可用 IP 数。举一个我实际用过的计算模型。假设有一个节点池单节点规格为 8C16G单节点 maxPods 设为 40节点池最大节点数为 50。那这个池子的 Pod IP 需求上限就是 40 × 50 2000 个。再考虑到 CNI 预热缓冲按 30% 计算建议该节点池绑定一个不小于 2600 个可用 IP 的子网段换算成 CIDR 就是 /20。如果你只给它绑了一个 /23512 个 IP那节点池规模根本不可能正常跑到 50 个节点而不触发 IP 耗尽。这个预算模型的价值在于它能够把网络容量变成调度边界。当节点池规模逼近预算值时可以提前扩容子网或阻止弹性伸缩继续拉新节点从机制上防止 IP 耗尽。这也比出了问题之后再去报警要舒服得多。预算模型还需要考虑时间维度。有的业务是白天高峰、晚上低峰弹性伸缩会把节点池在 10 个到 40 个之间来回调整。如果子网规划只按峰值配置成本偏高如果按平均值配置高峰就存在 IP 不足的风险。折中的办法是按峰值预留 70%剩余部分允许节点池短暂借用同一个大子网中的共享缓冲池。这个设计要依赖子网之间 IP 管理的灵活性具体操作因为云厂商差异比较大需要各团队对照自己的环境做适配演练。4.2 用节点池隔离实现地址域治理Node Pool 分层在 IP 治理上的另一个价值是把全局 IP 耗尽变成局部 IP 耗尽。如果所有业务共享一个大网段任何一个业务的异常扩容都可能导致全网段 IP 不足影响面极难控制。而通过节点池隔离每个池子绑定独立子网资源的故障半径就被锁定了。我碰到过一个比较典型的场景某团队所有业务共用一个节点池、一个 /20 子网某个数据同步任务因为代码 bug 疯狂扩容直接把节点数从 15 个拉到了 80 个2000 多个 IP 在十分钟内被全部占满。同一集群里还在跑核心接口服务结果核心服务弹性扩容的时候连地址都分不到整个业务线跟着抖动。如果节点池分好层数据同步任务池的异常只会锁死在它自己的子网里核心业务节点池的可用 IP 不受影响。这就是分层限流的核心逻辑隔离是一个安全边界资源配额是一个安全网两者结合才能做到故障影响可控。很多团队总把关注点放在调度层面能不能把 Pod 调度过去却忽略了 IP 地址才是最底层、最容易被占满的资源。4.3 弹性伸缩策略与 IP 预算联动最后聊一下集群自动伸缩和 IP 预算的配合。Kubernetes 的自动伸缩组件Cluster Autoscaler在决定是否扩容节点时通常会检查节点上是否有 Pending 的 Pod。如果节点池的子网 IP 已经耗尽那么节点即使被自动伸缩组件加到集群里也无法正常调度 Pod这次扩容就是无效的。我们后来在实践里加了一层逻辑使用自定义监控指标来影响自动伸缩决策。当节点池关联子网的 IP 使用率超过 80% 时自动伸缩的最大节点数被临时调低或者触发一条变更流程提醒运维人为介入。这样做的目的不是让集群失去弹性而是在无 IP 可用和超额扩容之间加了一个保护闸。没有这层保护自动伸缩可能会反复做无用功甚至导致节点数量虚高、账单飙升但业务毫无感知。5. 实操过程一次完整的 Node Pool 分层与 IP 扩容改造5.1 用一张表梳理改造前的现状为了让整个改造过程更具参考性我以某模拟项目 X 的集群为例梳理一下改造前的现状。模拟项目 X 是一个典型的中型在线业务集群规模不算大但已经出现 IP 不足的早期征兆。维度改造前现状风险点节点池数量2 个通用池、GPU 池业务混部严重在线离线互相影响子网分配每个池各绑定 1 个 /24单池最大支持约 250 个 IP明显偏小单节点 Pod 上限默认 110Pod 密度过高IP 消耗速度太快自动伸缩全集群共享 max 节点数无池级隔离异动影响全局IP 使用率告警无无法提前感知耗尽风险这里我不展开具体是哪个云厂商的配置入口因为不同平台控制台的位置和术语有差异但底层的网络经典概念是通用的。无论控制台怎么变核心操作都围绕三个动作规划段、绑子网、调限额。5.2 动手改造节点池重新分层与子网重配第一步重新设计节点池分层结构。我把模拟项目 X 原有的两个池拆分为四个池核心在线池通用型 CPU 节点maxPods 40绑定业务核心接口服务数据组件池内存型节点maxPods 20绑定缓存和数据库代理异步任务池通用型 CPU 节点maxPods 60绑定消息消费和任务处理离线计算池竞价型节点maxPods 80绑定非实时的批处理任务第二步为每个池规划独立子网。为了安全性我按照前面介绍的预算模型反推每个池的 CIDR 大小。以核心在线池为例最大节点数计划为 30maxPods 为 40需求 IP 为 1200再加上预热缓冲 30%约 1560 个向上取整到 /212048 个可用 IP。数据组件池最大节点数 10maxPods 20预留 /24 足够。异步任务池两个参数都比较高给了 /20。离线计算池因为是竞价实例节点池会被频繁回收重建给了 /20同时把预热调低以减少浪费。第三步按照新规划创建子网并绑定到对应节点池。这个过程在控制台上的操作不复杂但有一个注意点新子网的网段不能与现有子网冲突而且如果集群是跨可用区部署每个可用区都要有对应的子网不能只建一个。创建完成后把节点池的扩容模板更新新加节点就会自动使用新子网。5.3 存量节点迁移与业务验证子网绑定完成后并不会影响存量节点的运行。要让存量节点也进入新网络规划需要逐批滚动替换节点。这里有一个比较容易踩的坑直接删除节点池里的存量节点会导致其上 Pod 被驱逐如果业务没有多副本容错会有一阵抖动。所以我在实施时选择的是优先驱逐、分批替换策略每一批先通过节点封锁和排水把 Pod 安全挪走再删除节点并扩容新节点。在整个替换过程中要同步观察几个关键指标节点注册时间、Pod 启动成功率、子网 IP 使用率。我实测下来整个过程大概耗时两到三个小时30 个节点左右的池子如果节点池规模更大时间会相应拉长。不要图快把批次放得太大每一次驱逐和重建都会对控制面和业务产生瞬时压力。完成替换后最后一步是调整自动伸缩策略。把原来的全集群共享阈值改成每个节点池独立配置最小和最大节点数同时把子网 IP 使用率监控接入告警体系。我在模拟项目 X 中设置的告警规则是使用率超过 70% 时进入观察超过 85% 时触发运维事件并暂停所在节点池的自动扩容。经过这轮改造后该集群的 Pod 启动失败率明显下降扩容响应时间也恢复正常。6. 常见问题与排查技巧实录6.1 节点池子网有剩余 IP但 Pod 仍然创建失败这是一个非常典型的理论上有、实际用不了的问题。如果确认子网可用 IP 量还很多但节点上的 Pod 依然报地址分配失败优先检查 CNI 组件所在节点的 IP 缓存状态。很多 CNI 组件在初始化时会在节点上建立一个固定大小的 IP 池如果这个池子的地址已经被耗尽即使 VPC 子网还有余量Pod 也不会获得新 IP。尝试绕过的办法有两个一是重启该节点上的 CNI 组件进程触发一次重新申请二是检查是否设置了节点级的 IP 总数限制某些云厂商的 VPC 对单个网卡可分配的辅助 IP 数量有硬限制比如单网卡最多 40 个辅助 IP那么单节点能承载的 Pod 数天然就被限制在 40 左右调高 maxPods 也没用。想要突破这个限制需要额外配置多网卡或更换 ENI 多 IP 的模式这是一笔不小的架构改造。6.2 集群规模不大但 PodCIDR 提前耗尽我遇到过有人的集群只有 200 个节点却提示 Pod 网段耗尽。检查下来发现问题出在 controller-manager 给节点分配 PodCIDR 时不够均匀。某些特殊节点比如 GPU 节点被反复重装每一次重装都会申请一个新的 PodCIDR 段旧段又没有及时回收导致地址空间碎片化严重。对于这种情况首先要确认节点的 PodCIDR 是否被正确释放。正常删除节点后controller-manager 应该回收对应的 PodCIDR但某些版本存在回收不及时的 bug。其次可以结合节点状态和事件粗略评估当前集群实际用掉了多少 PodCIDR如果只是碎片化导致看似不够可以尝试把异常节点重新创建释放旧的 CIDR 段从而腾出空间。6.3 扩容时新增节点报错但节点池状态正常这种问题通常不在 IP 规划层面而在节点池底层关联的资源上。比如节点池绑定的实例套餐在某个可用区已经无货或者云厂商对单个 VPC 的弹性网卡数量有配额限制。当节点池自动扩容时节点创建请求失败控制台可能只显示一句创建资源失败如果不仔细看配额信息很容易误判成是网络问题。排查这类问题的技巧是不要只看集群一层的日志要看基础设施层的配额和可用状态。把云厂商的配额监控加到日常巡检清单里尤其是弹性网卡数、安全组规则数、VPC 路由表条目数这些都在 Kubernetes 控制面的视野之外但任何一个配额到达上限都会表现为节点上不去、Pod 起不来。6.4 临时救急不改网段也能缓解 IP 压力如果线上已经告警但又暂时没法完成大规模网段改造有几个临时缓解手段可以争取时间。第一个是把节点预热 IP 的缓存值降到最小哪怕牺牲一点 Pod 启动速度也能释放不少地址。第二个是排查集群中是否存在长期不释放 IP 的僵尸容器清理异常的 Pod 或重建相关 Deployment。第三个是临时降低某些非核心节点池的 maxPods把分配到这些池子的 IP 腾挪出来给核心业务用。需要强调的是以上都只是止血措施不要当成长期方案。真正的解法还是回到网络规划上把网段空间、节点池密度、集群规模三者绑定在一起来设计。6.5 关于跨节点池的 Pod 互通规则最后补充一个跨节点池通信的注意点。当节点池分层后不同池的子网可能落在不同的网段甚至分布在不同的可用区。默认情况下只要在同一个 VPC 内Pod 之间的互通是靠路由表或 Overlay 网络自动完成的不需要额外配置。但如果你的网络方案是 VPC-CNI并且用到了安全组一定要注意安全组不仅是绑定在节点上的某些 CNI 模式还会把安全组自动关联到每个 Pod 的辅助网卡上。此时不同节点池如果绑定不同的安全组Pod 之间可能出现平时通、偶尔断的诡异现象。排查时优先对比两边 Pod 实际生效的安全组规则而不是先怀疑 Kubernetes 网络策略。7. 从这次改造中沉淀的几件事整个改造做下来我个人最大的感受是Kubernetes 的节点池分层和 Pod IP 规划本质上是一件事而不是两个独立的问题。网络地址空间决定了节点池能做到多大节点池的划分方式又反向影响地址空间的消耗效率。任何一个环节单独优化最终都会被另一个环节拉回来。7.1 给节点池配置的 IP 预留一定要有冗余意识有些团队的运维同学在设置 CIDR 时非常精打细算觉得 /24 够用就绝不给 /23理由是地址空间浪费。这种想法在成本敏感的场景可以理解但放在 Kubernetes 节点池上却很容易翻车。因为节点池的 IP 消耗并不完全等同于业务实际的 Pod 数CNI 预热、节点滚动替换、跨可用区容灾都会产生额外的地址开销。预留 30% 以上的冗余不是一个奢侈的选择而是一个稳健的基线。7.2 监控和告警的覆盖范围要延伸到网络层在我接触过的团队里把 CPU、内存、磁盘、网络流量监控做得滴水不漏的不在少数但会去监控子网可用 IP 数的却不多。然而 IP 耗尽恰恰是那种平时不会坏、一坏就是大事的故障类型。建议把节点池关联子网的 IP 使用率纳入告警体系设置两级阈值并关联到对应节点池的负责人。这不是很复杂的工程却能帮你提前几周发现问题。7.3 最后一个小技巧谈不上多高级但挺实用做大规模节点池滚动替换时建议把节点排水和节点池扩容操作脚本化并且加入可控的间隔时间。我见过很多手速快的同学一口气把所有节点全部驱逐结果一批 Pod 同时重建把后端数据库连接数打满。给每一批节点之间留出 3 到 5 分钟的稳定期让业务 Pod 就绪后再操作下一批整个过程会更平滑。这个经验在模拟项目 X 的改造中实测非常管用后续几次集群扩容也都沿用了这个节奏。
阅读完成 · 觉得有帮助?
咨询建站