VPC-ENI指定子网分配IP(容器网络 v2)
PodSubnetTopologySpread 使用说明
本文档介绍如何在 cce-network-v2 中使用 PodSubnetTopologySpread(简称 PSTS)为 Pod 指定可分配 IP 的子网、地址范围,并控制命中 Pod 在可用区之间的调度分布。
功能简介
PSTS 是一个 Namespaced 资源,主要解决两类问题:
- 指定同一批 Pod 只能从哪些子网、哪些地址范围中分配 IP。
- 指定同一批 Pod 在可用区之间的调度分布约束。
当前代码实际支持的 IP 分配策略只有两种:
Elastic:弹性分配,Pod 删除后 IP 立即回收。Fixed:固定分配,Pod 删除后 IP 保留;可选ttl控制超时回收。
使用场景
PSTS 适用于以下典型场景:
- 希望工作负载只能从指定子网中分配地址。
- 希望工作负载只能使用指定的 IP 范围或 CIDR 地址池。
- 需要为 StatefulSet 等有状态工作负载分配稳定 IP。
- 希望命中同一条 PSTS 的 Pod 在多个可用区之间尽量均匀分布。
生效机制
PSTS 的生效链路如下:
- PSTS 只影响同一
namespace内的 Pod。 - Pod 创建时按
spec.selector匹配 PSTS。 - 如果多个 PSTS 同时命中,代码会按
priority从小到大排序,最终选priority最大的那一个。 - 如果多个 PSTS 的
priority相同,当前实现没有稳定 tie-break 规则,结果不应依赖。 - webhook 会根据 PSTS 可用子网所在的可用区,为 Pod 注入
nodeAffinity。 - 当
maxSkew > 0时,还会为 Pod 注入topologySpreadConstraints。 - Pod 创建成功后,会在 Pod 注解中记录命中的 PSTS:
cce.baidubce.com/PodSubnetTopologySpread: <psts-name>
使用前提
- 集群使用容器网络
v2。 - 集群已经具备 ENI 跨子网分配 IP 能力。
- PSTS 中声明的子网与集群属于同一个 VPC。
- 子网所在可用区中存在可调度节点,否则 Pod 会因为可用区亲和性无法调度。
使用限制
选择器限制
selector可以不填;不填表示该namespace下所有 Pod 都可能命中。selector: {}是非法配置;如果填写,必须至少包含一个matchLabels或matchExpressions条件。matchExpressions当前代码已真实支持,不只是 CRD 声明。
策略限制
strategy.type默认值是Elastic。- 当前只支持
Elastic和Fixed。 Elastic模式下不能配置strategy.ttl。Fixed模式下每个子网都必须显式配置range或cidrs,不能只写空数组[]。strategy.releaseStrategy、strategy.enableReuseIPAddress已废弃;即使填写,也会被 webhook 清空,不再生效。
子网与地址池限制
- 同一子网下,
range和cidrs可以同时存在。 - 同一子网下,所有
range/cidrs之间不能重叠。 - 双栈集群要求 PSTS 使用的子网本身也是双栈子网。
- 使用 IPv6
cidrs时,前缀长度必须大于等于/96。 Elastic模式下如果同一个 PSTS 同时配置了“有范围子网”和“空数组子网”,分配器会优先从有范围子网分配;范围耗尽后,再回退到空数组子网随机分配。- 实际使用中优先推荐
cidrs;range主要用于少量、连续地址段的场景。
变更限制
- 已运行的 PSTS 不能直接在
Elastic和Fixed之间切换。需要确保没有命中 Pod、没有已分配 IP 后再切换。 - 删除整个子网配置时,如果该子网下仍有已分配 IP,更新会被拒绝。
Fixed模式下,删除或缩小range/cidrs时,如果被删除的地址范围里还有 Pod 在使用 IP,更新会被拒绝。Elastic模式下,删除range/cidrs时不会检查在用 IP,但删除整个子网仍会检查。
关键配置项说明
顶层字段
| 字段 | 是否必填 | 说明 |
|---|---|---|
apiVersion |
是 | 固定为 cce.baidubce.com/v2 |
kind |
是 | 固定为 PodSubnetTopologySpread |
metadata.name |
是 | PSTS 名称,同一 namespace 内唯一 |
metadata.namespace |
是 | PSTS 所在命名空间,只影响该命名空间内 Pod |
spec |
是 | PSTS 主体配置 |
spec 字段
| 字段 | 是否必填 | 默认值 | 说明 |
|---|---|---|---|
spec.name |
否 | 自动回填为 metadata.name |
webhook 会自动覆盖为对象名,建议不填 |
spec.priority |
否 | 0 |
多个 PSTS 同时命中时,数值越大越优先 |
spec.selector |
否 | 不填表示匹配本 namespace 下所有 Pod |
Pod 标签选择器 |
spec.subnets |
是 | 无 | 子网及可分配地址范围定义,至少要有一个子网 |
spec.strategy |
否 | type: Elastic |
IP 分配/保留策略 |
spec.maxSkew |
否 | 1 |
可用区之间允许的最大倾斜度 |
spec.whenUnsatisfiable |
否 | DoNotSchedule |
倾斜不满足时的调度动作 |
spec.selector
spec.selector 使用标准 Kubernetes LabelSelector。
1selector:
2 matchLabels:
3 app: account
4 matchExpressions:
5 - key: workload
6 operator: In
7 values:
8 - sts
实际行为说明:
- webhook 准入时会校验
matchExpressions是否是合法的 KubernetesLabelSelector。 - Pod 命中 PSTS 时,代码会把整个
spec.selector转成标准 selector,再用 Pod 标签做匹配,因此matchLabels和matchExpressions都会生效。
priority
priority 只在“同一个 Pod 同时匹配多个 PSTS”时生效。
真实选择逻辑:
- 先筛出所有
selector命中的 PSTS。 - 按
priority从小到大排序。 - 取排序后的最后一个,即
priority最大的 PSTS。
建议:
- 同一类业务尽量只让一个 PSTS 命中。
- 如果必须叠加多条规则,务必把
priority拉开。 - 不要让两个含义不同的 PSTS 以相同
priority命中同一批 Pod。
spec.strategy
| 字段 | 是否必填 | 默认值 | 说明 |
|---|---|---|---|
spec.strategy.type |
否 | Elastic |
当前仅支持 Elastic、Fixed |
spec.strategy.ttl |
否 | 无 | 仅 Fixed 可用;表示 Pod 删除后固定 IP 保留多久,必须大于 0 |
策略说明:
-
Elastic- Pod 从子网或子网范围中分配 IP。
- Pod 删除后 IP 立即回收。
- 不能配置
ttl。 - 可以只写子网,不写
range/cidrs,表示整个子网随机分配。
-
Fixed- 适合需要稳定 IP 的业务,推荐搭配
StatefulSet。 - 每个子网必须配置
range或cidrs。 ttl不填表示长期保留,不自动超时回收。ttl填写后,表示固定 IP 在 Pod 删除后保留指定时长,超时自动回收。
- 适合需要稳定 IP 的业务,推荐搭配
spec.subnets
spec.subnets 是一个 map,key 是子网 ID,value 是该子网下的自定义分配规则数组。
语义如下:
-
sbn-xxxx: []- 表示该子网不限制具体 IP 范围,直接使用整段子网随机分配。
- 只适用于
Elastic。
sbn-xxxx:- family: "4"range/cidrs: ...表示该子网只允许从指定范围或指定 CIDR 中分配 IP。 适用于Elastic和Fixed。
推荐方式:
- 优先使用
cidrs表达地址池,语义更清晰,也更适合维护多段地址范围。 range仅在需要表达少量连续 IP 段时使用。- 如果同一子网下既能用
cidrs表达,也能用range表达,优先选cidrs。
关键字段:
| 字段 | 是否必填 | 说明 |
|---|---|---|
spec.subnets.<subnetId> |
是 | 子网下的自定义分配规则列表;子网 ID 必须是 sbn-* |
family |
否 | IP 协议族,可选 "4"、"6",默认 "4" |
range |
否 | 连续 IP 范围列表。每个范围项包含 start 和 end 两个字段,例如一个范围项写成 - start: 10.0.1.10、end: 10.0.1.100 |
cidrs |
否 | 可分配 CIDR 列表,例如 cidrs: ["10.0.1.0/28", "10.0.1.32/28"] |
maxSkew / whenUnsatisfiable
这两个字段控制的是“调度到哪个可用区”,不是“从哪个子网分配 IP”。
PSTS 命中 Pod 后,系统会做两件事:
- 根据 PSTS 当前可用子网所在的可用区,给 Pod 注入
nodeAffinity。 -
当
maxSkew > 0时,再给 Pod 注入一条topologySpreadConstraints,其中:topologyKey = topology.kubernetes.io/zonelabelSelector = PSTS.spec.selectormaxSkew = PSTS.spec.maxSkewwhenUnsatisfiable = PSTS.spec.whenUnsatisfiable
实际效果:
-
maxSkew: 0- 不注入
topologySpreadConstraints,只保留可用区亲和性。
- 不注入
-
whenUnsatisfiable: DoNotSchedule- 如果继续调度会超出
maxSkew,调度器拒绝调度。
- 如果继续调度会超出
-
whenUnsatisfiable: ScheduleAnyway- 调度器仍可调度,只是尽量向更均衡的可用区放置。
使用步骤
步骤一:准备子网和工作负载标签
在创建 PSTS 前,先确认:
- 目标子网与集群同 VPC。
- 子网所在可用区有 Ready 节点。
- 工作负载的 Pod 标签已经明确,便于
selector匹配。
步骤二:创建 PSTS
场景一:Elastic + 整个子网随机分配
适用无状态业务。
1apiVersion: cce.baidubce.com/v2
2kind: PodSubnetTopologySpread
3metadata:
4 name: elastic-random-psts
5 namespace: default
6spec:
7 priority: 10
8 selector:
9 matchLabels:
10 app: web
11 subnets:
12 sbn-aaaa1111: []
13 sbn-bbbb2222: []
14 strategy:
15 type: Elastic
16 maxSkew: 1
17 whenUnsatisfiable: DoNotSchedule
场景二:Elastic + 指定 IP 范围或 CIDR
适用希望限制地址池,但仍然在 Pod 删除后立即回收 IP 的业务。下面示例展示同一子网下配置多个 cidrs 的写法。
1apiVersion: cce.baidubce.com/v2
2kind: PodSubnetTopologySpread
3metadata:
4 name: elastic-ranged-psts
5 namespace: default
6spec:
7 selector:
8 matchLabels:
9 app: api
10 subnets:
11 sbn-cccc3333:
12 - family: "4"
13 cidrs:
14 - 10.0.1.0/25
15 - 10.0.1.128/28
16 sbn-dddd4444:
17 - family: "4"
18 cidrs:
19 - 10.0.2.0/27
20 strategy:
21 type: Elastic
22 maxSkew: 2
23 whenUnsatisfiable: ScheduleAnyway
场景三:Fixed + TTL,给 StatefulSet 固定 IP
适用有状态业务。
1apiVersion: cce.baidubce.com/v2
2kind: PodSubnetTopologySpread
3metadata:
4 name: fixed-cidrs-psts
5 namespace: default
6spec:
7 priority: 20
8 selector:
9 matchLabels:
10 app: mysql
11 workload: sts
12 subnets:
13 sbn-eeee5555:
14 - family: "4"
15 cidrs:
16 - 10.0.10.0/26
17 sbn-ffff6666:
18 - family: "4"
19 cidrs:
20 - 10.0.11.0/27
21 strategy:
22 type: Fixed
23 ttl: 168h0m0s
24 maxSkew: 1
25 whenUnsatisfiable: DoNotSchedule
场景四:Fixed 长期保留
如果希望固定 IP 不做超时清理,可以省略 ttl:
1apiVersion: cce.baidubce.com/v2
2kind: PodSubnetTopologySpread
3metadata:
4 name: fixed-permanent-psts
5 namespace: default
6spec:
7 selector:
8 matchLabels:
9 app: redis
10 workload: sts
11 subnets:
12 sbn-gggg7777:
13 - family: "4"
14 cidrs:
15 - 10.0.20.0/27
16 strategy:
17 type: Fixed
步骤三:创建工作负载
Deployment 示例
1apiVersion: apps/v1
2kind: Deployment
3metadata:
4 name: web
5 namespace: default
6spec:
7 replicas: 3
8 selector:
9 matchLabels:
10 app: web
11 template:
12 metadata:
13 labels:
14 app: web
15 spec:
16 containers:
17 - name: nginx
18 image: nginx
19 resources:
20 requests:
21 cce.baidubce.com/ip: "1"
22 limits:
23 cce.baidubce.com/ip: "1"
StatefulSet 示例
1apiVersion: apps/v1
2kind: StatefulSet
3metadata:
4 name: web
5 namespace: default
6spec:
7 serviceName: web
8 replicas: 2
9 selector:
10 matchLabels:
11 app: web
12 workload: sts
13 template:
14 metadata:
15 labels:
16 app: web
17 workload: sts
18 spec:
19 containers:
20 - name: nginx
21 image: nginx
22 resources:
23 requests:
24 cce.baidubce.com/ip: "1"
25 limits:
26 cce.baidubce.com/ip: "1"
生效验证
查看 PSTS 状态
1kubectl -n default get psts
2kubectl -n default get psts full-demo-psts -o yaml
重点关注:
status.availableSubnetsNumstatus.unavailableSubnetsNumstatus.availableSubnetsstatus.unavailableSubnetsstatus.podMatchedCount
查看 Pod 是否命中 PSTS
1kubectl -n default get pod -l app=account -o yaml
重点确认:
metadata.annotations["cce.baidubce.com/PodSubnetTopologySpread"]spec.affinity.nodeAffinityspec.topologySpreadConstraints
查看 Pod IP 是否来自预期子网或范围
1kubectl -n default get pod -l app=account -o wide
2kubectl -n default describe pod <pod-name>
如果使用了 range/cidrs,需要确认 Pod IP 落在配置的地址池内。
推荐配置
无状态业务
推荐:
strategy.type: Elasticsubnets: { sbn-xxx: [] }maxSkew: 1
有状态业务
推荐:
strategy.type: Fixed- 每个子网都配置
range或cidrs - 使用
StatefulSet - 需要自动回收时再配置
ttl
需要精细地址规划
推荐:
- 大池子用
cidrs - 不连续小范围用
range
常见问题
为什么 Pod 没有命中 PSTS?
优先检查:
- Pod 和 PSTS 是否在同一个
namespace。 - Pod 标签是否匹配
selector。 - 是否有其它更高
priority的 PSTS 同时命中。 selector是否误写成了空对象{}。
为什么 Pod 调度失败?
优先检查:
- PSTS 子网所在可用区是否有 Ready 节点。
status.unavailableSubnets是否有报错信息。maxSkew/whenUnsatisfiable是否让调度器拒绝继续倾斜。- 范围池是否耗尽。
为什么 Fixed 模式创建失败?
优先检查:
- 是否给每个子网都配置了
range或cidrs。 ttl是否为正数。- 子网范围之间是否有重叠。
为什么我写了 releaseStrategy 或 enableReuseIPAddress 没效果?
因为这两个字段已经废弃。当前 webhook 会在准入时把它们清空,实际行为只看:
strategy.typestrategy.ttl
为什么修改 PSTS 提示不能删除范围或切换策略?
这是保护逻辑,避免把还在使用中的地址池直接删除。通常需要先迁移或删除命中 Pod,等 IP 释放完成后再修改 PSTS。
评价此篇文章
