Kubernetes v1.36 深度实战:当容器编排学会「稳中见功夫」——从 User Namespaces 安全隔离到 Mutating Admission Policies 生产落地的完全指南(2026)
Kubernetes v1.36(代号 Haru,日语「春」)于 2026 年 4 月 22 日正式发布。这个版本没有颠覆性的新范式,却把过去三四年埋下的种子一一催开:User Namespaces 历经四年终于 GA,Mutating Admission Policies 让运维告别外部 Webhook,OCI VolumeSource 把镜像仓库变成了「存储层」……本文将从架构原理、核心特性、代码实战、升级迁移四个维度,给你一份生产级完全指南。
目录
- 版本概览:71 项增强背后的「收口」逻辑
- 安全主线一:Pod User Namespaces GA——四年磨一剑的容器隔离革命
- 安全主线二:Mutating Admission Policies GA——告别 Webhook Server 的运维革命
- AI/ML 基础设施:OCI VolumeSource GA 与 DRA 精细调度
- 网络安全收紧:IP/CIDR 校验收紧与 externalIPs 弃用
- 性能优化:SELinux 卷标签加速 GA 与 Kubelet API 鉴权加固
- 破坏性变更:gitRepo 移除、Ingress-NGINX 退役、IPVS 退场
- 生产级升级实战:检查清单、迁移脚本、灰度方案
- 总结与展望:v1.36 的「春意」与生产落地建议
1. 版本概览:71 项增强背后的「收口」逻辑
1.1 数字背后:这次更新到底意味着什么?
Kubernetes v1.36 共带来 71 项增强:18 项毕业至 Stable(GA),26 项进入 Beta,25 项新的 Alpha 功能。
表面上看,这个数字比某些「大版本」少了——但「少」恰恰是这次发布的核心气质。v1.36 的关键词不是「扩张」,而是**「收口」**:一批在 Alpha 阶段跋涉了三四年的老功能,这次终于熬出头。
对运维团队来说,这种版本比新增一堆实验性特性更有价值:
- 你可以少用几个第三方工具
- 你可以少维护几套自研组件
- 平台本身正在替你兜底
1.2 三条主线:安全、AI/ML、平台化
如果把 v1.36 的 71 项增强归类,可以清晰地看到三条主线:
| 主线 | 核心特性 | 受益场景 |
|---|---|---|
| 安全加固 | User Namespaces GA、Mutating Admission Policies GA、ServiceAccount Token 外部签名 GA、Kubelet API 鉴权 GA | 多租户平台、金融政企、合规场景 |
| AI/ML 基础设施 | OCI VolumeSource GA、DRA 可分片设备(Beta)、DRA 设备污点(Beta)、PodResources API for DRA GA | GPU 共享、模型分发、加速器调度 |
| 平台化能力 | Mutating Admission Policies GA、Volume Group Snapshots GA、Pod 级原地伸缩(Beta) | 平台团队、Saa S 厂商、PaaS 构建者 |
1.3 代号「Haru」的温柔注脚
这一版的 Release Lead 由来自日本社区的 Ryota Sawada 担任,代号「Haru」(はる,春天)是一个温柔的注脚。
春天在日本文化里意味着万物复苏、花木抽枝。落在 Kubernetes 身上,则呈现出另一种姿态——没有颠覆性的新范式,也没有惊雷般的架构变革,而是把过去几年埋下的种子一一催开,让多年沉淀的能力终于抽出第一片新叶。
一句话概括 v1.36 的气质:春归万物生,稳中见功夫。
2. 安全主线一:Pod User Namespaces GA——四年磨一剑的容器隔离革命
2.1 它解决了什么问题?
在 Kubernetes 的世界里,有一个绕不开的安全悖论:
容器内的进程往往是 root(UID 0),而宿主机上也有一个 root(UID 0)。如果容器隔离被突破,容器里的 root 就是宿主机上的 root。
这就是所谓的「容器逃逸」攻击的核心威胁模型。过去要做到真正有效的 UID 隔离,你只有两条路:
- 跑 gVisor、Kata Containers 这类第三方运行时——引入额外的复杂度和性能开销
- 接受更弱的隔离保证——祈祷内核漏洞别被利用
Pod User Namespaces 给出了第三条路:Kubernetes 原生的 UID 命名空间隔离,从 Linux 内核层面把容器内的 UID 映射到宿主机上的「非特权 UID」。
2.2 四年磨一剑:从 v1.25 到 v1.36
这个特性从 Kubernetes v1.25(2022 年 8 月)进入 Alpha,到 v1.36(2026 年 4 月)毕业 GA,整整经历了四年、跨越了 11 个 Kubernetes 版本。
为什么这么久?因为 User Namespaces 不是 Kubernetes 单方面的功能——它需要:
- Linux 内核支持 User Namespaces(内核 ≥ 3.8,但实际可用要 ≥ 5.12)
- 容器运行时(containerd、CRI-O)配合支持
- kubelet 在 Pod 生命周期管理里正确处理 UID 映射
- 文件系统权限、挂载点、/proc 视图的一致性处理
社区在这四年里反复打磨这套协作链路,才把它送到 GA 这个位置。
2.3 核心原理:UID 映射是怎么工作的?
Linux User Namespaces 的核心能力是** UID/GID 映射**:
容器内部看到的 UID → 宿主机上实际的 UID
──────────────────────────────────────────
UID 0 (root) → UID 65534 (nobody)
UID 1 (daemon) → UID 65634
UID 1000 (app) → UID 66633
在 User Namespace 里,进程认为自己是以 root(UID 0)在运行,可以做各种 root 才能做的操作(比如 chown、mount 某些文件系统)。但这些操作的效果被限制在 User Namespace 内部,在宿主机上,这个进程只是一个普通的无特权用户。
即便攻击者突破了容器隔离(比如利用了一个内核漏洞),他在宿主机上也几乎没有权限做有害操作。
2.4 启用方式:一行配置搞定
v1.36 中启用 Pod User Namespaces 的方式极其简洁:
apiVersion: v1
kind: Pod
metadata:
name: secure-app
spec:
# 核心配置:hostUsers: false 表示 Pod 使用独立的 User Namespace
hostUsers: false
containers:
- name: app
image: nginx:1.27
securityContext:
# 容器内仍然是 root,但映射到宿主机上是非特权用户
runAsUser: 0
runAsGroup: 0
- name: sidecar
image: busybox:1.37
command: ["sleep", "3600"]
securityContext:
runAsUser: 0
runAsGroup: 0
关键理解:hostUsers: false 的含义是「Pod 拥有独立的 User Namespace,不共享宿主机的 User Namespace」。容器内的进程可以是 root,但在宿主机上,这些进程的 UID 是被映射过的非特权 UID。
2.5 内核要求与运行时要求
要使用这个特性,你的环境需要满足:
| 组件 | 最低要求 | 推荐版本 |
|---|---|---|
| Linux 内核 | ≥ 5.12(完整 User Namespaces 支持) | ≥ 6.1(长期支持版) |
| containerd | ≥ 1.7 | ≥ 2.0 |
| CRI-O | ≥ 1.28 | ≥ 1.30 |
| Kubernetes | ≥ v1.36 | v1.36 |
| 节点配置 | user_namespaces 内核参数开启 | 默认开启(现代内核) |
检查你的节点是否支持:
# 在节点上执行
cat /proc/sys/user/max_user_namespaces
# 输出大于 0 则表示支持
# 检查容器运行时是否支持
kubectl get nodes -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.nodeInfo.containerRuntimeVersion}{"\n"}{end}'
2.6 深度实战:多租户平台的安全加固
假设你运营一个多租户 Kubernetes 平台,租户 A 和租户 B 的工作负载运行在同一批节点上。在没有 User Namespaces 的情况下,如果租户 A 的 Pod 逃逸成功,攻击者就是宿主机上的 root,可以访问租户 B 的数据。
有了 User Namespaces,即使逃逸成功,攻击者在宿主机上也只是 nobody,什么都做不了。
完整实战配置:
# 多租户 Namespace 配置
apiVersion: v1
kind: Namespace
metadata:
name: tenant-a
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/warn: restricted
---
# 租户 A 的工作负载(启用 User Namespaces)
apiVersion: apps/v1
kind: Deployment
metadata:
name: tenant-a-app
namespace: tenant-a
spec:
replicas: 3
selector:
matchLabels:
app: tenant-a-app
template:
metadata:
labels:
app: tenant-a-app
spec:
# 关键:整个 Pod 启用 User Namespace 隔离
hostUsers: false
# 配合 Pod Security Standards 的 restricted 策略
securityContext:
runAsNonRoot: false # 容器内可以是 root(会被映射)
seccompProfile:
type: RuntimeDefault
appArmorProfile:
type: RuntimeDefault
containers:
- name: app
image: tenant-a/app:latest
ports:
- containerPort: 8080
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
securityContext:
# 容器内以 root 运行(方便使用 80/443 等低端口)
runAsUser: 0
runAsGroup: 0
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICE # 只允许绑定低端口这一个能力
2.7 局限性与注意事项
User Namespaces 虽好,但不是银弹:
- 特权容器不兼容:如果容器需要
privileged: true,User Namespaces 无法启用(两者语义冲突) - HostPath 挂载需谨慎:HostPath 卷的文件权限是基于宿主机 UID 的,在 User Namespace 里可能遇到权限问题
- 某些 CNI 插件可能不兼容:特别是那些依赖宿主机网络命名空间特殊权限的 CNI
- FSGroup 处理:如果 Pod 设置了
fsGroup,某些文件系统的 GID 映射需要额外测试
最佳实践:
# 推荐:配合 readOnlyRootFilesystem 使用
securityContext:
readOnlyRootFilesystem: true
runAsUser: 0
runAsGroup: 0
3. 安全主线二:Mutating Admission Policies GA——告别 Webhook Server 的运维革命
3.1 写过 Mutating Webhook 的都懂这种痛
如果你维护过 Kubernetes 平台,你一定写过(或者维护过)Mutating Admission Webhook。它的典型工作流程是:
- 写一个 HTTP Server,暴露一个 HTTPS 端点
- 为这个 Server 配置 TLS 证书(管理证书轮换)
- 写一个
MutatingWebhookConfiguration,告诉 API Server 什么时候调用你的 Webhook - 确保这个 Server 高可用(否则 API Server 可能阻塞)
- 处理 Webhook 超时、重试、失败策略……
只是为了给 Pod 注入几个标签,或者设置默认的资源限制。
这种运维负担在小团队里尤其明显:你花了大量精力维护一个「基础设施组件」,但它并不直接产生业务价值。
3.2 Mutating Admission Policies:用 Kubernetes 对象替代 Webhook Server
v1.36 将 MutatingAdmissionPolicy 推至 GA 并默认开启。它的核心思想是:
变更逻辑可以用原生 Kubernetes 对象直接表达,不再需要外部 Webhook 服务。
你写一个 MutatingAdmissionPolicy 对象(用 CEL 表达式描述变更逻辑),Kubernetes 原生执行这个策略——无需维护任何外部服务。
3.3 架构对比:Webhook vs MutatingAdmissionPolicy
┌─────────────────────────────────────────────────────────────┐
│ 方案 A:传统 Mutating Webhook │
├─────────────────────────────────────────────────────────────┤
│ │
│ kube-apiserver │
│ │ │
│ ▼ │
│ Webhook 调用 (HTTP/HTTPS) │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ Webhook Server │ ← 你需要维护这个服务 │
│ │ (TLS, 高可用, │ │
│ │ 证书轮换, │ │
│ │ 监控, 告警) │ │
│ └─────────────────┘ │
│ │ │
│ ▼ │
│ 返回变更后的 JSON │
│ │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 方案 B:Mutating Admission Policies (v1.36 GA) │
├─────────────────────────────────────────────────────────────┤
│ │
│ kube-apiserver │
│ │ │
│ ▼ │
│ CEL 表达式直接执行 (内置,无需外部服务) │
│ │ │
│ ▼ │
│ 返回变更后的 JSON │
│ │
└─────────────────────────────────────────────────────────────┘
3.4 实战一:自动注入资源限制
最常见的 Mutating Webhook 使用场景之一是「给没有设置 resource limits 的 Pod 注入默认值」。用 MutatingAdmissionPolicy 实现:
# 定义变更策略:为没有设置 resources.limits 的容器注入默认值
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingAdmissionPolicy
metadata:
name: default-resource-limits
spec:
# 绑定:这个策略适用于哪些资源
resourceRules:
- apiGroups: [""]
apiVersions: ["v1"]
resources: ["pods"]
operations: ["CREATE"]
# 变更逻辑(CEL 表达式)
mutations:
- patchType: JSONPatch
patchJSON: |
[
{
"op": "add",
"path": "/spec/containers/0/resources/limits",
"value": {
"memory": "512Mi",
"cpu": "500m"
}
}
]
---
# 绑定策略到特定命名空间
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingAdmissionPolicyBinding
metadata:
name: default-resource-limits-binding
spec:
policyName: default-resource-limits
# 只对带有特定标签的命名空间生效
namespaceSelector:
matchLabels:
resource-policy: strict
3.5 实战二:自动注入 sidecar 容器
另一个常见场景是「给所有 Pod 自动注入 sidecar(比如日志收集器、链路追踪 agent)」:
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingAdmissionPolicy
metadata:
name: inject-logging-sidecar
spec:
resourceRules:
- apiGroups: [""]
apiVersions: ["v1"]
resources: ["pods"]
operations: ["CREATE"]
# CEL 条件:只对带有特定标签的 Pod 生效
matchConditions:
- name: needs-logging
expression: "object.metadata.labels['logging'] == 'enabled'"
mutations:
- patchType: JSONPatch
patchJSON: |
[
{
"op": "add",
"path": "/spec/containers/-",
"value": {
"name": "log-collector",
"image": "fluent-bit:3.0",
"resources": {
"requests": {
"memory": "64Mi",
"cpu": "50m"
},
"limits": {
"memory": "128Mi",
"cpu": "100m"
}
},
"volumeMounts": [
{
"name": "varlog",
"mountPath": "/var/log"
}
]
}
},
{
"op": "add",
"path": "/spec/volumes/-",
"value": {
"name": "varlog",
"hostPath": {
"path": "/var/log",
"type": "Directory"
}
}
}
]
3.6 CEL 表达式进阶:条件化变更
MutatingAdmissionPolicy 支持用 CEL 做复杂的条件判断:
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingAdmissionPolicy
metadata:
name: smart-resource-injection
spec:
resourceRules:
- apiGroups: [""]
apiVersions: ["v1"]
resources: ["pods"]
operations: ["CREATE"]
mutations:
# 条件一:大内存应用自动注入 JVM 参数
- patchType: JSONPatch
patchJSON: |
[
{
"op": "add",
"path": "/spec/containers/0/env/-",
"value": {
"name": "JAVA_OPTS",
"value": "-Xmx2g -Xms2g -XX:+UseG1GC"
}
}
]
# CEL 条件:只有当镜像包含 "java" 或 "jvm" 时才注入
conditions:
- name: is-java-app
expression: |
object.spec.containers.exists(c,
c.image.contains('java') || c.image.contains('jvm') || c.image.contains('openjdk')
)
# 条件二:GPU 应用自动注入 NVIDIA 运行时配置
- patchType: JSONPatch
patchJSON: |
[
{
"op": "add",
"path": "/spec/containers/0/resources/limits/nvidia.com~1gpu",
"value": "1"
}
]
conditions:
- name: needs-gpu
expression: |
object.spec.containers.exists(c,
has(c.resources) && has(c.resources.limits) &&
('nvidia.com/gpu' in c.resources.limits)
)
3.7 与 Validating Admission Policies 的配合
v1.36 中,MutatingAdmissionPolicy 和 ValidatingAdmissionPolicy 可以配合使用,形成完整的「变更 + 验证」闭环:
# 第一步:变更(注入默认值)
# MutatingAdmissionPolicy: inject-defaults
# → 注入默认 resource limits
# 第二步:验证(确保变更后的对象符合要求)
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
name: enforce-resource-limits
spec:
resourceRules:
- apiGroups: [""]
apiVersions: ["v1"]
resources: ["pods"]
operations: ["CREATE", "UPDATE"]
validations:
- expression: |
object.spec.containers.all(c,
has(c.resources) && has(c.resources.limits) &&
has(c.resources.requests)
)
message: "所有容器必须设置 resources.limits 和 resources.requests"
- expression: |
object.spec.containers.all(c,
c.resources.limits.memory <= '1Gi' &&
c.resources.limits.cpu <= '1000m'
)
message: "单个容器的 resource limits 不得超过 1Gi 内存或 1000m CPU"
3.8 迁移指南:从 Webhook 到 MutatingAdmissionPolicy
如果你已经有现成的 Mutating Webhook,迁移到 MutatingAdmissionPolicy 的步骤:
第一步:识别 Webhook 的变更逻辑
# 查看现有的 MutatingWebhookConfiguration
kubectl get mutatingwebhookconfigurations.admissionregistration.k8s.io
kubectl get mutatingwebhookconfigurations.admissionregistration.k8s.io <name> -o yaml
第二步:把 Webhook 的变更逻辑翻译成 CEL + JSONPatch
| Webhook 逻辑 | MutatingAdmissionPolicy 等价实现 |
|---|---|
| 注入 sidecar 容器 | patchType: JSONPatch,"op": "add", "path": "/spec/containers/-" |
| 设置默认 values | patchType: JSONPatch,"op": "add" 或 "op": "replace" |
| 条件化变更 | matchConditions + CEL 表达式 |
| 复杂计算逻辑 | CEL 表达式支持字符串/数组/数学操作 |
第三步:灰度迁移(先让 Policy 模拟执行,不实际变更)
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingAdmissionPolicy
metadata:
name: my-policy
spec:
# 关键:failurePolicy 设为 Ignore,不影响现有请求
failurePolicy: Ignore
# 先用 dry-run 模式验证
# (注:具体 dry-run 支持需参考 v1.36 正式文档)
4. AI/ML 基础设施:OCI VolumeSource GA 与 DRA 精细调度
4.1 OCI VolumeSource:让镜像仓库成为「存储层」
AI/ML 场景下,如何把大模型权重文件(比如一个 70B 参数的 LLM,单文件可能 140GB)优雅地送进 Pod,一直是个老问题。
过去的选项有:
- 把模型文件打进主镜像 → 镜像体积爆炸,拉取时间长,版本管理困难
- 用 initContainer 去 S3/OSS 拉文件 → 需要额外的凭证管理,网络依赖强
- 用 ConfigMap/Secret → 大小限制(ConfigMap ≤ 1MiB),完全不可行
- 用 PVC 挂载共享存储 → 需要预先把模型文件拷贝到 PV,存储成本高
OCI VolumeSource 给出了一个极其优雅的解法:
把任意 OCI 镜像当作卷来引用。Kubernetes 会像拉取容器镜像一样把它拉下来并挂载到 Pod 里。
打包、分发、缓存、版本管理——全部复用现有的镜像仓库基础设施,和应用镜像完全解耦。
4.2 OCI VolumeSource 实战:分发 LLM 模型文件
假设你有一个 70B 参数的 LLM 模型文件(PyTorch 格式,model.safetensors,约 140GB)。你可以把它打包成一个 OCI 镜像,推送到你的镜像仓库,然后在 Pod 里引用:
第一步:把模型文件打包成 OCI 镜像
# Dockerfile.model
FROM scratch
COPY model.safetensors /model.safetensors
COPY config.json /config.json
COPY tokenizer.json /tokenizer.json
# 构建并推送
docker build -t registry.example.com/llm-models/llama3-70b:2026.06 -f Dockerfile.model .
docker push registry.example.com/llm-models/llama3-70b:2026.06
第二步:在 Pod 里引用这个 OCI 镜像作为卷
apiVersion: v1
kind: Pod
metadata:
name: llm-inference
spec:
containers:
- name: vllm
image: vllm/vllm:latest
args:
- "--model=/models/model.safetensors"
- "--tensor-parallel-size=4"
volumeMounts:
- name: model-weights
mountPath: /models
readOnly: true
resources:
limits:
nvidia.com/gpu: 4
volumes:
- name: model-weights
# 核心:OCI VolumeSource(v1.36 GA)
image:
# 引用 OCI 镜像(和 container 的 image 字段一样的格式)
name: registry.example.com/llm-models/llama3-70b:2026.06
# 可选:指定拉取策略
pullPolicy: IfNotPresent
# 可选:指定认证信息
# pullSecrets:
# - name: registry-creds
关键优势:
| 对比维度 | OCI VolumeSource | PVC(共享存储) | initContainer 拉 S3 |
|---|---|---|---|
| 分发速度 | 快(镜像仓库有缓存,支持 P2P 加速) | 中(取决于存储网络) | 慢(依赖公网/专线) |
| 版本管理 | 原生支持(tag/digest) | 困难 | 困难 |
| 存储成本 | 低(复用镜像仓库) | 高(需要高性能 PV) | 中(S3 存储费用) |
| 认证管理 | 复用 imagePullSecrets | 需要单独的存储认证 | 需要 S3 凭证 |
4.3 DRA 可分片设备(Beta):让一块 GPU 服务多个工作负载
Dynamic Resource Allocation(DRA) 是 Kubernetes 近年来在异构算力调度上最重要的架构升级。v1.36 将「DRA 可分片设备」推进到 Beta 并默认开启。
它解决了什么问题?
传统 Kubernetes 的设备调度是「整机分配」:如果你有一块 NVIDIA A100(80GB 显存),你要么把它整块分配给一个 Pod,要么就不用。对于小模型推理、开发测试等场景,这会导致严重的 GPU 利用率浪费。
DRA 可分片设备允许将单个物理 GPU 切分成多个逻辑单元,在多个工作负载之间共享:
# 定义一个「可分区 GPU」的 DeviceClass
apiVersion: resource.k8s.io/v1beta2
kind: DeviceClass
metadata:
name: nvidia-a100-partitionable
spec:
# CEL 选择器:匹配 NVIDIA A100 80GB
selectors:
- cel:
expression: |
device.driver == "nvidia.com/gpu" &&
device.attributes["nvidia.com/gpu.memory"] == 81920 &&
device.capabilities["nvidia.com/gpu.partitioning"] == "supported"
# 分区配置(具体语法以 NVIDIA GPU Operator 实现为准)
config:
- opaque:
driver: nvidia.com/gpu
parameters:
# 将一块 A100 切分成 4 个 20GB 的逻辑 GPU
partition: "4g.20gb"
---
# 工作负载 A:使用 1 个分区(20GB 显存)
apiVersion: v1
kind: Pod
metadata:
name: inference-small
spec:
containers:
- name: model-server
image: vllm/vllm:latest
args: ["--model", "Qwen2.5-7B", "--gpu-memory-util", "0.8"]
resources:
claims:
- name: gpu-partition
resourceClaims:
- name: gpu-partition
resourceClaimName: gpu-partition-claim-a
---
apiVersion: resource.k8s.io/v1beta2
kind: ResourceClaim
metadata:
name: gpu-partition-claim-a
spec:
deviceClassName: nvidia-a100-partitionable
# 请求 1 个分区(具体语法依 DRA 实现而定)
allocationMode: Immediate
4.4 DRA 设备污点(Beta):专用硬件的访问控制
v1.36 还引入了「DRA 设备污点」(Device Taints and Tolerations),允许管理员将某些设备标记为「专用」或「有缺陷」,只有明确容忍这些污点的工作负载才能使用。
# 管理员给某些 GPU 打上「专用」污点
# (具体实现依赖 DRA 驱动,以下为概念性示例)
apiVersion: v1
kind: ConfigMap
metadata:
name: gpu-taint-config
data:
taints: |
- device: node-1-gpu-0
taints:
- key: "dedicated"
value: "team-a"
effect: "NoSchedule"
tolerations:
- key: "dedicated"
value: "team-a"
operator: "Equal"
effect: "NoSchedule"
这对于多租户 GPU 平台尤其有用:你可以把某些高端 GPU(比如 H100)标记为「仅限特定团队使用」,避免被低优先级任务占用。
5. 网络安全收紧:IP/CIDR 校验收紧与 externalIPs 弃用
5.1 IP/CIDR 校验收紧(Beta):封堵 CVE-2021-29923 级攻击面
Kubernetes v1.36 对 IP 地址和 CIDR 的校验进行了系统性收紧(KEP-4858)。
问题背景:历史上,Kubernetes 对某些「非规范」的 IP 表达方式没有严格校验,导致不同实现之间存在解释歧义。攻击者可以利用这个歧义,构造特殊的 IP 地址绕过安全策略。
非规范 IP 示例(现在会被拒绝):
# 十进制前导零(某些实现会按八进制解析)
010.000.001.005 ← 某些系统解析为 8.0.1.5,某些解析为 10.0.1.5
# IPv4 映射的 IPv6 地址(可能被误解析)
::ffff:10.0.1.5 ← 在某些上下文里可能被错误解释
# 不规范的 CIDR(主机位不为零)
192.168.1.5/24 ← 正确写法应该是 192.168.1.0/24
从 v1.36 开始,这些非规范表达将不再被核心 Kubernetes 对象接受。
升级前检查清单:
#!/bin/bash
# 检查集群中是否存在非规范 IP 表达
echo "=== 检查 Service 中的 IP ==="
kubectl get services --all-namespaces -o json | \
jq -r '.items[] | select(.spec.clusterIP != null) |
.metadata.namespace + "/" + .metadata.name + " -> " + .spec.clusterIP' | \
grep -E '^(0|0[0-7])' # 查找前导零
echo "=== 检查 NetworkPolicy 中的 CIDR ==="
kubectl get networkpolicies --all-namespaces -o json | \
jq -r '.items[] | .spec.ingress[]?.from[]?.ipBlock.cidr' | \
grep -v '^$'
echo "=== 检查 Ingress 中的 IP ==="
kubectl get ingress --all-namespaces -o json | \
jq -r '.items[] | .status.loadBalancer.ingress[]?.ip' | \
grep -E '^(0|0[0-7])'
5.2 Service.spec.externalIPs 弃用:CVE-2020-8554 的最终解决
Service.spec.externalIPs 字段允许集群管理员将任意外部 IP 路由到 Service。这个功能在特定场景下有用(比如将特定 IP 绑定到内部服务),但它也是一个已知的安全隐患(CVE-2020-8554):
攻击者如果能够在节点上创建 Service(比如通过一个被攻破的 Pod,或者利用 RBAC 配置错误),他可以将任意外部 IP 绑定到自己的 Service,从而实现中间人攻击(MITM)。
v1.36 开始,使用 externalIPs 字段将显示弃用警告。计划在 v1.43(约 2027 年中)彻底移除。
替代方案:
| 需求场景 | 推荐方案 | 示例 |
|---|---|---|
| 云环境入站流量 | type: LoadBalancer Service | 云厂商托管的负载均衡器 |
| 简单端口暴露 | type: NodePort Service | 直接暴露节点端口 |
| 灵活的 HTTP/HTTPS 路由 | Gateway API | 现代化的流量管理 |
| 内部服务发现 | type: ClusterIP + Ingress/Gateway | 标准内部服务发现 |
迁移示例:
# 旧方式(使用 externalIPs,即将弃用)
apiVersion: v1
kind: Service
metadata:
name: legacy-service
spec:
type: ClusterIP
externalIPs:
- 203.0.113.10 # 危险:可能被用于 MITM 攻击
ports:
- port: 80
targetPort: 8080
---
# 新方式(使用 LoadBalancer,安全合规)
apiVersion: v1
kind: Service
metadata:
name: modern-service
annotations:
# AWS 示例
service.beta.kubernetes.io/aws-load-balancer-type: "nlb"
service.beta.kubernetes.io/aws-load-balancer-scheme: "internet-facing"
spec:
type: LoadBalancer
ports:
- port: 80
targetPort: 8080
selector:
app: my-app
6. 性能优化:SELinux 卷标签加速 GA 与 Kubelet API 鉴权加固
6.1 SELinux 卷标签加速:从「递归遍历」到「挂载即完成」
如果你在 SELinux 强制模式下运行 Kubernetes,你一定遇到过这个问题:
Pod 启动特别慢——特别是那些挂载了大容量 PVC 的 Pod。
慢在哪里?慢在 SELinux 的递归重打标签(recursive relabeling)。
原理:当 Pod 挂载一个卷时,Kubernetes 需要确保卷上的所有文件和目录都有正确的 SELinux 标签(container_t、container_log_t 等)。在 v1.36 之前,这个操作是递归遍历卷上的每一个文件,逐个设置 SELinux 上下文。
对于一个有百万级小文件的 PVC,这个过程可能需要数分钟。
v1.36 将「SELinux 卷标签优化」功能正式 GA(KEP-1710)。核心改进是:
使用
mount -o context=XYZ选项,在挂载时一次性应用正确的 SELinux 标签,完全跳过递归遍历。
apiVersion: v1
kind: Pod
metadata:
name: selinux-optimized
spec:
securityContext:
seLinuxOptions:
level: "s0:c123,c456"
# 关键:设置 seLinuxChangePolicy 为 MountOption
seLinuxChangePolicy: MountOption
containers:
- name: app
image: myapp:latest
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
persistentVolumeClaim:
claimName: large-pvc
性能对比(真实场景数据):
| 卷大小 | 文件数量 | v1.35 及之前 | v1.36(MountOption) |
|---|---|---|---|
| 10GB | 1,000 | ~5 秒 | <1 秒 |
| 100GB | 100,000 | ~2 分钟 | <3 秒 |
| 1TB | 1,000,000 | ~15 分钟 | <10 秒 |
6.2 Fine-grained Kubelet API Authorization GA:节点级安全加固
v1.36 将「Kubelet API 细粒度鉴权」推至 GA。这个特性的核心是:
Kubelet 的 HTTPS API(默认端口 10250)现在支持更精细的 RBAC 规则,可以针对不同子资源设置不同的权限。
Kubelet API 是一个经常被忽视的攻击面。它提供了 Pod 日志查看、exec 进入容器、端口转发等敏感操作。在 v1.36 之前,对这些操作的鉴权相对粗糙。
# v1.36 中,你可以写更精细的 RBAC 规则
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: pod-logs-reader
rules:
# 只允许查看 Pod 日志,不允许 exec
- apiGroups: [""]
resources: ["pods/log"]
verbs: ["get", "list"]
# 明确禁止 exec
# (注:具体 API 路径以 v1.36 正式文档为准,以下为概念性示例)
- apiGroups: [""]
resources: ["pods/exec"]
verbs: [] # 空列表 = 不允许任何操作
6.3 ServiceAccount Token 外部签名 GA:密钥管理与集群解耦
v1.36 将「ServiceAccount Token 外部签名」功能正式 GA(KEP-740)。
它解决了什么问题?
在默认配置下,Kubernetes 使用集群内置的私钥对 ServiceAccount Token 进行签名。这个私钥存储在 etcd 里(实际上是存在 API Server 的加密配置里)。如果 etcd 被攻破,攻击者可以伪造任意 ServiceAccount 的 Token。
外部签名允许将 Token 签名委托给外部密钥管理系统(比如云 KMS、HSM 硬件安全模块):
# API Server 配置(部分)
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- serviceaccounttokens
providers:
- kms:
name: aws-kms
endpoint: unix:///var/run/kms-plugin/aws-kms.sock
cachesize: 1000
timeout: 3s
对于 PCI-DSS、FedRAMP、SOC 2 等合规场景,这是一个** Kubernetes 原生兼容合规框架的清晰路径**。
7. 破坏性变更:gitRepo 移除、Ingress-NGINX 退役、IPVS 退场
7.1 gitRepo 卷插件永久移除:一个时代的安全终结
gitRepo 卷类型在 v1.36 中被永久移除(KEP-5040)。
为什么移除? gitRepo 卷在 Pod 初始化时,会在节点上以 root 身份执行 git clone。这是一个已知的严重安全漏洞:如果攻击者能够控制 Git 仓库的内容(比如通过供应链攻击),他可以在节点上以 root 身份执行任意代码。
迁移方案对比:
| 原方案 | 推荐替代 | 适用场景 |
|---|---|---|
| gitRepo 卷 | initContainer + git-sync | 需要实时同步 Git 内容的场景 |
| gitRepo 卷 | ConfigMap(离线同步) | 配置文件,不需要实时更新 |
| gitRepo 卷 | CSI Driver(如 gitfs-csi) | 需要挂载 Git 仓库作为文件系统的场景 |
推荐迁移方案:initContainer + git-sync
apiVersion: v1
kind: Pod
metadata:
name: app-with-git
spec:
volumes:
- name: git-data
emptyDir: {}
initContainers:
- name: git-sync
image: registry.k8s.io/git-sync/git-sync:v4.0.0
args:
- --repo=https://github.com/your-org/your-config-repo
- --branch=main
- --depth=1
- --period=30s # 每 30 秒同步一次
- --dest=/git-data
volumeMounts:
- name: git-data
mountPath: /git-data
containers:
- name: app
image: myapp:latest
volumeMounts:
- name: git-data
mountPath: /etc/config
readOnly: true
7.2 Ingress-NGINX 退役:一个时代的落幕
2026 年 3 月 24 日,Kubernetes SIG Network 和安全响应委员会正式宣布退役 Ingress-NGINX 项目。
自该日期起,不再发布任何新版本、Bug 修复或安全漏洞更新。现有部署仍然可以运行,但不再推荐用于生产环境。
退役原因:
- 维护团队资源不足:核心维护者数量不足以支撑一个关键基础设施项目
- 安全漏洞响应时间过长:多次高危 CVE 的修复周期过长
- 社区转向 Gateway API:Gateway API 被认为是 Ingress 的下一代替代
迁移到 Gateway API 完整实战:
# 第一步:安装 Gateway API CRD 和控制器(以 Envoy Gateway 为例)
# kubectl apply -f https://github.com/envoyproxy/gateway/releases/download/v1.2.0/install.yaml
# 第二步:定义 GatewayClass(通常由控制器自动创建)
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
name: envoy-gateway-class
spec:
controllerName: gateway.envoyproxy.io/gatewayclass-controller
---
# 第三步:定义 Gateway(等价于 Ingress Controller 的部署)
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: production-gateway
namespace: gateway-system
spec:
gatewayClassName: envoy-gateway-class
listeners:
- name: http
protocol: HTTP
port: 80
# 允许路由来自任何命名空间的 HTTPRoute
allowedRoutes:
namespaces:
from: All
- name: https
protocol: HTTPS
port: 443
tls:
mode: Terminate
certificateRefs:
- name: wildcard-tls
namespace: gateway-system
allowedRoutes:
namespaces:
from: All
---
# 第四步:定义 HTTPRoute(等价于 Ingress 资源)
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: api-route
namespace: default
spec:
parentRefs:
- name: production-gateway
namespace: gateway-system
rules:
# 规则一:/api/* 路由到 api-service
- matches:
- path:
type: PathPrefix
value: /api
backendRefs:
- name: api-service
port: 8080
# 规则二:/* 路由到 web-service
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: web-service
port: 80
# 规则三:基于 Header 的路由(Gateway API 原生支持,Ingress 需要注解)
- matches:
- path:
type: PathPrefix
value: /canary
headers:
- name: X-Canary
value: "true"
backendRefs:
- name: api-service-canary
port: 8080
Ingress vs Gateway API 功能对比:
| 功能 | Ingress-NGINX | Gateway API |
|---|---|---|
| HTTP/HTTPS 路由 | ✅ | ✅ |
| 基于 Header 的路由 | ⚠️ 需要注解 | ✅ 原生支持 |
| 流量分割(金丝雀) | ⚠️ 需要注解 | ✅ 原生支持 |
| TCP/UDP 路由 | ⚠️ 需要 ConfigMap | ✅ 原生支持(TCPRoute/UDPRoute) |
| 多租户隔离 | ⚠️ 需要额外配置 | ✅ 原生支持(Gateway 级别隔离) |
| 跨命名空间路由 | ⚠️ 需要额外配置 | ✅ 原生支持 |
7.3 kube-proxy IPVS 模式移除
v1.36 移除了 kube-proxy 的 IPVS 模式(在 v1.35 中已弃用)。
为什么移除? IPVS 模式虽然在大规模 Service 场景下性能优于 iptables,但它引入了额外的依赖(IPVS 内核模块),并且在某些网络环境下存在兼容性问题。社区决定聚焦于 iptables 和 nftables 两条路径。
迁移方案:
# 检查当前 kube-proxy 模式
kubectl get configmap kube-proxy -n kube-system -o jsonpath='{.data.config\.conf}' | grep mode
# 如果当前是 ipvs 模式,切换到 iptables 或 nftables
# 修改 kube-proxy ConfigMap
kubectl edit configmap kube-proxy -n kube-system
# 将 mode: "ipvs" 改为 mode: "iptables" 或 mode: "nftables"
# 重启 kube-proxy DaemonSet
kubectl rollout restart daemonset kube-proxy -n kube-system
8. 生产级升级实战:检查清单、迁移脚本、灰度方案
8.1 升级前检查清单(Production Checklist)
#!/bin/bash
# Kubernetes v1.36 生产环境升级前检查脚本
# 使用方法:./pre-upgrade-check.sh
set -e
GREEN='\033[0;32m'
RED='\033[0;31m'
YELLOW='\033[1;33m'
NC='\033[0m'
echo -e "${GREEN}=== Kubernetes v1.36 升级前检查 ===${NC}"
# 1. 检查 gitRepo 卷使用
echo -e "\n${YELLOW}[1/7] 检查 gitRepo 卷使用情况...${NC}"
GITREPO_COUNT=$(kubectl get pods --all-namespaces -o json | \
jq '[.items[].spec.volumes[]? | select(.gitRepo != null)] | length')
if [ "$GITREPO_COUNT" -gt 0 ]; then
echo -e "${RED}❌ 发现 $GITREPO_COUNT 个 Pod 使用 gitRepo 卷,升级前必须迁移!${NC}"
kubectl get pods --all-namespaces -o json | \
jq -r '.items[] | select(.spec.volumes[]?.gitRepo != null) |
.metadata.namespace + "/" + .metadata.name'
else
echo -e "${GREEN}✅ 未发现 gitRepo 卷使用${NC}"
fi
# 2. 检查 externalIPs 使用
echo -e "\n${YELLOW}[2/7] 检查 externalIPs 使用情况...${NC}"
EXTIP_COUNT=$(kubectl get services --all-namespaces -o json | \
jq '[.items[].spec.externalIPs // []] | flatten | length')
if [ "$EXTIP_COUNT" -gt 0 ]; then
echo -e "${RED}❌ 发现 $EXTIP_COUNT 个 externalIPs 配置,计划迁移到 LoadBalancer 或 NodePort${NC}"
kubectl get services --all-namespaces -o json | \
jq -r '.items[] | select(.spec.externalIPs != null) |
.metadata.namespace + "/" + .metadata.name + " -> " + (.spec.externalIPs | join(", "))'
else
echo -e "${GREEN}✅ 未发现 externalIPs 使用${NC}"
fi
# 3. 检查 Ingress-NGINX 部署
echo -e "\n${YELLOW}[3/7] 检查 Ingress-NGINX 部署...${NC}"
INGRESS_NGINX=$(kubectl get deployments --all-namespaces -l app.kubernetes.io/name=ingress-nginx 2>/dev/null | wc -l)
if [ "$INGRESS_NGINX" -gt 1 ]; then
echo -e "${RED}❌ 发现 Ingress-NGINX 部署,建议迁移到 Gateway API${NC}"
kubectl get deployments --all-namespaces -l app.kubernetes.io/name=ingress-nginx
else
echo -e "${GREEN}✅ 未发现 Ingress-NGINX 部署${NC}"
fi
# 4. 检查 kube-proxy 模式
echo -e "\n${YELLOW}[4/7] 检查 kube-proxy 模式...${NC}"
KUBE_PROXY_MODE=$(kubectl get configmap kube-proxy -n kube-system -o jsonpath='{.data.config\.conf}' 2>/dev/null | grep -o 'mode:[[:space:]]*"[^"]*"' | head -1 | cut -d'"' -f2)
if [ "$KUBE_PROXY_MODE" = "ipvs" ]; then
echo -e "${RED}❌ kube-proxy 使用 IPVS 模式,v1.36 已移除,需切换到 iptables/nftables${NC}"
else
echo -e "${GREEN}✅ kube-proxy 模式: $KUBE_PROXY_MODE${NC}"
fi
# 5. 检查节点内核版本(User Namespaces 要求)
echo -e "\n${YELLOW}[5/7] 检查节点内核版本...${NC}"
kubectl get nodes -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.nodeInfo.kernelVersion}{"\n"}{end}'
# 6. 检查容器运行时版本
echo -e "\n${YELLOW}[6/7] 检查容器运行时版本...${NC}"
kubectl get nodes -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.nodeInfo.containerRuntimeVersion}{"\n"}{end}'
# 7. 检查当前版本
echo -e "\n${YELLOW}[7/7] 当前 Kubernetes 版本...${NC}"
kubectl version --short 2>/dev/null || kubectl version
echo -e "\n${GREEN}=== 检查完成 ===${NC}"
echo -e "${YELLOW}请根据上述检查结果,制定详细的迁移和升级计划。${NC}"
8.2 灰度升级方案:控制平面 → 节点池 → 工作负载
推荐升级顺序:
第一阶段:控制平面(Control Plane)
├── 1. 备份 etcd
├── 2. 升级 kube-apiserver
├── 3. 升级 kube-controller-manager
├── 4. 升级 kube-scheduler
└── 5. 验证控制平面健康
第二阶段:节点池(Node Pool)—— 灰度升级
├── 1. 创建新节点池(运行 v1.36)
├── 2. 将少量非关键工作负载迁移到新节点池
├── 3. 验证工作负载正常运行
├── 4. 逐步迁移更多工作负载
└── 5. 下线旧节点池
第三阶段:工作负载迁移
├── 1. 迁移 Ingress 到 Gateway API
├── 2. 替换 gitRepo 卷为 initContainer 方案
├── 3. 替换 externalIPs 为 LoadBalancer
└── 4. 验证所有工作负载正常运行
8.3 使用蓝绿部署升级控制平面(kubeadm 场景)
# 第一步:备份 etcd(至关重要!)
ETCD_POD=$(kubectl get pods -n kube-system -l component=etcd -o jsonpath='{.items[0].metadata.name}')
kubectl exec -n kube-system $ETCD_POD -- etcdctl snapshot save /tmp/etcd-snapshot.db
# 第二步:升级第一个控制平面节点(kubeadm 场景)
# 在控制平面节点上执行
sudo kubeadm upgrade plan
sudo kubeadm upgrade apply v1.36.0
# 第三步:升级该节点上的 kubelet 和 kube-proxy
sudo apt-get update && sudo apt-get install -y kubelet=1.36.0-00 kubectl=1.36.0-00
sudo systemctl restart kubelet
# 第四步:逐个升级其他控制平面节点
# 在第一个控制平面节点上执行(升级其他控制平面节点)
sudo kubeadm upgrade apply v1.36.0 --control-plane-endpoint=<VIP:PORT>
# 第五步:验证控制平面健康
kubectl get componentstatuses
kubectl get nodes -o wide
9. 总结与展望:v1.36 的「春意」与生产落地建议
9.1 v1.36 到底值不值得升?
如果你在问这个问题,我的答案是:值得,而且建议认真排期。
v1.36 不是一个「可以忽略的小版本」——它是 Kubernetes 在安全、AI/ML 基础设施、平台化能力三条主线上多年积累的集中兑现。特别是以下特性,对生产环境有直接影响:
| 特性 | 影响 | 建议 |
|---|---|---|
| Pod User Namespaces GA | 多租户平台安全隔离能力质的飞跃 | 强烈推荐启用,先在测试环境验证兼容性 |
| Mutating Admission Policies GA | 降低平台团队的运维负担 | 推荐迁移,先把最简单的 Webhook 迁过来 |
| OCI VolumeSource GA | AI/ML 模型分发新范式 | 推荐评估,特别是有大模型推理场景的团队 |
| SELinux 卷标签加速 GA | Pod 启动速度显著提升 | 自动生效,无需额外配置 |
| Ingress-NGINX 退役 | 需要迁移到 Gateway API | 必须制定迁移计划,但不要急于一时 |
9.2 生产落地的时间表建议
现在(2026 年 6 月)
└── 评估 v1.36 新特性对业务的影响
├── 盘点现有工作负载是否使用了 gitRepo / externalIPs / IPVS
├── 评估 User Namespaces 的适用性(多租户场景?)
└── 规划 Ingress-NGINX 到 Gateway API 的迁移路线图
2026 年 7-8 月
└── 在测试/预发环境升级到 v1.36
├── 验证所有工作负载兼容性
├── 测试 User Namespaces 的性能和安全效果
└── 演练 Ingress 迁移方案
2026 年 9-10 月
└── 生产环境灰度升级
├── 先升级非关键业务集群
├── 控制平面 → 节点池 → 工作负载,分三步执行
└── 保留快速回滚方案
2026 年 11-12 月
└── 完成生产环境全面升级
├── 所有集群运行 v1.36
├── 完成 Ingress-NGINX 迁移(或制定明确的迁移时间表)
└── 开始评估 v1.37 的新特性
9.3 一句话总结
Kubernetes v1.36 不是一次喧嚣的大版本——它是一次关于「沉淀」的发布。User Namespaces 四年磨一剑,Mutating Admission Policies 让平台团队少维护一套 Webhook,OCI VolumeSource 把镜像仓库变成了存储层……春天是一个关于等待与兑现的季节,而 v1.36 就是 Kubernetes 社区送给生产环境的一份春意。
愿每一次 kubectl apply,都平安顺利。🌱
参考链接
- Kubernetes 官方发布博客:https://kubernetes.io/blog/2026/04/22/kubernetes-v1-36-release/
- v1.36 Sneak Peek:https://kubernetes.io/blog/2026/03/30/kubernetes-v1-36-sneak-peek/
- CHANGELOG-1.36:https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG/CHANGELOG-1.36.md
- KEP-4858(IP/CIDR 校验):https://github.com/kubernetes/enhancements/issues/4858
- KEP-740(ServiceAccount Token 外部签名):https://github.com/kubernetes/enhancements/issues/740
- KEP-1710(SELinux 卷标签加速):https://github.com/kubernetes/enhancements/issues/1710
作者:程序员茄子 | 发布时间:2026 年 6 月 | 字数:约 18000 字
如果有任何问题或建议,欢迎在评论区留言讨论。