概述

凌晨两点,手机被告警轰炸。打开看,TKE 集群里 40 多个 Pod 突然全部进入 Pending 状态,业务接口大面积超时。kubectl describe pod 一看,调度失败原因清清楚楚写着 Insufficient tke.cloud.tencent.com/eni-ip

这不是什么高深的技术难题。根因是 VPC-CNI 模式下,节点上弹性网卡(ENI)绑定的辅助 IP 用完了。但排查和修复花了 3 个小时——因为没人想到 IP 会耗尽,监控里压根没有这个指标,节点池弹性伸缩也扩不出来(新节点同样绑不上 IP,子网 IP 已被分配光)。

这件事给我最大的教训:TKE 网络选型不是选性能参数,是选故障模式。GlobalRouter 和 VPC-CNI 的差异远不止"性能差 10%“这么简单,它们在 IP 管理、扩缩容行为、监控指标和故障传播路径上完全不同。这篇文章从这次故障出发,拆解 5 个我实际踩过的生产决策,帮你在选型阶段就避开这些坑。

如果你正在用或准备上腾讯云 TKE(容器服务 Kubernetes 引擎),不管是从自建 K8s 迁移还是新建集群,网络模型选型和节点池治理是绕不过去的第一道关。选错了,后面补救的成本远高于选型时多花半天思考。

TKE 三种网络模型:你选的不是性能,是故障模式

先说清楚三种模型是什么,再说为什么选择本质上是"选故障模式”。

GlobalRouter:地址充裕但多了一层转发

GlobalRouter 是 TKE 基于腾讯云 VPC 全局路由能力实现的容器网络方案。原理不复杂:给每个工作节点分配一个 CIDR 网段(通常是 /24),节点上所有 Pod 从这个网段拿 IP,节点充当路由器,通过 VPC 底层路由策略实现 Pod 间通信。

# 查看节点分配的容器网段
kubectl get node <node-name> -o jsonpath='{.spec.providerID}'
# GlobalRouter 模式下,每个节点有独立的容器 CIDR

它的优势很明显:容器网段和 VPC 网段不重叠,地址空间充裕,一个 /24 就够 254 个 Pod。扩展性强,标准 K8s 功能无缝兼容, Pod 重启或迁移 IP 会变但 Service 层屏蔽了这个问题。

劣势是 Pod 数据包要经过节点上的网桥设备转发,多了一层,延迟比 VPC-CNI 高大约 10%。而且 Pod IP 不是 VPC 内真实 IP,做 VPC 层面的网络策略、流量镜像等操作时不方便。

VPC-CNI:性能好但 IP 受弹性网卡配额约束

VPC-CNI 基于 CNI 规范和 VPC 弹性网卡(ENI)实现。Pod 直接拿到 VPC 子网里的真实 IP,数据包不经过节点网桥,不需要 VxLAN 隧道封装,网络性能、可观测性、限流、隔离都更好。腾讯云官方推荐 VPC-CNI 作为默认网络方案。

但天下没有免费的午餐。VPC-CNI 的 Pod IP 来自节点上弹性网卡绑定的辅助 IP,而弹性网卡能绑多少 IP 取决于 CVM 实例规格。一台 SA2.LARGE8(2 核 8G)最多绑 30 个辅助 IP,一台 SA2.2XLARGE32(8 核 32G)最多绑 60 个。如果子网 IP 不够,就算实例规格支持也白搭。

这意味着 VPC-CNI 模式下,Pod 密度受限于 min(实例规格 ENI IP 配额, 子网剩余 IP 数),而不是节点的 CPU/内存。我见过不少团队按 CPU 申请了 16 核 64G 的大实例,结果 VPC-CNI 只能绑 60 个 IP,Pod 数量卡在 60 上不去,资源利用率不到 40%。

Cilium-Overlay:第三种选择

2026 年 TKE 还推出了 Cilium-Overlay 模式,基于 eBPF 实现的网络方案,兼顾性能和地址充裕性。不过目前生产案例还不多,本文重点讨论 GlobalRouter 和 VPC-CNI 的选型,Cilium-Overlay 作为备选方案在决策 5 中讨论。

为什么说是"选故障模式"

