Kubernetes v1.36「Haru」深度解析:71 项增强、4 年磨一剑的安全隔离,与 AI 时代的异构算力新范式
引言:当「春」落在 Kubernetes 身上
2026 年 4 月 22 日,Kubernetes v1.36 正式发布。代号「ハル(Haru)」取自日语——春、晴、遥,三重意象叠在一起:一个季节、一片晴空、一道远方的地平线。
Logo 的灵感来自葛饰北斋的《富嶽三十六景》中的《凱風快晴》(赤富士),版本号恰好与「三十六景」呼应。守护画面的两只猫——Stella 和 Nacho——象征着 Kubernetes 社区中成千上万的贡献者和 SIG 工作组。山体上书写着「晴れに翔け」,而未刻完的下半句是「未来よ明け」——向着明天的日出飞去。
但诗意之外,v1.36 的技术分量同样厚重:71 项增强,18 项 GA,25 项 Beta,25 项 Alpha。如果用一句话概括这个版本的气质:春归万物生,稳中见功夫——没有颠覆性的新范式,却把过去几年埋下的种子一一催开。
本文将从安全隔离、异构算力、平台能力、弃用破坏性变更四个维度,深入拆解 v1.36 的每一项核心技术变更,为生产环境的升级决策提供完整的技术参考。
一、GA 亮点:四年磨一剑的三项能力
1.1 Pod User Namespaces 正式 GA —— 容器安全隔离的里程碑
这是整个 v1.36 最值得深入讲解的特性。
问题背景:在传统 Kubernetes 中,容器内的 root 用户(UID 0)与宿主机上的 root 用户共享同一个 UID 命名空间。这意味着,如果攻击者突破了容器边界,他在宿主机上就拥有了 root 权限——这几乎是容器安全领域最大的隐患。
解决方案:User Namespaces 允许每个 Pod 拥有独立的用户 ID 映射。容器里看起来是 root(UID 0)的进程,在宿主机层面只是一个无特权的普通用户。即使攻击者逃逸出容器,在宿主节点上也几乎什么都做不了。
四年历程:这个特性从 Kubernetes v1.25(2022 年 8 月)进入 Alpha,历经 v1.28 Beta,v1.32 扩展支持,终于在 v1.36 毕业到 GA。四年时间,社区反复打磨内核、运行时、kubelet 的协作链路,解决了以下核心问题:
- UID/GID 映射的一致性:确保容器内外的用户身份映射在所有主流运行时(containerd、CRI-O)中行为一致
- 文件系统权限处理:解决 User Namespace 下卷挂载的权限映射问题
- 与 Seccomp/AppArmor 的兼容性:确保安全策略组合不会产生意外的权限提升
实战配置:
# Pod User Namespaces 启用方式
# v1.36 中该特性已默认开启,无需设置 feature gate
apiVersion: v1
kind: Pod
metadata:
name: secure-app
spec:
hostUsers: false # 关键字段:启用 User Namespace 隔离
containers:
- name: app
image: my-app:latest
securityContext:
runAsUser: 0 # 容器内仍然是 root
# 但在宿主机上,这个进程映射为一个无特权用户
# 例如:容器内 UID 0 → 宿主机 UID 100000
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
emptyDir: {}
UID 映射原理:
┌─────────────────────────────────────────────────┐
│ User Namespace UID 映射 │
├─────────────────────────────────────────────────┤
│ 容器内视角 宿主机视角 │
│ UID 0 (root) → UID 100000 (无特权用户) │
│ UID 1 → UID 100001 │
│ UID 2 → UID 100002 │
│ ... ... │
│ UID 65534 → UID 165534 │
├─────────────────────────────────────────────────┤
│ 效果: │
│ ✓ 容器内进程看起来是 root │
│ ✓ 宿主机上只是普通用户 │
│ ✓ 逃逸后在宿主机上几乎无权限 │
│ ✓ 文件系统权限通过 idmap 挂载自动转换 │
└─────────────────────────────────────────────────┘
安全效果对比:
# 无 User Namespace(传统模式)
# 容器内 root 逃逸后
whoami # root —— 宿主机上的 root!
id # uid=0(root) gid=0(root)
# 攻击者可以执行任何操作
# 有 User Namespace(v1.36 GA)
# 容器内 root 逃逸后
whoami # nobody —— 宿主机上的无特权用户
id # uid=100000(nobody) gid=100000(nobody)
# 攻击者无法访问 /etc/shadow、无法修改系统文件
# 无法挂载文件系统、无法加载内核模块
生产迁移建议:
# 第一步:识别可启用 User Namespace 的工作负载
# 以下工作负载可以安全启用:
# - 无特权端口的 Web 服务(>1024)
# - AI/ML 推理服务
# - 批处理 Job
# - 不需要访问宿主机资源的服务
# 第二步:逐步灰度
# 先在 Staging 环境中添加 hostUsers: false
# 验证文件权限、卷挂载、网络功能是否正常
apiVersion: apps/v1
kind: Deployment
metadata:
name: ml-inference
spec:
replicas: 3
selector:
matchLabels:
app: ml-inference
template:
metadata:
labels:
app: ml-inference
spec:
hostUsers: false # 启用 User Namespace
securityContext:
seccompProfile:
type: RuntimeDefault
containers:
- name: inference
image: ml-model:latest
ports:
- containerPort: 8080 # 非特权端口
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
runAsNonRoot: false # 容器内可以是 root
capabilities:
drop: ["ALL"]
1.2 Mutating Admission Policies GA —— 告别 Webhook Server 的运维负担
写过 Mutating Webhook 的工程师都明白那种痛:维护一个 TLS 加密的 HTTP Server、管理证书轮换、担心 Webhook 延迟、处理崩溃可能阻塞整个 API Server 的故障模式——只是为了给 Pod 注入几个标签或者设置默认资源限制,实在不划算。
v1.36 将 MutatingAdmissionPolicy 推至 GA 并默认开启。变更逻辑可以用原生 Kubernetes 对象直接表达,不再需要外部 Webhook 服务。
架构对比:
传统 Mutating Webhook 架构:
┌──────────┐ HTTPS ┌──────────────────┐
│ API │ ──────────────→ │ Webhook Server │
│ Server │ ←────────────── │ (Python/Go/etc.) │
└──────────┘ JSON Patch └──────────────────┘
│ │
│ 问题: │
│ ✗ 需要维护 TLS 证书 │
│ ✗ Webhook 延迟(网络往返) │
│ ✗ Webhook 崩溃 → 集群不可用 │
│ ✗ 需要单独部署和监控 │
v1.36 MutatingAdmissionPolicy 架构:
┌──────────┐ CEL 表达式 ┌──────────────────┐
│ API │ ────────────────→ │ 内置 CEL 引擎 │
│ Server │ ←──────────────── │ (进程内执行) │
└──────────┘ JSON Patch └──────────────────┘
│ │
│ 优势: │
│ ✓ 无需外部服务 │
│ ✓ 微秒级执行延迟 │
│ ✓ 无 Webhook 崩溃风险 │
│ ✓ 声明式配置,可 GitOps 管理 │
实战示例:自动注入默认资源限制
# MutatingAdmissionPolicy:为未设置资源限制的 Pod 自动注入默认值
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingAdmissionPolicy
metadata:
name: default-resource-limits
spec:
matchConstraints:
objectSelector:
matchLabels:
inject-defaults: "true" # 只对标记了此标签的资源生效
resourceRules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["pods"]
mutations:
- patchType: ApplyConfiguration
expression: |
# 如果未设置 CPU 请求,注入默认值
has(object.spec.containers) ?
object.spec.containers.map(c,
Object {
metadata: Object {
name: c.name
},
resources: Object {
requests: c.resources?.requests?.cpu == null ?
{"cpu": Quantity.string("100m")} : {},
limits: c.resources?.limits?.cpu == null ?
{"cpu": Quantity.string("500m")} : {}
}
}
) : []
failurePolicy: Fail # 策略执行失败时拒绝请求
---
# 绑定策略到目标
apiVersion: admissionregistration.k8s.io/v1alpha1
kind: MutatingAdmissionPolicyBinding
metadata:
name: default-resource-limits-binding
spec:
policyName: default-resource-limits
matchResources:
namespaceSelector:
matchLabels:
environment: production # 仅在生产命名空间生效
更多实用场景:
# 场景一:自动注入 sidecar 代理
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingAdmissionPolicy
metadata:
name: inject-sidecar
spec:
matchConstraints:
resourceRules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE"]
resources: ["pods"]
mutations:
- patchType: ApplyConfiguration
expression: |
[Object {
metadata: Object {
name: "istio-proxy"
},
image: "proxy:1.20.0",
ports: [Object {containerPort: 15001}]
}]
# 场景二:强制添加安全标签
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingAdmissionPolicy
metadata:
name: enforce-security-labels
spec:
matchConstraints:
resourceRules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["pods"]
mutations:
- patchType: ApplyConfiguration
expression: |
Object {
metadata: Object {
labels: {
"security-tier": "standard",
"managed-by": "admission-policy"
}
}
}
与 ValidatingAdmissionPolicy 的协同:
# Mutating + Validating 组合拳:
# 1. MutatingAdmissionPolicy 先注入默认值
# 2. ValidatingAdmissionPolicy 再校验最终状态
# 第一步:Mutating 注入默认资源限制(如上)
# 第二步:Validating 确保资源限制在合理范围内
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
name: validate-resource-limits
spec:
matchConstraints:
resourceRules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["pods"]
validations:
- expression: |
object.spec.containers.all(c,
c.resources?.limits?.cpu != null &&
c.resources?.limits?.memory != null
)
message: "所有容器必须设置 CPU 和 Memory 的 limits"
- expression: |
object.spec.containers.all(c,
c.resources?.limits?.cpu.compareTo(Quantity.string("4")) <= 0
)
message: "CPU limits 不能超过 4 核"
---
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicyBinding
metadata:
name: validate-resource-limits-binding
spec:
policyName: validate-resource-limits
validationActions: [Deny]
1.3 OCI VolumeSource GA —— 让镜像仓库成为 Pod 的「外挂硬盘」
AI/ML 场景下,如何把模型权重、配置、数据集优雅地送进 Pod,一直是个老问题。过去的选项要么把主镜像撑大(每次更新模型都要重新构建镜像),要么写一个 init 容器去拉(增加启动时间和复杂度),或者和 ConfigMap 的 1MB 体积限制死磕。
OCI VolumeSource 把任意 OCI 镜像当作卷来引用。Kubernetes 会像拉取容器镜像一样把它拉下来并挂载到 Pod 里。打包、分发、缓存、版本管理——全部复用现有的镜像仓库基础设施。
# OCI VolumeSource:将模型权重镜像作为卷挂载
apiVersion: v1
kind: Pod
metadata:
name: ml-inference-with-model
spec:
containers:
- name: inference
image: pytorch/inference:2.4.0
volumeMounts:
- name: model-weights
mountPath: /models/resnet50
readOnly: true
- name: config
mountPath: /etc/model-config
readOnly: true
command: ["python", "serve.py"]
env:
- name: MODEL_PATH
value: "/models/resnet50"
volumes:
- name: model-weights
oci:
# 模型权重独立打包为 OCI 镜像
# 与应用镜像完全解耦
reference: registry.example.com/models/resnet50:v2.1
- name: config
oci:
# 配置文件也独立打包
reference: registry.example.com/configs/inference-config:latest
OCI VolumeSource 的核心优势:
# 传统方式 vs OCI VolumeSource 对比
# 传统方式 1:构建大镜像(模型内置)
# ┌──────────────────────────┐
# │ 应用镜像 + 模型权重 │ 5GB+,每次更新模型都要重新构建
# │ python:3.11 + resnet50 │ 推送和拉取都很慢
# └──────────────────────────┘
# 传统方式 2:Init 容器拉取
# ┌──────────┐ ┌──────────────────────┐
# │ 应用镜像 │ + │ Init 容器下载模型文件 │ 启动时间增加
# │ 500MB │ │ 从 S3/GCS 拉取 │ 需要管理存储凭证
# └──────────┘ └──────────────────────┘
# OCI VolumeSource(v1.36 GA)
# ┌──────────┐ ┌──────────────┐
# │ 应用镜像 │ + │ 模型 OCI 镜像 │ 各自独立版本管理
# │ 500MB │ │ 4.5GB │ 复用镜像仓库缓存
# └──────────┘ └──────────────┘ 节点间自动分发
多模型推理服务的实战配置:
# 一个推理服务同时加载多个模型
# 每个模型独立打包为 OCI 镜像
apiVersion: apps/v1
kind: Deployment
metadata:
name: multi-model-inference
spec:
replicas: 3
selector:
matchLabels:
app: multi-model-inference
template:
metadata:
labels:
app: multi-model-inference
spec:
containers:
- name: inference
image: inference-server:latest
volumeMounts:
- name: resnet50-weights
mountPath: /models/resnet50
readOnly: true
- name: bert-weights
mountPath: /models/bert-base
readOnly: true
- name: whisper-weights
mountPath: /models/whisper-small
readOnly: true
resources:
limits:
nvidia.com/gpu: 1
volumes:
- name: resnet50-weights
oci:
reference: registry.example.com/models/resnet50:v2.1
- name: bert-weights
oci:
reference: registry.example.com/models/bert-base:v1.5
- name: whisper-weights
oci:
reference: registry.example.com/models/whisper-small:v3.0
1.4 其他关键 GA 特性一览
Volume Group Snapshots GA:支持对一组卷做崩溃一致性(crash-consistent)快照,并恢复为新的卷集合。有状态工作负载的灾备场景可以少写很多脚本。
# 创建卷组快照
apiVersion: groupsnapshot.storage.k8s.io/v1
kind: VolumeGroupSnapshot
metadata:
name: database-backup-20260422
spec:
source:
selector:
matchLabels:
app: postgres-cluster # 选择同一应用的所有 PVC
volumeGroupSnapshotClassName: csi-group-snapclass
---
# 从快照恢复
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres-data-restored
spec:
dataSource:
name: database-backup-20260422
kind: VolumeGroupSnapshot
apiGroup: groupsnapshot.storage.k8s.io
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 100Gi
ServiceAccount Token 外部签名 GA(KEP-740):kube-apiserver 可以把 Token 签发委托给外部 KMS 或 HSM。对 PCI-DSS、FedRAMP、SOC 2 等合规场景而言,这是 Kubernetes 原生兼容合规框架的一条清晰路径。
Fine-grained Kubelet API Authorization GA:节点级安全能力显著增强。过去访问 kubelet API 需要 nodes/proxy 权限——这个权限太大了,能看到所有信息。现在可以精确控制:只允许读取日志、只允许查看 Pod 状态、只允许访问 metrics 端点等。
# 精细化 Kubelet API 权限示例
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: node-metrics-reader
rules:
- apiGroups: [""]
resources: ["nodes/metrics"] # 只允许访问 metrics 端点
verbs: ["get"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: node-logs-reader
rules:
- apiGroups: [""]
resources: ["nodes/log"] # 只允许读取日志
verbs: ["get", "list"]
SELinux 递归重打标签加速 GA(KEP-1710):从 v1.27(2023 年 4 月)就在 Beta 的特性终于稳定,挂卷容器的启动速度会有可感知的提升。
二、Beta 阶段:异构算力与网络安全同步推进
2.1 DRA 继续「抽枝」:从整机调度走向精细分配
动态资源分配(DRA)是近两个版本演进最快的模块。v1.36 再推两项关键增强:
DRA 可分片设备(KEP-4815)进入 Beta:允许将单个硬件加速器切分成多个逻辑单元,在多个工作负载之间共享。对 GPU 这类昂贵资源尤其重要——独占式分配常常造成严重的利用率浪费。
# DRA 可分片设备:GPU 分时共享
# 场景:一块 A100 GPU 被切分为 4 个逻辑单元
apiVersion: resource.k8s.io/v1beta1
kind: ResourceClass
metadata:
name: gpu-a100-sliced
driverName: gpu.nvidia.com
parametersRef:
apiGroup: resource.k8s.io
kind: ResourceClassParameters
name: gpu-a100-sliced-params
---
apiVersion: resource.k8s.io/v1beta1
kind: ResourceClassParameters
metadata:
name: gpu-a100-sliced-params
vendorParams:
gpu.nvidia.com/slice-count: "4" # 切分为 4 个逻辑 GPU
gpu.nvidia.com/memory-per-slice: "20Gi" # 每个逻辑 GPU 20GB 显存
gpu.nvidia.com/compute-percentage: "25" # 每个逻辑 GPU 25% 算力
---
# Pod 请求一个逻辑 GPU 切片
apiVersion: v1
kind: Pod
metadata:
name: inference-task
spec:
containers:
- name: inference
image: pytorch/inference:latest
resourceClaims:
- name: gpu-slice
resourceClaims:
- name: gpu-slice
resourceClassName: gpu-a100-sliced
DRA 设备污点与容忍(KEP-5055)进入 Beta:默认开启,允许管理员将某些设备标记为不可用或仅允许特定工作负载使用。类似于 Node Taints,但作用于设备级别。
# DRA 设备污点:将故障 GPU 标记为不可调度
# 在 ResourceSlice 中为特定设备添加污点
apiVersion: resource.k8s.io/v1beta1
kind: ResourceSlice
metadata:
name: gpu-node-1
spec:
driver: gpu.nvidia.com
pool:
name: gpu-pool-1
deviceTaints:
- key: "gpu.nvidia.com/unhealthy"
value: "true"
effect: NoSchedule # 不健康的 GPU 不调度新 Pod
devices:
- name: gpu-0
basic:
attributes:
gpu.nvidia.com/model: "A100"
gpu.nvidia.com/memory: "80Gi"
---
# 需要使用该设备的 Pod 必须添加容忍
apiVersion: v1
kind: Pod
metadata:
name: gpu-diagnostic-tool
spec:
containers:
- name: diagnostic
image: gpu-diagnostic:latest
resourceClaims:
- name: gpu
resourceClaims:
- name: gpu
resourceClassName: gpu-a100
tolerations:
- key: "gpu.nvidia.com/unhealthy"
value: "true"
effect: NoSchedule # 只有诊断工具才容忍不健康设备
2.2 Pod 级原地伸缩进入 Beta:不重启 Pod 也能调资源
In-Place Pod Level Resize 进入 Beta,支持 Pod 级别(而非仅容器级别)的 CPU/内存在线伸缩,面向 cgroup v2 环境。
# Pod 原地伸缩:在线调整资源限制
# 注意:需要 cgroup v2 支持
apiVersion: v1
kind: Pod
metadata:
name: resizable-app
spec:
containers:
- name: app
image: my-app:latest
resources:
requests:
cpu: "500m"
memory: "1Gi"
limits:
cpu: "2"
memory: "4Gi"
resizePolicy: # 定义伸缩策略
- resourceName: cpu
restartPolicy: NotRequired # CPU 变更不需要重启
- resourceName: memory
restartPolicy: RestartContainer # 内存变更需要重启容器
# 在线伸缩操作
kubectl patch pod resizable-app --type=json -p='[
{"op":"replace",
"path":"/spec/containers/0/resources/requests/cpu",
"value":"1"},
{"op":"replace",
"path":"/spec/containers/0/resources/limits/cpu",
"value":"4"}
]'
# 查看伸缩状态
kubectl get pod resizable-app -o jsonpath='{.status.resize}'
# 输出:InProgress —— 伸缩正在进行中
2.3 IP/CIDR 校验收紧(KEP-4858):堵上 CVE 级别的安全漏洞
非规范 IP 格式(如 010.000.001.005 或 ::ffff:10.0.1.5)以及模糊的 CIDR 值,将不再被核心 Kubernetes 对象接受。这个变更背后是 CVE-2021-29923 级别的安全隐患——历史上这些非规范格式在不同实现之间存在解释歧义,曾被用作攻击面。
# 升级前排查脚本:检查集群中是否存在非规范 IP 格式
#!/bin/bash
echo "=== 检查 Service 中的非规范 IP ==="
kubectl get services -A -o json | jq -r '.items[] |
select(.spec.externalIPs != null) |
.metadata.namespace + "/" + .metadata.name + ": " + (.spec.externalIPs | @json)'
echo "=== 检查 NetworkPolicy 中的非规范 CIDR ==="
kubectl get networkpolicies -A -o json | jq -r '.items[] |
.spec.ingress[]?.from[]?.ipBlock?.cidr // empty |
select(test("0[0-9]\\.")) |
"Non-canonical CIDR found"'
echo "=== 检查 ConfigMap 中可能包含 IP 配置的键 ==="
kubectl get configmaps -A -o json | jq -r '.items[] |
.data | to_entries[] |
select(.value | test("0[0-9]\\.[0-9]+\\.[0-9]+\\.[0-9]+")) |
"Non-canonical IP in ConfigMap"'
echo "=== 升级前必须修复以上问题 ==="
三、Alpha 实验:来年之春的种子
3.1 Workload Aware Scheduling:AI/ML 分布式训练的调度福音
v1.36 引入了一整套工作负载感知调度(Workload Aware Scheduling,WAS)特性。过去 Kubernetes 调度器把每个 Pod 当作独立单元来调度,对于分布式训练这类需要多个 Pod 协同工作的场景,经常出现「部分 Pod 调度成功、部分失败」的碎片化问题。
新的 PodGroup API 把相关的 Pod 视为一个逻辑实体,要么全部调度成功,要么全部不调度——这就是所谓的原子调度(Atomic Scheduling)。
# PodGroup:分布式训练的原子调度
apiVersion: scheduling.x-k8s.io/v1alpha1
kind: PodGroup
metadata:
name: distributed-training
spec:
minMember: 8 # 最少需要 8 个 Pod 同时启动
scheduleStartTime: "2026-04-22T00:00:00Z"
queue: default
---
# Worker Pod 引用 PodGroup
apiVersion: v1
kind: Pod
metadata:
name: training-worker-0
labels:
pod-group.scheduling.x-k8s.io/name: distributed-training
pod-group.scheduling.x-k8s.io/role: worker
spec:
containers:
- name: training
image: pytorch/distributed-training:latest
resources:
limits:
nvidia.com/gpu: 4
env:
- name: WORLD_SIZE
value: "8"
- name: RANK
value: "0"
3.2 HPA 外部指标获取失败回退
当外部指标 API(如云厂商队列、Datadog)抖动时,HPA 能采取合理的降级策略,避免灾难性伸缩决策。在之前的版本中,如果外部指标 API 返回错误,HPA 可能会做出极端的扩缩容决策。
3.3 PVC 增加 UnusedSince 字段
记录上一次被使用的时间戳,为存储资源的生命周期治理提供依据。这是云成本优化的基础设施——你可以很容易地找出「超过 90 天没被使用」的 PVC,清理闲置存储。
# 查找长时间未使用的 PVC
kubectl get pvc -A -o json | jq -r '.items[] |
select(.status.unusedSince != null) |
.metadata.namespace + "/" + .metadata.name +
": unused since " + .status.unusedSince'
四、移除与弃用:升级前务必清点
这一节是升级决策中最关键的部分——涉及到升级能否平稳落地。
4.1 gitRepo 卷插件被移除(KEP-5040)
根因是容器内以 root 身份执行代码的严重安全隐患。仍在使用的,升级前必须迁移。
# ❌ 已移除:gitRepo 卷
apiVersion: v1
kind: Pod
spec:
volumes:
- name: code
gitRepo:
repository: "https://github.com/example/repo"
revision: "main"
# ✅ 迁移方案:使用 Init 容器 + EmptyDir
apiVersion: v1
kind: Pod
spec:
initContainers:
- name: git-clone
image: alpine/git:latest
command:
- git
- clone
- --depth=1
- --branch=main
- https://github.com/example/repo
- /code
volumeMounts:
- name: code
mountPath: /code
containers:
- name: app
image: my-app:latest
volumeMounts:
- name: code
mountPath: /app/code
volumes:
- name: code
emptyDir: {}
4.2 kube-proxy IPVS 模式被移除
v1.35 已弃用,v1.36 彻底移除。继续使用的场景需切换到 iptables 或 nftables 模式。
# 检查集群是否使用 IPVS 模式
kubectl get configmap kube-proxy -n kube-system -o yaml | grep mode
# 如果输出 mode: "ipvs" → 必须迁移
# 迁移到 nftables 模式(推荐)
kubectl get configmap kube-proxy -n kube-system -o yaml | \
sed 's/mode: "ipvs"/mode: "nftables"/' | \
kubectl apply -f -
# 重启 kube-proxy
kubectl rollout restart daemonset kube-proxy -n kube-system
4.3 Service externalIPs 字段弃用警告
从 v1.36 开始显示弃用警告,计划在 v1.43 彻底移除。这是 CVE-2020-8554 的安全隐患,可能被用于中间人攻击。
# 排查集群中使用了 externalIPs 的 Service
kubectl get services -A -o json | jq -r '.items[] |
select(.spec.externalIPs != null and (.spec.externalIPs | length) > 0) |
.metadata.namespace + "/" + .metadata.name +
": externalIPs = " + (.spec.externalIPs | join(","))'
4.4 Ingress-NGINX 正式退役
这是独立于 v1.36 版本但同期发生的大事件:2026 年 3 月 24 日,Kubernetes SIG Network 和安全响应委员会正式退役 Ingress-NGINX 项目。不再发布任何新版本、Bug 修复或安全漏洞更新。
替代方案:Gateway API
# Gateway API 替代 Ingress-NGINX
# 第一步:安装 Gateway API CRD
kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.3.0/standard-install.yaml
# 第二步:部署 Gateway 控制器(如 Envoy Gateway)
helm install envoy-gateway oci://docker.io/envoyproxy/gateway-helm --version v1.3.0 -n envoy-gateway-system --create-namespace
# 第三步:创建 Gateway 资源
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: main-gateway
namespace: default
spec:
gatewayClassName: envoy-gateway
listeners:
- name: http
port: 80
protocol: HTTP
allowedRoutes:
namespaces:
from: All
- name: https
port: 443
protocol: HTTPS
tls:
mode: Terminate
certificateRefs:
- name: wildcard-cert
---
# 第四步:创建 HTTPRoute 替代 Ingress
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: app-route
spec:
parentRefs:
- name: main-gateway
hostnames:
- "app.example.com"
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: app-service
port: 8080
五、Node Log Query GA:不用 SSH 也能查节点日志
之前查看节点日志需要 SSH 到节点上。v1.36 中 Node Log Query 正式 GA,允许通过 kubelet API 直接查询节点上的服务日志。
# 通过 kubectl 查询节点日志
# 查看 kubelet 日志
kubectl get --raw "/api/v1/nodes/node-1/proxy/logs/?path=kubelet.log"
# 查看 kube-proxy 日志
kubectl get --raw "/api/v1/nodes/node-1/proxy/logs/?path=kube-proxy.log"
# 查看容器运行时日志
kubectl get --raw "/api/v1/nodes/node-1/proxy/logs/?path=containerd.log"
# 带时间范围过滤
kubectl get --raw "/api/v1/nodes/node-1/proxy/logs/?path=kubelet.log&sinceTime=2026-04-22T00:00:00Z"
六、声明式验证(validation-gen)GA:告别手写 OpenAPI Schema
自定义资源(CRD)的校验一直是 Kubernetes 开发者的一大痛点。手写 OpenAPI v3 Schema 繁琐、容易出错、难以维护。v1.36 中,声明式验证正式 GA,开发者可以用 CEL 表达式在 Go struct tag 中定义校验规则,validation-gen 工具自动生成校验代码。
// 传统方式:手写 OpenAPI Schema(繁琐且易出错)
// +k8s:openapi-gen=true
// +schema:... (大量手写 Schema 定义)
// v1.36 声明式验证:在 Go struct 中直接定义规则
type MyResourceSpec struct {
// +k8s:minimum=1
// +k8s:maximum=100
Replicas int32 `json:"replicas"`
// +k8s:enum=small
// +k8s:enum=medium
// +k8s:enum=large
Size string `json:"size"`
// +k8s:pattern=`^[a-z0-9]([-a-z0-9]*[a-z0-9])?$`
Name string `json:"name"`
// +k8s:validation:cel=:self.port > 0 && self.port < 65536
Port int32 `json:"port"`
// +k8s:validation:cel=:self.minReplicas <= self.maxReplicas
MinReplicas int32 `json:"minReplicas"`
MaxReplicas int32 `json:"maxReplicas"`
}
七、gogoprotobuf 依赖移除:代码库的安全清道夫
v1.36 完成了对 gogoprotobuf 依赖的彻底移除。这个库已经长期无人维护,成为潜在的安全漏洞来源和 Go 新特性采用的阻碍。Kubernetes 项目选择了 fork 并内化所需的生成逻辑到 k8s.io/code-generator 中,既消除了不安全的运行时依赖,又保持了 API 行为和序列化兼容性。
八、升级决策框架:从评估到灰度
8.1 升级前检查清单
#!/bin/bash
# Kubernetes v1.36 升级前自动化检查脚本
echo "====== Kubernetes v1.36 升级前检查 ======"
# 1. 检查 gitRepo 卷使用
echo -e "\n[1] 检查 gitRepo 卷使用..."
GIT_VOLUMES=$(kubectl get pods -A -o json | jq '[.items[] | select(.spec.volumes[]?.gitRepo != null)] | length')
if [ "$GIT_VOLUMES" -gt 0 ]; then
echo "⚠️ 发现 $GIT_VOLUMES 个使用 gitRepo 卷的 Pod,必须迁移!"
else
echo "✅ 未发现 gitRepo 卷使用"
fi
# 2. 检查 IPVS 模式
echo -e "\n[2] 检查 kube-proxy 模式..."
PROXY_MODE=$(kubectl get configmap kube-proxy -n kube-system -o jsonpath='{.data.configConf}' 2>/dev/null | grep mode || echo "auto")
if echo "$PROXY_MODE" | grep -q "ipvs"; then
echo "⚠️ kube-proxy 使用 IPVS 模式,v1.36 已移除!需迁移到 nftables"
else
echo "✅ kube-proxy 未使用 IPVS 模式"
fi
# 3. 检查 externalIPs 使用
echo -e "\n[3] 检查 externalIPs 使用..."
EXTERNAL_IPS=$(kubectl get services -A -o json | jq '[.items[] | select(.spec.externalIPs != null and (.spec.externalIPs | length) > 0)] | length')
if [ "$EXTERNAL_IPS" -gt 0 ]; then
echo "⚠️ 发现 $EXTERNAL_IPS 个使用 externalIPs 的 Service,v1.43 将移除"
else
echo "✅ 未发现 externalIPs 使用"
fi
# 4. 检查 Ingress-NGINX 使用
echo -e "\n[4] 检查 Ingress-NGINX 使用..."
INGRESS_NGINX=$(kubectl get pods -A -l app.kubernetes.io/name=ingress-nginx --no-headers 2>/dev/null | wc -l)
if [ "$INGRESS_NGINX" -gt 0 ]; then
echo "⚠️ 发现 Ingress-NGINX 部署,已退役,需评估 Gateway API 迁移"
else
echo "✅ 未发现 Ingress-NGINX 部署"
fi
# 5. 检查非规范 IP 格式
echo -e "\n[5] 检查非规范 IP 格式..."
NON_CANONICAL=$(kubectl get services -A -o json | jq '[.items[] | .spec.clusterIP | select(test("^0[0-9]\\."))] | length' 2>/dev/null)
if [ "$NON_CANONICAL" -gt 0 ]; then
echo "⚠️ 发现非规范 IP 格式,v1.36 将拒绝"
else
echo "✅ 未发现非规范 IP 格式"
fi
echo -e "\n====== 检查完成 ======"
8.2 灰度升级路径
升级路径推荐:
1.29/1.30 → 1.32 → 1.34 → 1.36(逐版升级,最安全)
1.33/1.34 → 1.36(跳一版,需仔细测试)
1.35 → 1.36(同系列升级,最简单)
灰度步骤:
1. Staging 环境全量升级 + 运行完整测试套件
2. 生产环境:先升级一个 Control Plane 节点
3. 观察 48 小时:API 延迟、调度延迟、etcd 性能
4. 逐步灰度 Node 池
5. 启用 User Namespaces 灰度:先对非核心服务添加 hostUsers: false
总结:v1.36 为什么值得认真排期
如果说 v1.34「Of Wind & Will」是一次关于「意志」的推进,v1.35「Timbernetes(世界树)」是一次关于「根系」的扩展,那么 v1.36「Haru」就是一次关于「新芽」的收束——它不喧嚣,却足够扎实。
三条主线上的多年积累,在这一版集中兑现:
- 安全:User Namespaces(4 年 GA)、Kubelet API Authorization、外部 Token 签名、IP/CIDR 校验收紧——从容器隔离到节点权限到身份签发,全栈安全加固
- AI/ML 基础设施:DRA 可分片设备、DRA 设备污点、PodResources for DRA、OCI VolumeSource——GPU 从「独占整数卡」走向「按需分片共享」,模型分发从「撑大镜像」走向「独立卷挂载」
- 平台化能力:Mutating Admission Policies GA、声明式验证 GA、Node Log Query GA——运维负担的结构性降低
对在生产环境维护 Kubernetes 的团队而言,v1.36 不是一个可以忽略的小版本,而是值得认真排期升级的那种发布。春天是一个关于「等待与兑现」的季节——四年前埋下的种子,终于在这一版里抽出了新芽。
愿每一次 kubectl apply,都平安顺利。🌱