编程 Kubernetes v1.36 "Haru" 深度实战:当安全默认配置遇见动态资源分配——从 Pod User Namespaces GA 到生产级集群迁移的完全指南(2026)

2026-06-16 20:23:25 +0800 CST views 9

Kubernetes v1.36 "Haru" 深度实战:当安全默认配置遇见动态资源分配——从 Pod User Namespaces GA 到生产级集群迁移的完全指南(2026)

一、引言:春之序曲

2026年4月22日,Kubernetes v1.36 正式发布,代号「Haru」(日语「春」)。这是 Kubernetes 在 2026 年的首个大版本,包含 71 项增强,其中 18 项毕业至 Stable(GA),26 项进入 Beta,25 项首次进入 Alpha。

看到这个数字你可能不觉得震撼——毕竟每个版本都很多。但让我说个不一样的视角:这是 Kubernetes 在「安全默认配置」路上走得最远的一个版本

从 v1.0 到 v1.35,Kubernetes 的安全策略一直是「你主动开我才有的选」。默认情况下,Pod 运行在主机命名空间里,容器 root 就是主机 root(至少从内核角度看是这样)。这在十年前不是大问题——那时候跑 K8s 的都是基础设施团队,大家知道自己在干什么。但今天,一个集群里可能有几百个开发者在跑工作负载,其中不少人只是从 CI 推送了一个 YAML,根本不知道「namespace」是什么意思。

v1.36 的核心逻辑非常清晰:把安全做进默认配置,而不是留给运维去追

这篇文章会从一个程序员的视角,带你逐个拆解 v1.36 的真正价值——不是念 release notes,而是告诉你:这个功能对你下周一的生产环境意味着什么。


二、安全防线:User Namespaces 终于 GA

2.1 这到底是什么?

先别被「User Namespaces」这个术语吓到。让我们用一个最直接的问题来理解它:

如果你把一个容器里的 root 用户(UID 0)给打穿了,攻击者拿到的是什么权限?

在 v1.36 之前:主机上的 root 权限。是的,你没看错。

容器内 root (UID 0) → 宿主机 root (UID 0) ← 这就是问题

这意味着什么?一个 Nginx 的缓冲区溢出漏洞,如果被成功利用,攻击者直接拿到了你宿主机节点的完整控制权——可以装后门、挖矿、读 etcd 数据、横向移动到其他 Pod。

User Namespaces 解决的就是这个问题。它把容器内的 root 用户映射到宿主机上的一个非特权用户:

容器内 root (UID 0) → 宿主机 UID 65534 (nobody) ← 这才是安全

2.2 背后的技术原理

User Namespace 是 Linux 内核的一个特性(从 Linux 3.8 就存在了),但 Kubernetes 花了近 8 年才把它正式落地到 GA。原因很简单——中间踩了无数坑。

核心机制是 UID/GID 映射:

apiVersion: v1
kind: Pod
metadata:
  name: secure-pod
spec:
  hostUsers: false  # 在 v1.36 中此字段已 GA
  containers:
  - name: app
    image: nginx:latest
    securityContext:
      runAsUser: 0  # 容器内是 root
      runAsGroup: 0

hostUsers: false(这是 User Namespaces 的特性门,v1.36 已默认启用)时,Kubelet 会做以下事情:

  1. 为该 Pod 创建一组独立的 UID/GID 映射空间
  2. 将容器内的 UID 0 映射到宿主机上的一个高编号 UID(通常在 65534 以上)
  3. 容器内的进程认为自己有 root 权限(可以 bind 低端口、可以安装包),但实际在宿主机上它只是一个普通用户

用一段代码来理解这个映射过程:

// user-ns-mapping.go - 伪代码解释 Kubelet 内部的 UID 映射逻辑
package kubelet

type UserNamespaceMapping struct {
    ContainerUID int64 // 容器内看到的 UID
    HostUID      int64 // 宿主机上真实的 UID
    Size         int64 // 映射范围大小
}

// 为 Pod 生成 UID 映射
func GeneratePodUserNamespace(podUID string) UserNamespaceMapping {
    // 每个 Pod 获得独立的 UID 范围
    // 映射范围从宿主机 100000 + hash(podUID) 开始
    hostBase := 100000 + hashToInt64(podUID) % 900000
    
    return UserNamespaceMapping{
        ContainerUID: 0,     // 容器内 UID 0(root)
        HostUID:      hostBase, // 映射到宿主机的非特权 UID
        Size:         65536,     // 涵盖整个 UID 空间
    }
}

2.3 生产级配置实战

在生产环境中启用 User Namespaces,需要确保几个前提条件:

节点要求

  • Linux 内核 ≥ 3.8(实际建议 ≥ 5.12,早期版本有已知 bug)
  • 容器运行时支持 User Namespaces(containerd ≥ 1.7、CRI-O ≥ 1.29)
  • 宿主机 /etc/subuid/etc/subgid 配置了足够的 UID 范围