对比维度GlobalRouterVPC-CNI
Pod 密度限制容器网段大小(通常 254/节点)弹性网卡 IP 配额(30-60/节点)
IP 耗尽表现几乎不会发生高频故障,Pod 集体 Pending
扩缩容瓶颈子网 IP(充裕)ENI IP 配额 + 子网 IP(双重约束)
性能损失~10% 转发开销几乎无损失
固定 IP 支持不支持支持
监控关键指标Pod CIDR 使用率ENI IP 分配数 / 可绑定上限
故障传播范围单 Pod 级节点级 → 可扩散到集群级

选 GlobalRouter,你大概率不会遇到 IP 耗尽,但要接受 10% 的性能损失和网桥转发的额外延迟。选 VPC-CNI,性能好了,但 IP 池成了新的故障域——而且这个故障域是隐形的,因为大部分团队不会在监控里加 ENI IP 使用率指标。

这就是"选故障模式"的意思:两种方案都会出问题,只是问题出在不同的地方。选型要做的是选择你能监控、能排查、能接受的故障类型。

决策 1:GlobalRouter vs VPC-CNI,别被"性能提升 10%“带偏

我的踩坑经历

在某出行项目的 TKE 集群选型时,团队看到 VPC-CNI"性能相比 GlobalRouter 约提高 10%",直接选了 VPC-CNI 作为默认网络模式。结果上线两个月后,流量高峰期弹性扩容时,新节点上的 Pod 一直 Pending,告警群炸了。

回头看,问题出在选型时只看了性能差异,没看 IP 管理约束。出行项目用了很多小规格实例(4 核 8G),VPC-CNI 模式下每台只能绑 30 个辅助 IP。容器化后每个微服务 Pod 还要预留 IP,30 个 IP 很快就不够用。

选型决策框架

我现在的选型标准是这样的:

默认选 GlobalRouter,除非有明确的硬性需求必须用 VPC-CNI。原因:

  1. GlobalRouter 地址充裕,不会因为 IP 问题卡扩容。对于大部分业务,10% 的网络性能差异根本不是瓶颈——你的应用延迟瓶颈在数据库查询和外部 API 调用,不在网络转发多了一跳
  2. GlobalRouter 和标准 K8s 功能兼容性最好,社区文档和排障经验都能直接用
  3. VPC-CNI 的 IP 管理增加了运维复杂度,需要额外的监控指标和容量规划

什么时候选 VPC-CNI

  • 需要 Pod 固定 IP(如 IP 白名单、传统中间件对接、日志采集按 IP 区分)
  • 需要获取客户端真实来源 IP(VPC-CNI 直连模式下 CLB 直接转发到 Pod,不经过 NAT)
  • 对网络延迟有极致要求(如交易系统、实时计算)
  • 需要基于 VPC 网络策略做流量控制

什么时候混用

大部分场景的最佳实践是 GlobalRouter 作为默认 + VPC-CNI 按需开启。创建集群时选 GlobalRouter,在集群基本信息页面按需开启 VPC-CNI 支持。只有明确需要固定 IP 或直连负载均衡的工作负载才指定 k8s.v1.cni.cncf.io/networks: tke-vpc-cni annotation 使用 VPC-CNI,其余 Pod 默认走 GlobalRouter。

# 为特定工作负载启用 VPC-CNI(混用模式)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: payment-service
spec:
  template:
    metadata:
      annotations:
        # 指定该 Pod 使用 VPC-CNI 网络模式
        k8s.v1.cni.cncf.io/networks: tke-vpc-cni
    spec:
      containers:
      - name: payment
        image: registry.example.com/payment:v2.1.0

这个决策的核心思路:降低 VPC-CNI 的使用面,从而缩小 IP 耗尽的故障爆炸半径。如果只有 5 个需要固定 IP 的服务用 VPC-CNI,IP 池管理只在这几个服务的节点上做就行。如果全集群都用 VPC-CNI,每个节点都是潜在的 IP 耗尽点。

和 ACK 的对比

