相关文档
PodTopologySpread介绍
Kubernetes 文档 <<Pod 拓扑分布约束>>
How to evenly distribute pods across a topology without using pod anti-affinity?
Pod Topology Spread Constraints介绍
KEP-895: Pod Topology Spread
#80921 EvenPodsSpread 关于受污染节点的讨论
问题背景
因业务需要,要对服务做多可用区改造,改造后的服务满足:
- zone-a/zone-b两个可用区必须有都有实例.
- 多个实例均匀分布在集群的节点之上,即基于
kubernetes.io/hostname
水平打散。
技术背景
公司集群相关信息如下:
- 集群版本1.20.10 (部分集群1.14)
- 所有业务实例部署在
host.type=common
的机器上。 - ingress部署在
host.type=common
,node-role.kubernetes.io/ingress: "true"
的机器上。 - 生产实例必须开启hpa,hpa最小实例数为2。
- 为了保证节点资源均衡,对业务申请的limit进行打折设置request,实现超卖,然后使用
Scheduler extender
实现节点直接水位平均。
当前部署设置
当前部署开启了节点亲和性,pod反亲和性
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- preference:
matchExpressions:
- key: MEM-CAP
operator: In
values:
- 16GB
- 32GB
- 48GB
- 64GB
- 128GB
- 256GB
- 384GB
- 512GB
- 768GB
weight: 100
- preference:
matchExpressions:
- key: sub.host.type
operator: DoesNotExist
weight: 100
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- podAffinityTerm:
labelSelector:
matchExpressions:
- key: service-name
operator: In
values:
- app1
- key: deploy-type
operator: In
values:
- prod
topologyKey: kubernetes.io/hostname
weight: 10
- podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- app1-prod-aws-d
topologyKey: kubernetes.io/hostname
weight: 20
...
nodeSelector:
host.type: common
在未加topologySpreadConstraints
的情况下,调度器加上了默认拓扑规则,代码片段如下:
var systemDefaultConstraints = []v1.TopologySpreadConstraint{
{
TopologyKey: v1.LabelHostname,
WhenUnsatisfiable: v1.ScheduleAnyway,
MaxSkew: 3,
},
{
TopologyKey: v1.LabelTopologyZone,
WhenUnsatisfiable: v1.ScheduleAnyway,
MaxSkew: 5,
},
}
为了实现目标将topologySpreadConstraints
修改为如下配置:
topologySpreadConstraints:
- labelSelector:
matchLabels:
app: app1-prod-aws-d
maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: ScheduleAnyway
- labelSelector:
matchLabels:
app: app1-prod-aws-d
maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: ScheduleAnyway
期望结果:
- 节点之间最大相差1个实例
- 可用区之间相差1个实例 测试环境少量节点少量实例数量符合预期。
上生产遇到的问题
1. 节点 topology.kubernetes.io/zone
值为空。
节点上线时因为上线脚本的问题,节点标签不全部分节点topology.kubernetes.io/zone
值为空。
导致调度器认为是三个可用区 zone-a
,zone-b
,以及空值的可用区。
发布以后一直不符合期望的比例。
2. 未进行打散,单节点上实例数过多。
生产环境52台机器,两台设置node-role.kubernetes.io/NoSchedule:true
为不可调度。
3台ingress机器因为是8G内存不符合节点亲和性,剩余47台机器。
当业务发布50个实例的时候,部分节点实例数超过三个。未能实现每个节点1个,有3个节点是2个实例。
3. 强制节点差值为1。
将节点均衡性的whenUnsatisfiable
改为 DoNotSchedule
想让其均匀打散。
结果发现调度器中处理节点亲和性时只处理requiredDuringSchedulingIgnoredDuringExecution
.
根据nodeSelector
选择到的节点是50个。
因为没有requiredDuringSchedulingIgnoredDuringExecution
,所有的节点均参与了Skew
的计算,
在调度第48个pod的时候,因为有common上的实例数为0(ingress机器上的实例数)导致无论在已存在的47个节点上增加pod时Skew
的值都会是2,超过1导致pending.
4. 缩容问题
锁容时并不会触发平衡性检查,即总是从最多的那个节点进行锁容。导致的问题是锁容后其比例完全不符合预期。
5. 节点污点问题
1. pod先调度到节点之上。
2. 节点出现问题,节点打污点。
3. 新增pod。
此时调度器会略过污点的node计算,如果增加太多实例然后节点放开调度在whenUnsatisfiable: "DoNotSchedule"
的情况下一样会存现问题。
6. 可用区比例问题
多可用区作为容灾方案,一般不会两边部署同样多的实例(多可用区之间流量费用)。目前我们的形式是主备方案(中间件切换成本)。但topologySpreadConstraints
根本无法实现比例方式,如果maxSkew
值设置的过大,实际上完全丢失了均衡性功能。
综上测试:
topologySpreadConstraints
无法满足我们的需求。
生产代码被回退。
最终方案
# OpenKruise v0.10.0 新特性WorkloadSpread解读
一口气将openkruise 版本从0.9.0 升级到 1.2.0
相关命令
# 统计可用区实例数量
for node in $(kubectl get po -n devops -o wide | grep -v NODE |grep qt-java-test-aws1 | awk '{print $7}'); do kubectl get no $node -o=jsonpath='zone-{.metadata.labels.topology\.kubernetes\.io/zone}'; done | sort | uniq -c
# 查看节点type和可用区
kubectl get node -o=jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.metadata.labels.host\.type} {"\t"} {.metadata.labels.topology\.kubernetes\.io/zone} {"\n"}{end}'