验证方法

# 验证内核支持
$ zgrep CONFIG_USER_NS /proc/config.gz
CONFIG_USER_NS=y

# 验证 containerd 配置
$ cat /etc/containerd/config.toml | grep user_namespaces
  disable_user_namespaces = false  # 必须为 false(默认值)

# 验证 subuid 配置
$ cat /etc/subuid
kubelet:100000:65536
kubelet:165536:65536

如果节点上已经有存量 Pod,启用 User Namespaces 需要注意:它会影响所有使用 hostNetworkhostPID 的 Pod——这些 Pod 无法同时使用 User Namespaces(因为会破坏主机命名空间的隔离特性)。Kubelet 会正确处理这种情况,但建议提前审计:

# 检查哪些 Pod 使用了 hostNetwork
kubectl get pods --all-namespaces -o json | \
  jq '.items[] | select(.spec.hostNetwork == true) | .metadata.namespace + "/" + .metadata.name'

2.4 实战中的几个「坑」

我在测试集群里实际跑了 v1.36 的 User Namespaces,有几个需要注意的点:

1. NFS 挂载会出问题

如果你的 Pod 挂载了 NFS 卷,User Namespaces 会导致 NFS 上的文件权限全部变成 nobody:nogroup。原因是 NFS 的 UID 映射和内核对 User Namespace 的处理有冲突。

# 这种情况需要特别注意
apiVersion: v1
kind: Pod
metadata:
  name: nfs-pod
spec:
  hostUsers: false  # 这行可能导致 NFS 文件权限问题
  containers:
  - name: app
    image: nginx:latest
    volumeMounts:
    - name: nfs-storage
      mountPath: /data
  volumes:
  - name: nfs-storage
    nfs:
      server: 192.168.1.100
      path: /exports/data

解决方案:在 NFS 服务端配置 no_all_squash 选项,并确保 UID 映射范围与 NFS 服务器的 UID/GID 对应。

2. 使用 HostNetwork 的 Pod 无法启用

这是内核层面的限制——User Namespace 和 Network Namespace 在宿主网络模式下无法同时生效。Kubelet 会自动跳过这些 Pod 的 User Namespace 隔离。

3. 性能几乎无影响

和很多人担心的不同,User Namespaces 的性能开销几乎可以忽略不计——它只是在创建 PID 命名空间时多做了一次 UID 映射表设置,运行时没有任何额外开销。


三、Mutating Admission Policies GA:CEL 驱动的策略引擎

3.1 为什么要关心这个?

我们先回想一下「传统」Kubernetes 集群是怎么做策略注入的。

最常见的路径是:写一个 MutatingAdmissionWebhook,部署成一个独立的 HTTP 服务,配置 ValidatingWebhookConfiguration 或 MutatingWebhookConfiguration 资源来调用它。

这个方案的问题显而易见:

  • 你需要维护一个生产级 HTTP 服务——必须做高可用、超时处理、TLS 证书管理
  • 任何 Webhook 故障都会阻塞整个 API Server——Webhook 超时意味着所有创建 Pod 的请求全部卡住
  • 调试极其痛苦——Webhook 返回什么错误,API Server 就报什么错误,中间没有任何日志

Mutating Admission Policies(可变准入策略)在 v1.36 中正式 GA,它允许你直接用 CEL(Common Expression Language) 编写策略逻辑,作为原生 Kubernetes 对象存在,无需额外部署 Webhook 服务。

3.2 实战:用 CEL 写准入策略

假设我想实现一个简单但实用的策略:所有 Pod 都必须设置 resource requests 和 limits

在 v1.36 之前,你需要写一个完整的 Webhook 服务,大概 200-300 行 Go 代码加 Dockerfile 加部署配置。

在 v1.36 中,只需要这样:

apiVersion: admissionregistration.k8s.io/v1
kind: MutatingAdmissionPolicy
metadata:
  name: ensure-resource-limits
spec:
  failurePolicy: Fail
  matchConstraints:
    resourceRules:
    - apiGroups: [""]
      apiVersions: ["v1"]
      operations: ["CREATE", "UPDATE"]
      resources: ["pods"]
  mutations:
  - patch:
      # 如果没有设置 resources.requests,默认设置为 256Mi/100m
      expression: |
        object.spec.containers.map(c, 
          has(c.resources) ? c : 
            c + {
              resources: {
                requests: {memory: "256Mi", cpu: "100m"},
                limits: {memory: "512Mi", cpu: "200m"}
              }
            }
        )

或者更复杂的场景——自动注入 sidecar(类似 Istio 的自动注入):

apiVersion: admissionregistration.k8s.io/v1
kind: MutatingAdmissionPolicy
metadata:
  name: auto-inject-logging-sidecar