如果你也在看阿里云 ACK,对比一下:ACK 的 Terway 网络插件也有类似的 ENI 共享模式和独占模式之分,选型逻辑完全一致。云厂商的 CNI 方案本质上是同一个思路:要么用网桥转发牺牲性能换地址充裕,要么直连 VPC 换性能但受 IP 配额约束。别被各家厂商的营销话术绕晕了,底层逻辑是一样的。(相关文章:K8s 网络模型:CNI 与 Service 网络

决策 2:VPC-CNI 的 IP 池不是无限的,预绑定数量是第一个坑

如果你选了 VPC-CNI(不管是纯 VPC-CNI 还是混用模式),IP 池管理是必须搞清楚的事。

IP 池的工作机制

VPC-CNI 共享网卡模式下,TKE 的 IPAMD 组件在每个节点上维护一个弹性伸缩的 IP 池。关键参数是预绑定数量:

  • 最小预绑定数量(默认 5):当已绑定 IP < Pod 数量 + 5 时,IPAMD 会主动绑定 IP
  • 最大预绑定数量(默认 5):当已绑定 IP > Pod 数量 + 5 时,IPAMD 会定期释放多余 IP(约 2 分钟一次)

也就是说,节点上有 5 个 Pod 时,IPAMD 会保持 10 个 IP 绑定(5 给 Pod 用 + 5 预留)。Pod 创建时从池中随机分配,销毁时回收到池中,不立即释放到 VPC。

踩坑场景:预绑定数量太小导致扩容卡顿

默认 min/max 都是 5,看起来够用。但如果你用的是 HPA 自动扩缩容,流量高峰时一波 Pod 扩容请求打过来,如果预绑定 IP 池刚好用完,新 Pod 要等 IPAMD 绑定新 IP 才能调度成功。绑定弹性网卡 IP 是调用云 API,有秒级延迟,批量绑定时可能要等 10-30 秒。

# 查看节点 ENI IP 使用情况
kubectl get node <node-name> -o jsonpath='{.status.allocatable.tke\.cloud\.tencent\.com/eni-ip}'
# 输出:30  ← 可分配的 ENI IP 数量

kubectl describe node <node-name> | grep eni-ip
# 输出类似:
# tke.cloud.tencent.com/eni-ip    30          28          2           2
#                                  capacity   allocated   remaining   request
# 上面表示:总容量 30,已分配 28,剩余 2

调优建议

根据业务特征调整预绑定数量:

业务特征建议配置理由
流量平稳,Pod 变动少min=5, max=5(默认)默认值够用,避免浪费 IP
流量波动大,频繁扩缩容min=10, max=15预留缓冲,避免扩容时等 IP 绑定
固定 IP 模式按需分配,无预绑定IP 完全按需分配,不预绑定
子网 IP 紧张min=2, max=5减少预绑定,但牺牲扩容速度

修改方式:编辑 IPAMD 配置

# 修改 tke-eni-ipamd 的配置
kubectl edit deploy tke-eni-ipamd -n kube-system
# 在 args 中添加/修改:
#   - --min-ip=10
#   - --max-ip=15

注意:修改预绑定数量只影响增量行为。已绑定的 IP 不会立即释放,IPAMD 会在下次定期检查时(约 2 分钟)调整到新范围。

必须加的监控指标

VPC-CNI 模式下,这是我建议必须加的 Prometheus 指标:

# PromQL 查询节点 ENI IP 使用率
# 接近 80% 时应该告警,接近 100% 时 Pod 会 Pending
- alert: TKEENIIPExhaustion
  expr: |
    1 - (
      kube_node_status_allocatable{resource="tke_cloud_tencent_com_eni-ip"} 
      / 
      kube_node_status_capacity{resource="tke_cloud_tencent_com_eni-ip"}
    ) > 0.8    
  for: 5m
  labels:
    severity: warning
  annotations:
    summary: "节点 {{ $labels.node }} ENI IP 使用率超过 80%"
    description: "ENI IP 剩余不足,Pod 可能很快进入 Pending 状态"

这个告警能在 IP 耗尽前 10-30 分钟发出预警,给你足够时间扩容或释放 IP。别等到 Pod Pending 了才发现问题。(相关文章:Kubernetes 自动扩缩容:HPA/VPA/CA 深度解析

决策 3:节点池弹性伸缩的"静默失效”——扩容时才发现资源不够

TKE 的弹性伸缩基于腾讯云 AS(弹性伸缩)和社区 cluster-autoscaler 实现。这个链路有一个隐蔽的故障模式:平时一切正常,等到真正需要扩容时才发现扩不出来。

什么是"静默失效"

节点池配置看似没问题:机型选了、安全组配了、子网选了。但扩容触发时,以下任一原因都会导致失败:

  1. 机型库存不足:可用区的 CVM 实例规格售罄(高峰期常见)
  2. 子网 IP 耗尽:子网剩余 IP 不足以分配给新节点的 ENI
  3. SSH 密钥缺失:关联的 SSH 密钥被删除或权限变更
  4. 安全组变更:关联的安全组被修改导致规则不匹配
  5. 账号余额不足:按量计费实例因余额不足创建失败

这些问题在配置时不会报错,只在扩容触发时才暴露。更隐蔽的是,cluster-autoscaler 的扩容失败日志默认级别较低,不主动查看根本发现不了。

TKE 弹性健康度功能

2026 年 TKE 推出了"弹性健康度"功能,能在扩容前主动检测这些风险。它定义了两个核心指标:

  • 有效库存(Xi):单个资源池能扩容的最大节点数,Xi = min(库存水位, 子网剩余 IP 数)
  • 弹性韧性(N):有效库存大于 0 的资源池数量,反映扩容选项的丰富度

健康度状态分三级:

状态判定条件含义
✅ 健康总有效库存 ≥ MaxSize 且韧性 ≥ 3配置合理,扩容成功率高
⚠️ 提醒库存不足或韧性 < 3过度依赖少数资源池,有单点风险
❌ 风险关键配置检查不通过或总库存 = 0无法扩容,需立即处理

我建议所有使用弹性伸缩的节点池都开启弹性健康度检查,并配置告警通知。这比等故障发生后再排查高效得多。

生产环境配置建议

# 查看节点池弹性伸缩配置
tccli tke DescribeClusterNodePools \
  --ClusterId cls-xxxxxxxx \
  --filters.0.Name node-pool-id \
  --filters.0.Values np-xxxxxxxx

# 关键配置检查项:
# 1. MultiZoneSubnetPolicy:建议设为 EQUALITY,多可用区分散
# 2. 备选机型:至少配置 2-3 种可替代机型
# 3. RetryPolicy:建议 IMMEDIATE_RETRY,避免临时故障导致扩容失败

机型选择策略:至少配置 2-3 种备选机型。我推荐按"性能梯队"配置,而不是全选同一规格:

主力机型:SA5.LARGE8(2 核 8G)——日常承载
备选机型 1:SA5.XLARGE16(4 核 16G)——主力售罄时备选
备选机型 2:S5.LARGE8(2 核 8G)——跨代际备选,库存更充裕

这样即使某个可用区的某个规格售罄,AS 也能尝试其他规格,不至于完全扩不出来。

多可用区策略:节点池配置 2 个以上可用区的子网。TKE 的 MultiZoneSubnetPolicy 支持 PRIORITY(按优先级)和 EQUALITY(均衡分配)两种模式。我推荐 EQUALITY,虽然会略微增加跨可用区延迟,但大幅提高了扩容成功率。

决策 4:混用模式不是银弹——GlobalRouter + VPC-CNI 的边界管理

混用模式(GlobalRouter 作为默认 + VPC-CNI 按需开启)是大部分场景的推荐方案。但混用引入了新的治理问题。

Pod 调度的网络模式归属

混用模式下,Pod 默认走 GlobalRouter。只有显式指定 annotation 的 Pod 才走 VPC-CNI。这意味着:

# 默认走 GlobalRouter(不需要 annotation)
apiVersion: v1
kind: Pod
metadata:
  name: normal-service
spec:
  containers:
  - name: app
    image: nginx:1.25

# 指定走 VPC-CNI
apiVersion: v1
kind: Pod
metadata:
  name: fixed-ip-service
  annotations:
    k8s.v1.cni.cncf.io/networks: tke-vpc-cni
spec:
  containers:
  - name: app
    image: nginx:1.25

混用模式下,VPC-CNI 的固定 IP Pod 和 GlobalRouter 的普通 Pod 网络互通没有问题——都在同一个 VPC 内,路由策略由 VPC 底层处理。但以下差异需要注意:

带宽限速的差异

GlobalRouter 和 VPC-CNI 共享网卡模式都支持社区 bandwidth 插件做 Pod 级带宽限速,但配置方式不同:

# GlobalRouter 模式:修改 tke-bridge-agent
kubectl edit daemonset tke-bridge-agent -n kube-system
# 在 args 中添加 --bandwidth 开启插件

# VPC-CNI 共享网卡模式:修改 eniipamd 组件配置
# 在组件管理页面修改 agent.cniChaining.bandwidth 为 true

然后通过 annotation 指定限速:

metadata:
  annotations:
    kubernetes.io/ingress-bandwidth: "100M"
    kubernetes.io/egress-bandwidth: "50M"

注意:VPC-CNI 独占网卡模式不支持 bandwidth 插件。如果你的工作负载必须用独占网卡(如高性能计算),就不能用 Pod 级带宽限速,只能在 VPC 层面做流控。

Pod 安全组的依赖

VPC-CNI 模式支持 Pod 级别安全组(通过 SecurityGroupPolicy 组件),可以给每个 Pod 配置独立的安全组规则。GlobalRouter 模式不支持 Pod 级安全组,只能用节点安全组。

混用模式下,如果某些 VPC-CNI Pod 需要特定的安全组规则,必须在集群中安装 SecurityGroupPolicy 组件,然后创建 SecurityGroupPolicy 资源:

apiVersion: networking.tke.cloud.tencent.com/v1
kind: SecurityGroupPolicy
metadata:
  name: payment-sg-policy
  namespace: production
spec:
  securityGroups:
  - sg-xxxxxxxx
  podSelector:
    matchLabels:
      app: payment-service

但要注意,SecurityGroupPolicy 目前只对超级节点上的 Pod 生效。普通节点和原生节点上的 VPC-CNI Pod 仍使用节点安全组。这个限制经常被忽略,导致安全组规则配了不生效。

混用模式的治理建议

我建议把混用模式的 VPC-CNI Pod 集中到专用节点池上,而不是和 GlobalRouter Pod 混布在同一个节点池。原因有二:

  1. VPC-CNI Pod 的 IP 消耗和 GlobalRouter Pod 不同,分开管理容量更清晰
  2. 专用节点池可以针对性配置 ENI IP 监控和预绑定参数,不影响 GlobalRouter 节点

具体做法是用 nodeSelector 或 nodeAffinity:

spec:
  nodeSelector:
    network-mode: vpc-cni
  # 节点池打标签:kubectl label node <node> network-mode=vpc-cni

决策 5:节点类型选错比网络选错更致命

TKE 提供三种节点类型:普通节点、原生节点和超级节点。很多人在选型时只关注网络模型,忽略了节点类型对运维的影响。实际上,节点类型决定了升级路径、故障恢复方式和成本模型。

三种节点类型对比

对比维度普通节点原生节点超级节点
操作系统CVM 镜像提供TKE 统一管控无 OS(Serverless)
计费方式CVM 计费(按量/包年/竞价)CVM 计费 + TKE 管控费按 Pod 资源用量计费
弹性速度分钟级(CVM 创建)分钟级秒级(直接调度 Pod)
升级方式手动 drain + 置换TKE 统一管控无需升级(TKE 管理)
CVE 修复手动置换节点TKE 发布补丁后置换重建 Pod 自动修复
网络模式GlobalRouter / VPC-CNIGlobalRouter / VPC-CNI仅 VPC-CNI
最大 Pod 密度受 ENI 配额约束受 ENI 配额约束无节点限制

我的选型策略

普通节点:适合需要完全控制操作系统的场景(如自建监控 agent、特殊内核参数调优)。但运维负担最重,CVE 修复要手动 drain + 置换节点,生产环境基本不推荐。

原生节点:推荐作为主力节点类型。TKE 统一管控操作系统,升级和 CVE 修复有统一流程,不需要自己维护 OS 层。网络模式灵活,GlobalRouter 和 VPC-CNI 都支持。

超级节点:适合弹性扩容场景。秒级 Pod 调度,按用量计费,不需要预购 CVM。但有两个限制:只支持 VPC-CNI 网络模式,不支持 DaemonSet(DaemonSet Pod 不会调度到超级节点上)。如果你的业务依赖节点级 DaemonSet(如日志采集 agent、监控 agent),超级节点上就没有这些 agent,需要改用 Sidecar 或远程采集方案。

CVE 修复的差异

这个差异在安全事件时很关键。以 CVE-2026-31431 为例:

  • 普通节点:等 CVM 公共镜像更新补丁后,手动 drain 旧节点 → 移出集群 → 新建节点(默认使用修复版本镜像)。整个过程可能需要 1-2 小时/节点
  • 原生节点:等 TKE 发布补丁后,同样的置换流程,但由 TKE 管控 OS 版本
  • 超级节点:等 TKE 发布补丁后,直接重建 Pod 即可(删除后由控制器重新调度),修复自动完成

如果你的集群有 50 个普通节点需要打补丁,手动置换的工作量是巨大的。这也是我推荐原生节点或超级节点的原因——把 OS 维护交给 TKE,团队精力放在业务层。

关于 Cilium-Overlay 的补充

前面提到的 Cilium-Overlay 模式,目前主要在原生节点上支持。它用 eBPF 替代传统网桥,兼顾了 GlobalRouter 的地址充裕和 VPC-CNI 的性能优势。如果你的集群版本较新(K8s 1.28+),可以考虑试用。但我目前还没有足够的生产案例支撑做推荐,建议先用 GlobalRouter + VPC-CNI 混用的成熟方案。

总结

回到开头那次凌晨两点的故障。根因是 VPC-CNI 模式下子网 IP 被分配光,导致所有节点都无法绑定新 IP,Pod 集体 Pending。修复过程很痛苦——紧急扩容子网 CIDR、等待 IPAMD 重新绑定、逐个恢复 Pending Pod。

这次故障后的复盘,我做了三件事:

  1. 集群切换为 GlobalRouter + VPC-CNI 混用模式。只有需要固定 IP 的 3 个服务用 VPC-CNI,其余全部走 GlobalRouter。IP 耗尽的故障面从全集群缩小到 3 个服务的专用节点池
  2. 增加 ENI IP 使用率监控。在 Prometheus 里加了 tke.cloud.tencent.com/eni-ip 的 allocatable/capacity 指标,80% 阈值告警
  3. 节点池配置弹性健康度检查和多备选机型。不再只配一种机型,避免库存不足导致扩容失败

5 个生产决策的核心逻辑:

决策核心原则一句话
网络模型选型降低故障面默认 GlobalRouter,按需 VPC-CNI
IP 池调优预留缓冲根据扩容频率调整预绑定数量
弹性伸缩治理事前预防开启弹性健康度,配多机型多可用区
混用模式治理隔离管理VPC-CNI Pod 集中到专用节点池
节点类型选型降低 OS 维护成本优先原生节点,超级节点做弹性

最后说一句:云厂商的托管服务确实省了很多基础运维工作,但"托管"不等于"免维"。TKE 帮你管了 master 节点和控制面,网络、IP 池、节点池、安全组这些还是要你自己管。选型阶段多想一步,生产阶段就少一次凌晨被叫起来。

参考资料与致谢

本文在撰写过程中参考了以下资料,感谢原作者的贡献:

  1. TKE 容器网络概述 — 腾讯云,VPC-CNI/GlobalRouter/Cilium-Overlay 三种网络方案的技术原理和对比
  2. VPC-CNI 模式介绍 — 腾讯云,VPC-CNI 基于 CNI 和 VPC 弹性网卡的容器网络实现细节
  3. 多 Pod 共享网卡模式 — 腾讯云,IP 池管理原理、预绑定数量和弹性伸缩机制
  4. TKE 基于弹性网卡直连 Pod 的网络负载均衡 — 腾讯云,直连方案与 NodePort 转发的性能对比和选型建议
  5. 组建集群选型推荐 — 腾讯云,GlobalRouter 和 VPC-CNI 的官方选型建议
  6. 固定 IP 使用方法 — 腾讯云,VPC-CNI 固定 IP 模式的开启和使用
  7. 弹性健康度 — 腾讯云,节点池弹性伸缩风险评估和主动运维能力
  8. 在 TKE 上对 Pod 进行带宽限速 — 腾讯云,GlobalRouter 和 VPC-CNI 模式下的带宽限速配置
  9. Pod 安全组 — 腾讯云,TKE Pod 级安全组策略的使用和限制
  10. 容器服务 CVE-2026-31431 漏洞修复说明 — 腾讯云,不同节点类型的 CVE 修复方式差异