编程 Kubernetes v1.36「Haru」深度解析:71 项增强、4 年磨一剑的安全隔离,与 AI 时代的异构算力新范式

2026-05-10 00:12:43 +0800 CST views 8

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 的协作链路,解决了以下核心问题:

  1. UID/GID 映射的一致性:确保容器内外的用户身份映射在所有主流运行时(containerd、CRI-O)中行为一致
  2. 文件系统权限处理:解决 User Namespace 下卷挂载的权限映射问题
  3. 与 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」就是一次关于「新芽」的收束——它不喧嚣,却足够扎实。

三条主线上的多年积累,在这一版集中兑现

  1. 安全:User Namespaces(4 年 GA)、Kubelet API Authorization、外部 Token 签名、IP/CIDR 校验收紧——从容器隔离到节点权限到身份签发,全栈安全加固
  2. AI/ML 基础设施:DRA 可分片设备、DRA 设备污点、PodResources for DRA、OCI VolumeSource——GPU 从「独占整数卡」走向「按需分片共享」,模型分发从「撑大镜像」走向「独立卷挂载」
  3. 平台化能力:Mutating Admission Policies GA、声明式验证 GA、Node Log Query GA——运维负担的结构性降低

对在生产环境维护 Kubernetes 的团队而言,v1.36 不是一个可以忽略的小版本,而是值得认真排期升级的那种发布。春天是一个关于「等待与兑现」的季节——四年前埋下的种子,终于在这一版里抽出了新芽。

愿每一次 kubectl apply,都平安顺利。🌱

推荐文章

robots.txt 的写法及用法
2024-11-19 01:44:21 +0800 CST
初学者的 Rust Web 开发指南
2024-11-18 10:51:35 +0800 CST
如何在Vue3中定义一个组件?
2024-11-17 04:15:09 +0800 CST
thinkphp swoole websocket 结合的demo
2024-11-18 10:18:17 +0800 CST
html流光登陆页面
2024-11-18 15:36:18 +0800 CST
利用图片实现网站的加载速度
2024-11-18 12:29:31 +0800 CST
Golang 中应该知道的 defer 知识
2024-11-18 13:18:56 +0800 CST
php curl并发代码
2024-11-18 01:45:03 +0800 CST
程序员茄子在线接单