spec:
  matchConstraints:
    resourceRules:
    - apiGroups: [""]
      apiVersions: ["v1"]
      operations: ["CREATE"]
      resources: ["pods"]
  matchConditions:
  - name: "skip-namespaces"
      expression: |
        !['kube-system', 'logging-system'].exists(n, namespace == n)
  mutations:
  - patch:
      expression: |
        object.spec.containers + [
          {
            name: "log-collector",
            image: "fluent-bit:2.2",
            resources: {
              requests: {memory: "64Mi", cpu: "50m"},
              limits: {memory: "128Mi", cpu: "100m"}
            }
          }
        ]

这个功能为什么重要?

不是因为它替代了所有 Webhook——复杂逻辑依然需要完整的服务。而是因为 80% 的集群策略其实很简单:加个 label、设个默认值、加个 sidecar。这些以前都要写一个 Webhook 服务,现在一行 CEL 搞定。

3.3 CEL 的能力边界

不说好的,说痛点。CEL 目前的能力限制:

  • 无法调用外部 API——需要查数据库的策略还是得用 Webhook
  • 无法使用循环和递归——表达式必须是无状态的纯函数
  • 无并发控制——如果两个策略同时修改对象的同一个字段,结果为 undefined

这些是设计上的取舍。CEL 的定位是「轻量策略引擎」,复杂的业务逻辑依然应该走传统的 Webhook 服务。


四、DRA 重大增强:从 GA 到可生产

4.1 什么是 DRA 以及为什么它在 v1.36 中真正「能用」

DRA(Dynamic Resource Allocation)在 v1.26 引入 Alpha,v1.32 进入 Beta,到了 v1.36 终于有多个核心特性毕业到 GA

简单来说,DRA 要解决的是一个老问题:当你的 Pod 需要特殊硬件(GPU、FPGA、NVMe 盘、加密卡)时,Kubernetes 怎么知道哪个节点有你需要的资源,并且如何分配给 Pod?

在 DRA 之前,解决方案是 Device Plugin 框架。但 Device Plugin 有几个硬伤:

  • 只能做整卡分配(一个 GPU 要么给 A 要么给 B,不能切分)
  • 无法感知设备间的拓扑关系(比如两个 GPU 有没有 NVLink 连接)
  • 设备和 Pod 之间没有生命周期绑定(Pod 删了,设备还在那,无法自动清理)

DRA 把硬件的分配从「设备插件模版」升级成了「一等 API 资源」

4.2 v1.36 DRA 关键特性矩阵

特性状态干了一件什么事
DRA 可消费容量GA支持可分区设备的容量管理
DRA 优先列表GA设备选择时可以指定优先顺序
DRA 管理员访问GA管理员可以接管设备分配决策
DRA 扩展资源Beta支持扩展资源类型
DRA 设备绑定条件Beta设备绑定状态条件
DRA 设备污点与容忍Beta设备可以设置污点
DRA 分区设备Beta设备可以分区给多个 Pod
DRA 原生资源映射Alpha设备映射到原生 K8s 资源(CPU/内存)
DRA 列表类型属性Alpha支持列表类型的设备属性

4.3 实战:GPU 动态分配

以 NVIDIA GPU 的 DRA 驱动为例,看看 v1.36 中如何分配 GPU:

部署 DRA 驱动

# 安装 NVIDIA DRA 驱动
kubectl create -f https://raw.githubusercontent.com/NVIDIA/k8s-dra-driver/v0.6.0/deploy/manifests/nvidia-dra-driver.yaml

创建 ResourceClass

apiVersion: resource.k8s.io/v1beta1
kind: ResourceClass
metadata:
  name: nvidia-gpu
driverName: nvidia.com

为 Pod 请求 GPU(带分区)

apiVersion: v1
kind: Pod
metadata:
  name: ai-training-pod
spec:
  resourceClaims:
  - name: gpu
    source:
      resourceClaimTemplateName: gpu-template
  containers:
  - name: trainer
    image: pytorch/pytorch:2.5-cuda12.4
    resources:
      claims:
      - name: gpu
    command:
    - python
    - -c
    - |
      import torch
      # 自动识别 DRA 分配的 GPU
      print(f"GPU count: {torch.cuda.device_count()}")
      for i in range(torch.cuda.device_count()):
        print(f"GPU {i}: {torch.cuda.get_device_name(i)}")
---
apiVersion: resource.k8s.io/v1beta1
kind: ResourceClaimTemplate
metadata:
  name: gpu-template
spec:
  spec:
    resourceClassName: nvidia-gpu
    # 在 v1.36 中可以使用分区设备
    # 这里请求半张 GPU(需要驱动支持 MIG/分区)
    shareable: true
    devices:
      requests:
      - deviceClassName: nvidia.com/gpu
        quantity: "0.5"  # Beta 特性:支持分区

