编程 Kubernetes v1.36 深度实战:当容器编排学会「稳中见功夫」——从 User Namespaces 安全隔离到 Mutating Admission Policies 生产落地的完全指南(2026)

2026-06-14 02:48:00 +0800 CST views 5

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 把镜像仓库变成了「存储层」……本文将从架构原理、核心特性、代码实战、升级迁移四个维度,给你一份生产级完全指南。


目录

  1. 版本概览:71 项增强背后的「收口」逻辑
  2. 安全主线一:Pod User Namespaces GA——四年磨一剑的容器隔离革命
  3. 安全主线二:Mutating Admission Policies GA——告别 Webhook Server 的运维革命
  4. AI/ML 基础设施:OCI VolumeSource GA 与 DRA 精细调度
  5. 网络安全收紧:IP/CIDR 校验收紧与 externalIPs 弃用
  6. 性能优化:SELinux 卷标签加速 GA 与 Kubelet API 鉴权加固
  7. 破坏性变更:gitRepo 移除、Ingress-NGINX 退役、IPVS 退场
  8. 生产级升级实战:检查清单、迁移脚本、灰度方案
  9. 总结与展望: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 GAGPU 共享、模型分发、加速器调度
平台化能力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 隔离,你只有两条路:

  1. 跑 gVisor、Kata Containers 这类第三方运行时——引入额外的复杂度和性能开销
  2. 接受更弱的隔离保证——祈祷内核漏洞别被利用

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 才能做的操作(比如 chownmount 某些文件系统)。但这些操作的效果被限制在 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.36v1.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 虽好,但不是银弹:

  1. 特权容器不兼容:如果容器需要 privileged: true,User Namespaces 无法启用(两者语义冲突)
  2. HostPath 挂载需谨慎:HostPath 卷的文件权限是基于宿主机 UID 的,在 User Namespace 里可能遇到权限问题
  3. 某些 CNI 插件可能不兼容:特别是那些依赖宿主机网络命名空间特殊权限的 CNI
  4. 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。它的典型工作流程是:

  1. 写一个 HTTP Server,暴露一个 HTTPS 端点
  2. 为这个 Server 配置 TLS 证书(管理证书轮换)
  3. 写一个 MutatingWebhookConfiguration,告诉 API Server 什么时候调用你的 Webhook
  4. 确保这个 Server 高可用(否则 API Server 可能阻塞)
  5. 处理 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/-"
设置默认 valuespatchType: 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,一直是个老问题。

过去的选项有:

  1. 把模型文件打进主镜像 → 镜像体积爆炸,拉取时间长,版本管理困难
  2. 用 initContainer 去 S3/OSS 拉文件 → 需要额外的凭证管理,网络依赖强
  3. 用 ConfigMap/Secret → 大小限制(ConfigMap ≤ 1MiB),完全不可行
  4. 用 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 VolumeSourcePVC(共享存储)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_tcontainer_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)
10GB1,000~5 秒<1 秒
100GB100,000~2 分钟<3 秒
1TB1,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 修复或安全漏洞更新。现有部署仍然可以运行,但不再推荐用于生产环境

退役原因

  1. 维护团队资源不足:核心维护者数量不足以支撑一个关键基础设施项目
  2. 安全漏洞响应时间过长:多次高危 CVE 的修复周期过长
  3. 社区转向 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-NGINXGateway 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 GAAI/ML 模型分发新范式推荐评估,特别是有大模型推理场景的团队
SELinux 卷标签加速 GAPod 启动速度显著提升自动生效,无需额外配置
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 字

如果有任何问题或建议,欢迎在评论区留言讨论。

推荐文章

批量导入scv数据库
2024-11-17 05:07:51 +0800 CST
维护网站维护费一年多少钱?
2024-11-19 08:05:52 +0800 CST
Graphene:一个无敌的 Python 库!
2024-11-19 04:32:49 +0800 CST
用 Rust 玩转 Google Sheets API
2024-11-19 02:36:20 +0800 CST
程序员茄子在线接单