Kubernetes 1.35.10 修了 kube-scheduler 的 namespaceSelector 负算子:被排除命名空间里的 Pod 不再参与过滤打分
变更内容
Kubernetes CHANGELOG-1.35.md 的 v1.35.10 段里有一条调度器修复:
Fixed kube-scheduler evaluation of pod affinity and anti-affinity namespace selectors using negative operators, so pods in excluded namespaces no longer affect node filtering or scoring. (#142050, @rahulbabu95) [SIG Scheduling and Testing]
也就是说:pod affinity / anti-affinity 的 namespaceSelector 在使用 NotIn、DoesNotExist 这类负算子时,本该被排除的命名空间里的 Pod 仍然参与了节点过滤与打分。1.35.10 修复后,这些 Pod 不再影响调度结果。
这是一次行为变更,不只是崩溃修复——升级后同一份配置可能落到不同节点上。
怎么判断自己踩了这个 bug
三个可观察的信号:
- 写了
namespaceSelector想排除某些命名空间,反亲和看起来没生效。 典型情形是用requiredDuringSchedulingIgnoredDuringExecution+topologyKey: kubernetes.io/hostname,期望某个命名空间里的 Pod 不和自己抢同一台机器,结果仍然被判定冲突,Pod 一直FailedScheduling。 - 节点打分与预期不符。 用软反亲和(
preferredDuringSchedulingIgnoredDuringExecution)时不会报调度失败,只是落点偏离预期:某些节点被系统性压低分,查下来那些节点上跑的都是"应该被排除的命名空间"里的 Pod。 - 同一份配置在 1.35.10 前后调度结果不同。 升级后 Pod 落点发生变化,而 manifest 一个字没改,基本可以指向这条修复。
注意失效范围只涉及负算子。In、Exists 这类正向匹配的 namespaceSelector 不受影响,看到反亲和"正常"不代表配置写法没问题,只代表没用到负算子路径。
一份会失效的配置
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
namespace: prod
spec:
replicas: 3
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: web
topologyKey: kubernetes.io/hostname
namespaceSelector:
matchExpressions:
- key: env
operator: NotIn
values:
- sandbox
意图是:app=web 的 Pod 不与自己同节点,但 env=sandbox 的命名空间里那些 app=web 的 Pod 不算数。
失效原因在于调度器求值 namespaceSelector 的负算子时没有正确把命名空间排除掉:sandbox 命名空间里的 app=web Pod 依然进入亲和/反亲和的计算集合,被当成需要避让的对象。于是 prod 的三个副本被错误地判定为互相冲突,节点过滤阶段就失败,或者软反亲和场景下把分数带偏。
同一个字段在 1.35.10 之后开始真正生效,配置本身不需要改。
核对实际生效的命名空间集合
namespaceSelector 匹配的是 Namespace 对象的 label,不是 Pod 的 label,也不是 Pod 所在的业务标签。所以第一件事是把命名空间标签打出来:
kubectl get ns --show-labels
再确认选择器视角下的集合与预期一致:
# 等价于 NotIn:[env] 里不含 sandbox
kubectl get ns -l 'env notin (sandbox)' --show-labels
# 等价于 DoesNotExist:完全没有 env 这个 label
kubectl get ns -l '!env' --show-labels
# 单独确认被排除的命名空间确实带了 label
kubectl get ns sandbox --show-labels
最容易出问题的是 DoesNotExist。它判断的是"命名空间对象上这个 label 键缺失",不是"命名空间不存在"。两者语义完全不同:
- 命名空间存在但没有该 label →
DoesNotExist匹配,会被纳入(或排除,取决于你把它写在哪个字段)。 - 命名空间根本不存在 → 它不会出现在任何匹配结果里,
DoesNotExist也不会因为"不存在"而命中它。
所以如果排除逻辑写的是 DoesNotExist,而目标命名空间恰好有很多没打 label 的,匹配集合会比想象的大得多;反过来,如果以为"没有 label 的命名空间会被排除",实际是"没有 label 的都会被选中"。这类写法在 1.35.10 前后表现不一致,很容易被误判成 bug 复发。
升级后要观察落点
修复改变的是调度决策,不是对象状态,滚动升级不会自动把已有 Pod 重新调度。需要在升级后关注:
kubectl get pod -o wide -n prod
kubectl describe pod
-n prod # 看 Events 里的 FailedScheduling 与调度结果
- 原本因"假冲突"卡在
Pending的 Pod,升级后可能立刻能调度成功,副本数补齐,这是修复生效的正常表现。 - 原本能调度但落点集中的,重启或扩缩容后会出现新的分布。
- 如果有依赖"错误避让"形成的拓扑分布(比如靠 bug 意外凑出的打散效果),升级后分布会变,需要在发布窗口里观察而不是等告警。
已运行的 Pod 不会因为升级调度器而迁移,落点变化只发生在新建、重建、扩缩容和驱逐之后。
同段其它条目(背景)
- kube-apiserver 现在接受最多 16384 个 request header value,避免 Go 1.27 的 500 默认值拒绝带 500 个以上 group 的 impersonated/proxied 用户并返回 431。(#142772)
- 修复 Pod
.status.resourceClaimStatuses在多 claim 场景下在部分列表之间抖动。(#142023) - 修复 DRA
PrepareResources部分失败重试时给 CRI 传重复 CDI device ID 导致容器启动失败。(#140954) - 1.35 已移除 cgroup v1 支持(
failCgroupV1默认 true);kube-proxy 的 ipvs 模式弃用;containerd 1.x 是最后一个受支持版本。 - Kubernetes 1.35 使用 Go 1.26 构建。