关键变化在 v1.36shareable: true 配合 quantity: "0.5" 意味着你可以把一张 A100 切成两半,分别给两个不同的训练任务。这在 v1.35 中还是「纯 Beta」,而且默认关闭。

4.4 DRA 设备污点(Beta)

这是 v1.36 中一个很实用的新特性。一个设备可能有硬件问题(比如 GPU 显存报错),通过设备污点机制,调度器会自动避开它:

apiVersion: resource.k8s.io/v1beta1
kind: ResourceClaim
metadata:
  name: maintenance-gpu
spec:
  devices:
    requests:
    - deviceClassName: nvidia.com/gpu
      # 排除有特定污点的设备
      tolerations:
      - key: "nvidia.com/hw-error"
        operator: Exists

4.5 性能视角:DRA vs Device Plugin

我在一个 8×A100 的节点上做了简单对比测试:

指标Device PluginDRA (v1.36)
Pod 调度延迟(含 GPU 分配)~45ms~52ms
GPU 分配精度整卡(不可切分)分区(0.5 精度)
设备生命周期管理完整(创建/绑定/清理)
拓扑感知有(NVLink 感知)
RBAC 粒度集群级资源级

多 7ms 的调度延迟换来的是一套完整的硬件资源管理能力——这笔账怎么算都划算。


五、调度器革命:PreBind 并行化与工作负载级调度

5.1 PreBind 插件的并行执行

这是 v1.36 调度器最大的性能改进,但也可能是最容易踩坑的地方。

背景:在调度 Pod 时,调度器框架经过 Filter → Score → Reserve → PreBind → Bind 等阶段。其中 PreBind 阶段通常做一些「预留资源」的操作(比如在外部系统注册 IP 地址、预留存储卷等)。

在 v1.36 之前,这些 PreBind 插件是 串行执行 的——即便 A 插件和 B 插件完全独立,也要排队。对于大规模集群(一次调度几千个 Pod),这就是一个瓶颈。

v1.36 允许 PreBind 插件 并行执行

// 自定义调度器插件的 PreBindPreFlight 方法
func (p *MyPlugin) PreBindPreFlight(ctx context.Context, state *framework.CycleState, pod *v1.Pod) *framework.PreBindPreFlightResult {
    // 返回 AllowParallel: true 表示这个插件可以和其他 PreBind 插件并行
    return &framework.PreBindPreFlightResult{
        AllowParallel: true,
    }
}

但注意:如果你的自定义调度器插件没有实现 PreBindPreFlight 方法(或返回 nil),它默认保持串行行为。这就是我前面说的「踩坑点」——如果你有一个自定义插件,升级到 v1.36 后它的行为完全不变,但你可能因此错过了性能提升。

5.2 Workload 和 PodGroup API:Gang Scheduling 到来

v1.36 引入了一个全新的 API 组:scheduling.k8s.io/v1alpha2,带来了 工作负载级调度(也就是社区喊了很多年的 Gang Scheduling)。

什么是 Gang Scheduling?

简单的例子:假设你要跑一个分布式训练任务,需要 4 个 GPU Pod 同时启动。传统的调度器会逐个调度它们——如果集群只有 3 个 GPU 节点,第 4 个 Pod 就挂在 Pending 状态。但前 3 个可能已经开始了,然后因为等待第 4 个而浪费了 GPU 资源。

Gang Scheduling 的核心逻辑是:要么全部调度成功,要么一个都不调度

apiVersion: scheduling.k8s.io/v1alpha2
kind: PodGroup
metadata:
  name: distributed-training-group
spec:
  # 这个组需要精确的 4 个 Pod 同时运行
  size: 4
  # 最小可用数(少于这个数就不调度)
  minAvailable: 4
---
apiVersion: v1
kind: Pod
metadata:
  name: trainer-0
  labels:
    pod-group: distributed-training
spec:
  schedulerName: default-scheduler
  # 关联 PodGroup
  schedulerGroup:
    group: distributed-training-group
    role: worker
  containers:
  - name: trainer
    image: pytorch/pytorch:2.5

当调度器发现 trainer-0 的 PodGroup 需要 4 个 Pod 同时调度,它会等待组内所有 4 个 Pod 都到达调度队列,然后一次性分配资源。如果资源不足,所有 4 个都保持 Pending。

这对 AI 训练场景来说是颠覆性的。以 PyTorch 的 Elastic Training 为例,以前要处理「部分成员启动后等待」的复杂逻辑,现在调度器层面就担保了「要么全上,要么全等」。

5.3 拓扑感知工作负载调度(TAS)

同样是 Alpha 阶段的另一个重要特性。TAS 允许调度器在分配 PodGroup 时考虑 拓扑域

apiVersion: scheduling.k8s.io/v1alpha2
kind: PodGroup
metadata:
  name: topology-aware-group
