多主节点集群在滚动升级时,新旧版本的 API Server 会短暂共存。一个请求如果恰好落到了还没认识新资源类型的旧节点上,就会拿到一个 404 Not Found——但这个资源在集群里明明存在。这个看似无害的错误响应,实际上能触发垃圾回收误删、命名空间卡死删除等连锁事故。Kubernetes 1.28 引入的 Mixed Version Proxy(MVP)正是为了堵住这个漏洞,而 1.36 把它送到了 Beta,默认开启。
404 的代价比你想的大
高可用控制平面升级时,至少会有一个阶段同时运行两个版本的 API Server。新版本可能引入了新的 API Group/Version/Resource(比如某个 CRD 升级到了 v2),旧版本的 API Server 对这些资源一无所知。
没有 MVP 时,流程是这样的:
- 客户端请求
v2版本的资源 - 请求被负载均衡分发到旧版 API Server A
- A 不认识
v2,返回404 Not Found - 客户端认为资源不存在,可能触发垃圾回收或阻塞命名空间删除
这不是理论推演——垃圾回收器(GC)和命名空间控制器都依赖 API 可见性来决定资源是否存在。一个错误的 404 就能让它们做出不可逆的操作。
MVP 的代理机制
MVP 的核心思路很简单:旧版 API Server 收到自己无法处理的请求时,不直接返回 404,而是把请求代理到能处理它的新版 peer 上。
Client → API Server A (旧版,不认识 v2)
↓ 查询 Discovery Cache,找到能处理 v2 的 peer
↓ 代理请求到 API Server B(添加 x-kubernetes-peer-proxied 头)
API Server B (新版) → 本地处理 → 返回响应
API Server A → 把响应转发回 Client
客户端完全无感,拿到的是正确的响应而非 404。
从 Alpha 到 Beta:三个关键演进
1. 抛弃 StorageVersion API,转向 Aggregated Discovery
Alpha 版本依赖 StorageVersion API 来判断 peer 能服务哪些资源。这个方案有个硬伤:StorageVersion API 尚不支持 CRD 和 Aggregated API Server,意味着 MVP 对大量自定义资源是盲的。
Beta 版改用 Aggregated Discovery 数据。API Server 现在通过聚合发现信息动态了解 peer 的能力集合,覆盖范围更广,数据也更实时。
2. 补上缺失的一环:Peer-Aggregated Discovery
1.28 的博客明确指出一个缺口:MVP 能代理资源请求,但发现请求(discovery)仍然只返回本地 API Server 自己知道的内容。客户端连到旧节点做 discovery,看到的 API 列表是不完整的。
1.36 加入了 Peer-Aggregated Discovery:API Server 在响应 discovery 请求时,会合并本地视图和所有活跃 peer 的发现数据,按确定性顺序排序后返回统一视图。客户端无论连到哪个节点,都能看到集群的全部 API。
不过,有时你确实只想看当前节点的本地资源。这时可以在请求的 Accept 头里加 profile=nopeer:
# 查看集群完整 API 视图(默认行为,peer-aggregated)
kubectl get --raw /apis | jq '.groups[].name'
# 只看当前 API Server 本地服务的资源(跳过 peer 合并)
kubectl get --raw /apis \
-H 'Accept: application/json;g=apidiscovery.k8s.io;v=v2;as=APIGroupDiscoveryList;profile=nopeer'
注意:peer-aggregated discovery 的生效前提是设置了
--peer-ca-file,否则 API Server 会回退到仅展示本地 API。
3. 默认开启,但需要显式配置安全参数
功能门 UnknownVersionInteroperabilityProxy 在 1.36 默认为 true,但代理本身依赖 TLS 双向认证,必须手动配置以下参数才能实际工作。
实操配置:让 MVP 真正跑起来
以下是一个完整的 kubeadm 集群配置示例,覆盖 MVP 所需的全部参数。
kubeadm ClusterConfiguration
apiVersion: kubeadm.k8s.io/v1beta4
kind: ClusterConfiguration
apiServer:
extraArgs:
# MVP 在 1.36 默认开启,显式声明以便审计
feature-gates: "UnknownVersionInteroperabilityProxy=true"
# [关键] peer 间 TLS 验证的 CA 证书路径,不设则代理失败
peer-ca-file: "/etc/kubernetes/pki/ca.crt"
# 如果控制面节点间走内部网络(如单独的网卡/子网),建议显式指定
peer-advertise-ip: "10.0.0.10"
peer-advertise-port: "6443"
手动管理 API Server 的配置片段
如果你不是用 kubeadm,而是直接管理 kube-apiserver 的启动参数,可以这样写:
# /etc/systemd/system/kube-apiserver.service 中的 ExecStart 补充参数
ExecStart=/usr/local/bin/kube-apiserver \
--feature-gates=UnknownVersionInteroperabilityProxy=true \
--peer-ca-file=/etc/kubernetes/pki/ca.crt \
--peer-advertise-ip=10.0.0.10 \
--peer-advertise-port=6443 \
# ... 其他原有参数
修改后重启:
systemctl daemon-reload
systemctl restart kube-apiserver
验证 MVP 是否生效
升级期间,可以用以下方法确认代理正在工作:
# 在升级进行中(新旧 API Server 共存时),检查代理头
# 如果响应包含 x-kubernetes-peer-proxied,说明请求被代理了
kubectl get --raw /apis/mygroup.example.com/v2/myresources -v=8 2>&1 | grep -i "peer-proxied"
# 检查 discovery 是否合并了 peer 数据
# 对比两个 API Server 的 discovery 输出是否一致
kubectl get --raw /apis -s https://old-master:6443 | jq '.groups | length'
kubectl get --raw /apis -s https://new-master:6443 | jq '.groups | length'
# 两个数字应该相同(peer-aggregated discovery 生效)
升级前的检查清单
MVP 默认开启不等于自动可用——缺少 --peer-ca-file 代理会静默失败,回退到旧行为(返回 404)。在升级到 1.36 之前,逐项确认:
--peer-ca-file已设置:这是硬性要求。CA 证书必须能验证 peer API Server 的 serving 证书。通常用集群根 CA 即可(/etc/kubernetes/pki/ca.crt),但如果你的 API Server serving 证书是由不同 CA 签发的,需要用对应的 CA bundle。--peer-advertise-ip/port按网络拓扑评估:如果多主节点间有专用内部网络,显式设置这两个参数,避免 peer 间流量走外部接口。- 在 staging 环境先跑一轮完整升级:观察是否有请求被正确代理,确认 discovery 输出在升级各阶段都是完整的。
- 向 SIG API Machinery 反馈:Beta 阶段正是收集真实场景问题的窗口。遇到 CRD 或 Aggregated API Server 相关的代理异常,及时在 Slack 或邮件列表报告。
MVP 解决的是一个长期存在但容易被忽视的问题——升级期间的 API 可见性断裂。对任何定期升级多主节点集群的团队来说,这个功能从"可选"变成了"必备",而配置门槛只是一个 CA 文件路径。别让它静默失效。