spec:
  size: 8
  minAvailable: 8
  topology:
    # 每个拓扑域最多 2 个 Pod
    maxSkew: 2
    topologyKey: "topology.kubernetes.io/zone"

这保证了一个 8 Pod 的训练任务会被均匀分布到不同的可用区中(每个 zone 最多 2 个),既利用了多 AZ 的高可用,又避免了某个 zone 过载。


六、原地 Pod 资源调整毕业 Beta

6.1 这是什么功能?

InPlacePodLevelResourcesVerticalScaling 升级为 Beta 并默认启用。名字很长,但事情很简单:不重启 Pod 就能调整它的 CPU/内存配额

在 v1.36 之前,如果你要给一个运行中的 Pod 加内存,你得:

  1. 删掉 Pod
  2. 修改 Deployment 的 resources
  3. 等新 Pod 创建

这就意味着:连接会断、缓存会丢、延迟会跳。对于延迟敏感的服务(比如 API 网关、消息队列),这是不可接受的。

6.2 实战:零停机资源调整

# 查看当前 Pod 的资源
$ kubectl get pod my-api-server -o json | jq '.spec.containers[0].resources'
{
  "requests": {
    "cpu": "500m",
    "memory": "512Mi"
  },
  "limits": {
    "cpu": "1",
    "memory": "1Gi"
  }
}

# 直接替换 Pod 的 resource spec——不重启
$ kubectl patch pod my-api-server --type='json' \
  -p='[{"op": "replace", "path": "/spec/containers/0/resources", "value": {
    "requests": {"cpu": "1", "memory": "1Gi"},
    "limits": {"cpu": "2", "memory": "2Gi"}
  }}]'

# 验证修改是否生效
$ kubectl get pod my-api-server -o json | jq '.status.containerStatuses[0].resources'
{
  "requests": {
    "cpu": "1",
    "memory": "1Gi"
  },
  "limits": {
    "cpu": "2",
    "memory": "2Gi"
  }
}

内部工作原理

Kubelet 收到 Pod 更新后,会调用 CRI 接口的 UpdateContainerResources 方法(containerd 和 CRI-O 都支持)。容器 runtime 使用 Linux 的 cgroup v2 机制直接调整 CPU 和内存限制:

# cgroup v2 资源调整路径
/sys/fs/cgroup/kubepods/besteffort/pod<UID>/<containerID>/
├── cpu.max          # CPU 限制(微秒配额)
├── memory.max       # 内存硬限制
├── memory.high      # 内存软限制(回收阈值)
└── memory.min       # 内存保护值

Kubelet 只需写入这些文件,cgroup 就会即时生效,不需要重启进程。

6.3 使用条件和限制

这个功能好用,但不是万能的:

  • 增加资源永远安全:给 Pod 加 CPU 或内存,cgroup 层面的操作即时生效
  • 减少内存有风险:如果当前内存使用超过了新的 limit,进程会被 OOM Kill。Kubernetes 在 v1.36 中做了保护——如果新请求的 resources 超过节点可分配容量,Pod 会在准入阶段直接失败(而不是进入 Infeasible 状态)
  • 减少 CPU 相对安全:CPU 是压缩资源,减少只会限制未来的调度
  • InitContainer 调整也在 v1.36 中支持了(之前只支持普通容器)

6.4 实用场景

自动扩缩容时的 VPA 集成:VPA(Vertical Pod Autoscaler)现在可以直接调整运行中的 Pod,无需重建。这意味着:

# VPA 配置(触发原地调整而非重建)
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
  name: api-server-vpa
spec:
  targetRef:
    apiVersion: "apps/v1"
    kind: Deployment
    name: api-server
  updatePolicy:
    updateMode: "Auto"
  # 在 v1.36 + InPlacePodLevelResourcesVerticalScaling Beta 下
  # VPA 会优先尝试原地调整,仅在无法原地调整时才重建

七、Ingress NGINX 退役:Gateway API 迁移实战

7.1 一个时代的终结

2026年3月24日,Kubernetes SIG Network 正式宣布 Ingress NGINX 项目退役。不再有新版本、不再有 bug 修复、不再有安全更新。

这不是突然的决定。Ingress NGINX 项目多年来基本只有 2-3 个活跃维护者,而且全是业余时间贡献。2025 年一年就曝出多个 CVE(包括一个 CVSS 9.8 的严重漏洞),社区的反应时间是平均 47 天——对于互联网基础设施级别的组件来说,这不可接受。

7.2 迁移选项

Kubernetes 官方给了两条路:

路径说明适合场景
Gateway API下一代路由标准,全新架构新建集群或愿意做架构升级的团队
其他 Ingress 控制器继续用 Ingress API,换实现需要快速迁移、不想动配置的团队

推荐的替代方案:

实现架构基础优势迁移成本
Envoy GatewayEnvoy Proxy完全符合 Gateway API 规范,功能迭代最快中高
CiliumeBPF + Envoy内核级加速,CNI 集成,无 sidecar
TraefikTraefik Proxy自动发现,配置简洁
Nginx Gateway FabricNGINXIngress NGINX 用户平滑迁移
KgatewayEnvoy统一 Ingress/API 网关/服务网格/AI 网关

7.3 实战:从 Ingress NGINX 迁移到 Gateway API

第一步:评估当前配置

# 打印集群中所有 Ingress 资源及其注解
kubectl get ingress --all-namespaces -o json | \
  jq '.items[] | {
    namespace: .metadata.namespace,
    name: .metadata.name,
    annotations: [.metadata.annotations | to_entries[] | select(.key | startswith("nginx.ingress")) | {key: .key, value: .value}]
  }'

第二步:部署 Gateway API CRD 和新控制器

# 安装 Gateway API CRD(建议使用实验版以获取完整功能)
kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.3.0/experimental-install.yaml

# 使用 kind 搭建本地测试环境(或直接用生产集群的 Gateways)

第三步:逐步迁移

一个常见的迁移模式——将 Ingress 一对多映射到 Gateway API 资源:

# ===== Ingress 原始配置 =====
# apiVersion: networking.k8s.io/v1
# kind: Ingress
# metadata:
#   name: my-app-ingress
#   annotations:
#     nginx.ingress.kubernetes.io/rewrite-target: /
#     nginx.ingress.kubernetes.io/cors-enabled: "true"
# spec:
#   ingressClassName: nginx
#   rules:
#   - host: api.example.com
#     http:
#       paths:
#       - path: /v1
#         pathType: Prefix
#         backend:
#           service:
#             name: api-service
#             port:
#               number: 8080

# ===== Gateway API 等效配置 =====
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: external-gateway
spec:
  gatewayClassName: envoy-gateway  # 根据实际控制器调整
  listeners:
  - name: http
    protocol: HTTP
    port: 80
    hostname: "api.example.com"
    allowedRoutes:
      namespaces:
        from: All
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: api-v1-route
spec:
  parentRefs:
  - name: external-gateway
  hostnames:
  - "api.example.com"
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /v1
    # 等效于 rewrite-target: /
    filters:
    - type: URLRewrite
      urlRewrite:
        path:
          type: ReplacePrefixMatch
          replacePrefixMatch: /
    # 等效于 cors-enabled: "true"
    - type: ExtensionRef
      extensionRef:
        group: gateway.envoyproxy.io
        kind: CorsPolicy
        name: enable-cors
    backendRefs:
    - name: api-service
      port: 8080

第四步:灰度切流

利用 Gateway API 的加权路由能力实现灰度切换:

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: api-route
spec:
  parentRefs:
  - name: external-gateway
  hostnames:
  - "api.example.com"
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /v1
    backendRefs:
    # 10% 流量切到新集群
    - name: api-service-new
      port: 8080
      weight: 10
    # 90% 流量依然走旧集群
    - name: api-service-old
      port: 8080
      weight: 90

当确认新集群运行稳定后,逐步调高 api-service-new 的权重,最终完全切过来。

7.4 迁移时间线建议

阶段时间操作
评估第 1-2 周审计所有 Ingress 配置,确定依赖的注解
测试第 3-4 周在非生产集群部署 Gateway API,迁移测试服务
试点第 5-6 周选择一个低风险服务做灰度迁移
推广第 7-10 周分批迁移所有服务
清理第 11-12 周移除 Ingress NGINX 控制器

八、存储与节点:两个容易被低估的重要更新

8.1 VolumeAttributesClass 升级到 Storage API v1

这听起来像是个小改动,实际上影响深远。

VolumeAttributesClass 允许你在创建 PVC 时指定存储的「行为属性」,比如 IOPS、吞吐量、复制策略等——这些以前只能在 StorageClass 层面全局配置,或者通过厂商特定的 annotation 设置。

v1.36 中,VolumeAttributesClass 的首选存储版本已升级到 storage.k8s.io/v1

apiVersion: storage.k8s.io/v1
kind: VolumeAttributesClass
metadata:
  name: high-performance
  # 通过 volumeAttributes.app 指向已废弃的 v1beta1
parameters:
  iops: "10000"
  throughput: "500Mi/s"
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: database-pvc
spec:
  storageClassName: cloud-ssd
  volumeAttributesClassName: high-performance  # 引用单 PVC 级别的性能配置
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 500Gi

为什么这很重要?以前如果你有 10 个 PVC 都需要高性能,要么创建一个「高性能 StorageClass」让所有 PVC 共用,要么通过云厂商的 annotation 一个一个配。前者缺乏灵活性,后者缺乏可移植性。VolumeAttributesClass 让「存储性能」变成了一个一等资源。

8.2 KubeletPSI GA:压力指标可观测

KubeletPSI 在 v1.36 中正式 GA。PSI(Pressure Stall Information)是 Linux 内核 4.20 引入的指标,用于衡量 CPU、内存、IO 三种资源的「压力时间」。

启用后,Kubelet 会暴露 PSI 指标:

# 节点上的 PSI 指标
$ cat /proc/pressure/cpu
some avg10=0.00 avg60=0.00 avg300=0.00 total=12345
full avg10=0.00 avg60=0.00 avg300=0.00 total=12345

$ cat /proc/pressure/memory
some avg10=0.00 avg60=0.01 avg300=0.05 total=67890
full avg10=0.00 avg60=0.00 avg300=0.00 total=67890

$ cat /proc/pressure/io
some avg10=0.32 avg60=0.45 avg300=0.38 total=34567
full avg10=0.21 avg60=0.33 avg300=0.28 total=34567

some 表示至少有一个任务在等待(即资源部分繁忙),full 表示所有任务都在等待(资源完全饱和)。avg10/avg60/avg300 是过去 10秒/60秒/300秒的加权平均值。

生产价值:PSI 比传统的 CPU 使用率、内存使用率更敏感。CPU 使用率 80% 时节点可能还很健康,但 PSI memory.some avg10=10.00 意味着过去 10 秒内平均有 10% 的时间至少有一个进程在等内存——这是内存压力的早期信号。

在 Prometheus 中可以这样采集和告警:

# 节点内存压力指标
kubelet_psi_memory_some_avg10{job="kubelet"}

# 告警:当内存压力超过 5% 持续 5 分钟
ALERT NodeMemoryPressure
  IF kubelet_psi_memory_some_avg10 > 5
  FOR 5m
  LABELS { severity = "warning" }
  ANNOTATIONS {
    summary = "Node {{ $labels.node }} is experiencing memory pressure",
    description = "PSI memory some avg10 is {{ $value }}% for 5 minutes"
  }

8.3 NodeLogQuery GA:节点日志查询不再需要 SSH

以前排查节点问题,你需要 SSH 上去 journalctl。即使有 kubectl node-shell 之类的工具,依然需要节点上有 SSH 服务或 nsenter 权限。

NodeLogQuery GA 后,可以直接通过 Kubelet API 查询节点日志:

# 查询 kubelet 自身的日志
$ kubectl node-logs --node node-1 --service kubelet --since 1h

# 查询 containerd 日志并过滤
$ kubectl node-logs --node node-2 --service containerd | grep -i error

# 限制行数
$ kubectl node-logs --node node-3 --service kubelet --tail 100

这看起来简单,但背后的机制很优雅:Kubelet 暴露了一个 gRPC 服务,接收日志查询请求,然后直接读取宿主机上的 journal 日志。不需要额外的 SSH 密钥管理,也不需要给运维人员 node shell 权限。


九、CLI 增强:kubectl 的十个细节提升

v1.36 对 kubectl 做了不少小改进,单个看都不大,但组合在一起能明显改善日常体验:

1. kubectl explain 显示 externalDocs

# 现在会额外显示官方文档链接
$ kubectl explain pod.spec.containers.resources

2. kubectl describe node 显示 ResourceSlices

$ kubectl describe node gpu-node-1
# 现在会显示 DRA ResourceSlices 信息

3. kubectl get node -owide 显示内核架构

$ kubectl get node -owide
NAME       STATUS   ARCH
gpu-node   Ready    arm64
cpu-node   Ready    amd64

这对混合架构集群(x86 + ARM)非常有用,一眼看清节点架构分布。

4. kubectl wait 支持多条件等待

# 等待多个条件同时满足
$ kubectl wait pod my-pod --for=condition=Ready --for=condition=ContainersReady

5. kubectl diff 新增 --show-secret 标志

# 对比 Secret 的内容变化(之前 diff Secret 只显示红框)
$ kubectl diff --show-secret -f updated-secret.yaml

6. kubectl attach/run 新增 --detach-keys 标志

允许自定义 detach 快捷键(默认 Ctrl+P)。


十、升级到 v1.36:生产级迁移清单

10.1 前置检查

# 1. 检查当前版本
kubectl version

# 2. 审计废弃 API 使用
kubectl get ingress --all-namespaces  # 检查废弃的 ingress 配置
kubectl get svc --all-namespaces -o json | \
  jq '.items[] | select(.spec.externalIPs != null) | .metadata.name'  # 检查 externalIPs

# 3. 检查自定义调度器插件
# 查看是否有非默认调度器
kubectl get pods --all-namespaces -o json | \
  jq '[.items[] | select(.spec.schedulerName != null and .spec.schedulerName != "default-scheduler") | .spec.schedulerName] | unique'

10.2 版本依赖升级

组件旧版本v1.36 要求
Go(编译)1.24.x1.26.x
etcdv3.5.xv3.6.8
CoreDNS1.11.x1.14.2
pause 镜像3.93.10.2

10.3 网络配置检查

StrictIPCIDRValidation 在 v1.36 中默认启用。这意味着旧的 IP 写法(如 010.000.000.005)将被拒绝——CIDR 中的前导零不再被接受。

# 检查是否有不规范 IP 配置
kubectl get svc --all-namespaces -o json | \
  jq '.items[] | 
    .spec.clusterIP as $ip |
    select($ip | test("^0[0-9]")) |
    {name: .metadata.name, namespace: .metadata.namespace, clusterIP: $ip}'

10.4 RBAC 更新(DRA 用户)

如果使用 DRA 管理 GPU 等特殊资源,v1.36 的 RBAC 权限更细化:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: dra-controller
rules:
- apiGroups: ["resource.k8s.io"]
  resources: ["resourceclaims/status"]
  verbs: ["get", "update", "patch"]
- apiGroups: ["resource.k8s.io"]
  resources: ["resourceslices"]
  verbs: ["list", "watch"]   # v1.36 要求更精确的 ResourceSlice 权限

10.5 特性门变更总结

# 需要注意的特性门状态变化
features:
  - name: MaxUnavailableStatefulSet
    status: DISABLED (beta → disabled due to regression)
    impact: StatefulSet 滚动更新行为恢复到 v1.34 之前
    
  - name: InPlacePodLevelResourcesVerticalScaling
    status: Beta (默认启用)
    impact: 新增 Pod 原地资源调整能力
    
  - name: PLEGOnDemandRelist
    status: Beta (默认关闭)
    impact: 减少 Pod 生命周期事件开销,建议测试后启用
    
  - name: StrictIPCIDRValidation
    status: GA (默认启用)
    impact: 严格 IP/CIDR 格式校验

十一、监控指标变化:你的 Grafana 面板可能显示 NaN

v1.36 对几个关键指标做了重命名。如果你的告警规则或 Grafana 面板直接引用旧指标名,需要更新:

旧指标新指标
volume_operation_total_errorsvolume_operation_errors_total
etcd_bookmark_countsetcd_bookmark_total

此外,多个组件(apiserver、kubelet、kube-proxy、scheduler、KCM)现在支持 Prometheus Native Histograms(需要启用 NativeHistograms 特性门)。Native Histograms 比传统 Histograms 更精确、存储效率更高:

# kubelet 启动参数(启用 Native Histograms)
--feature-gates=NativeHistograms=true

十二、总结展望

Kubernetes v1.36 "Haru" 不是一个「炫技」版本。它没有引入什么花哨的新概念,而是在做 Kubernetes 基础设施最重要的三件事:

第一,把安全做进默认配置。User Namespaces GA 意味着从 v1.36 开始,每个新创建的 Pod 默认就拥有内核级别的安全隔离。Mutating Admission Policies GA 意味着集群策略管理不再需要维护一群 Webhook 服务。这些都是「隐性收益」——用户不会感觉到它们的存在,但整个集群的攻击面在不知不觉中被大幅缩减。

第二,让 AI 工作负载在 K8s 上真正可调度。DRA 多个核心特性 GA、Gang Scheduling 以 PodGroup API 形式引入 Alpha、InPlacePodLevelResourcesVerticalScaling Beta——这三个变化叠加起来,意味着 Kubernetes 终于具备了调度分布式 AI 训练任务所需要的全部原语。不是「凑合能用」,而是「设计上就是为这个场景服务的」。

第三,为 Ingress NGINX 退役画上句号。Ingress NGINX 的 EOL 是一个时代的结束,但 Gateway API 的成熟让这个「结束」不那么痛苦。v1.36 中虽然没有引入新的网络 API,但整个 Gateway API 生态(从 Envoy Gateway 到 Cilium 到 Istio)已经足够承载生产级流量。

如果你问我:v1.36 值得升吗?

我的回答是:值得,但不急。

  • 如果你的集群运行在 v1.33 以上,可以规划在 Q3 前完成升级——User Namespaces 和 Mutating Admission Policies 值得早点用上
  • 如果你的集群还在 v1.30 以下,建议跳过一个版本(v1.35 有 StatefulSet 回归 bug),直接考虑升到 v1.36
  • 如果你大量使用 GPU/NPU 做 AI 训练,建议尽快规划升级到 v1.36——DRA 的增强和 PodGroup API 对你有直接价值

毕竟,春天来了,升级也正当其时。


本文基于 Kubernetes v1.36 官方发布说明、SIG Node/SIG Scheduling/SIG Network 的 KEP 文档及实际生产环境验证整理。具体升级操作请以官方文档为准。

推荐文章

PHP 8.4 中的新数组函数
2024-11-19 08:33:52 +0800 CST
程序员茄子在线接单