编程 Kubernetes 1.36 深度实战:从 DRA 可切分设备到 Agent Sandbox,云原生调度器如何重新定义 AI 时代的硬件分配边界

2026-05-04 09:53:26 +0800 CST views 4

Kubernetes 1.36 深度实战:从 DRA 可切分设备到 Agent Sandbox,云原生调度器如何重新定义 AI 时代的硬件分配边界

引言:当 Kubernetes 遇上 AI 工作负载

Kubernetes 在 2026 年走到了一个关键岔口。过去十年,它的核心调度模型几乎没变——CPU、内存、存储,三板斧打天下。但 AI 时代的硬件图谱完全不同了:GPU、FPGA、ASIC、NPU、RDMA 网卡……每一类设备都有自己的分配逻辑和共享语义,传统的 Device Plugin 机制已经力不从心。

2026 年 4 月底,Kubernetes v1.36(代号「晴」,取自葛饰北斋《富嶽三十六景》的日式意象)正式发布。这个版本的最大看点不是某个炫酷的新功能,而是一整套围绕「动态资源分配」的架构升级——DRA 从 Alpha 到 Stable 再到可切分、可污点、可共享容量,调度器从「按整数分配设备」进化到「按容量精细切片」。

与此同时,Agent Sandbox 的出现标志着 Kubernetes 开始正式面对 AI Agent 的运行时安全问题——这不再是「跑个容器」那么简单。

本文将从架构设计、核心原理、代码实战、性能优化四个维度,深度拆解 Kubernetes 1.36 的关键变更,帮你理解这些变化背后的技术逻辑,以及如何在实际生产中落地。


一、Kubernetes 1.36 变更全景:不只是版本号递增

1.1 版本发布与升级节奏

Kubernetes v1.36 于 2026 年 4 月底正式发布,遵循每 4 个月一个大版本的节奏。但这个版本的特殊性在于:它同时包含了多项 GA(正式可用)和 Beta(默认启用)特性,以及若干破坏性变更。

核心变更矩阵:

变更类型内容影响程度状态
移除gitRepo 卷驱动永久移除
弃用Service.spec.externalIPs计划 v1.43 移除
退役Ingress NGINX不再维护
GASELinux 卷标签优化默认启用
GADRA 按优先级排序列表默认启用
GADRA 管理性质访问(Admin Access)默认启用
BetaDRA 设备污点与容忍度默认启用
BetaDRA 可切分设备默认启用
BetaDRA 可消耗容量默认启用
BetaDRA 扩展资源分配默认启用
Beta设备健康监控默认启用

这个矩阵告诉我们一个关键信息:Kubernetes 正在把 AI 硬件分配从「补丁式支持」推向「一等公民」。 DRA 的多项子特性在 v1.36 集体进入 Beta/GA,这不是巧合,而是社区对 AI 工作负载需求的系统性回应。

1.2 升级前必须做的五件事

在升级到 v1.36 之前,别急着 kubeadm upgrade。先跑一遍这个检查清单:

#!/bin/bash
# k8s-v136-precheck.sh — Kubernetes v1.36 升级前检查脚本

set -euo pipefail

echo "=== Kubernetes v1.36 升级前检查 ==="
echo ""

# [1/5] 检查 gitRepo 卷使用 — v1.36 永久移除
echo "[1/5] 检查 gitRepo 卷使用情况..."
GITREPO_COUNT=$(kubectl get pods --all-namespaces -o json | \
  jq '[.items[] | select(.spec.volumes[]?.gitRepo != null)] | length')
if [ "$GITREPO_COUNT" -gt 0 ]; then
  echo "  ⚠️  发现 $GITREPO_COUNT 个 Pod 使用 gitRepo 卷,必须先迁移!"
  kubectl get pods --all-namespaces -o json | \
    jq -r '.items[] | select(.spec.volumes[]?.gitRepo != null) |
    "\(.metadata.namespace)/\(.metadata.name)"'
else
  echo "  ✅ 无 gitRepo 卷使用"
fi
echo ""

# [2/5] 检查 externalIPs 使用 — v1.36 弃用警告
echo "[2/5] 检查 externalIPs 使用情况..."
EXTIP_COUNT=$(kubectl get svc --all-namespaces -o json | \
  jq '[.items[] | select(.spec.externalIPs != null and .spec.externalIPs != [])] | length')
if [ "$EXTIP_COUNT" -gt 0 ]; then
  echo "  ⚠️  发现 $EXTIP_COUNT 个 Service 使用 externalIPs,v1.43 将移除"
  kubectl get svc --all-namespaces -o json | \
    jq -r '.items[] | select(.spec.externalIPs != null and .spec.externalIPs != []) |
    "\(.metadata.namespace)/\(.metadata.name): \(.spec.externalIPs)"'
else
  echo "  ✅ 无 externalIPs 使用"
fi
echo ""

# [3/5] 检查 Ingress NGINX — 已退役
echo "[3/5] 检查 Ingress NGINX 部署..."
INGRESS_NS=$(kubectl get namespaces -o json | \
  jq -r '.items[] | select(.metadata.name | test("ingress-nginx")) | .metadata.name')
if [ -n "$INGRESS_NS" ]; then
  echo "  ⚠️  发现 Ingress NGINX 命名空间: $INGRESS_NS"
  echo "  ⚠️  Ingress NGINX 已于 2026-03-24 退役,建议迁移到 Gateway API"
  kubectl get deployments -n "$INGRESS_NS" -o wide 2>/dev/null || true
else
  echo "  ✅ 未发现 Ingress NGINX 部署"
fi
echo ""

# [4/5] 检查 SELinux 状态
echo "[4/5] 检查节点 SELinux 状态..."
for node in $(kubectl get nodes -o name); do
  SESTATUS=$(kubectl debug "$node" --image=alpine -- cat /etc/selinux/config 2>/dev/null | \
    grep "^SELINUX=" | head -1 || echo "unknown")
  echo "  $node: $SESTATUS"
done
echo ""

# [5/5] 当前版本
echo "[5/5] 当前 Kubernetes 版本..."
kubectl version -o yaml 2>/dev/null | grep -E "gitVersion|minor|major" || \
  kubectl version --short 2>/dev/null || true

echo ""
echo "=== 检查完成 ==="

升级顺序建议:

  1. 先在 staging 环境做完整验证
  2. 备份 etcd(etcdctl snapshot save
  3. 升级控制平面(kube-apiserver → kube-controller-manager → kube-scheduler)
  4. 逐节点 drain + 升级 kubelet + uncordon
  5. 执行应用层迁移(Ingress 配置、gitRepo 替换等)

二、DRA 深度解析:从设备插件到动态资源分配的范式跃迁

2.1 为什么 Device Plugin 不够用了?

在 DRA 出现之前,Kubernetes 通过 Device Plugin 机制支持异构硬件。Device Plugin 的工作方式简单粗暴:向 kubelet 注册设备列表,调度器按整数数量分配。

这个模型有一个根本缺陷——它把所有设备都当成不可分割的原子单位

实际场景中的问题:

# 问题 1: 一个 80GB 的 A100 GPU,我只想用 20GB 跑推理
# Device Plugin: 抱歉,要么用整张卡,要么别用

# 问题 2: 3 个低优先级任务共享一张 GPU,1 个高优先级任务来了
# Device Plugin: 抱歉,不支持抢占,请手动驱逐

# 问题 3: 某张 GPU 的显存出了 ECC 错误,我想标记它
# Device Plugin: 抱歉,没有污点机制,只能手动删除设备资源

DRA 的设计哲学完全不同。它引入了四个核心 API 对象:

  • ResourceSlice:设备驱动发布的设备清单(类比 PV)
  • DeviceClass:设备分类和选择策略(类比 StorageClass)
  • ResourceClaim:对设备的分配请求(类比 PVC)
  • ResourceClaimTemplate:为每个 Pod 自动生成 ResourceClaim 的模板

这个设计直接借鉴了存储子系统的成功经验——动态卷制备(Dynamic Provisioning)。

2.2 DRA 工作流全链路拆解

让我用一个完整的 GPU 分配流程来展示 DRA 的工作机制:

第一步:设备驱动注册设备(ResourceSlice)

# 由 NVIDIA DRA 驱动自动创建
apiVersion: resource.k8s.io/v1
kind: ResourceSlice
metadata:
  name: nvidia-gpu-node-01
spec:
  driver: gpu.nvidia.com
  nodeName: worker-gpu-01   # 设备挂载在这个节点
  pool:
    name: nvidia-gpu-pool
    generation: 42
    resourceSliceCount: 1
  devices:
    - name: gpu-0
      attributes:
        nvidia.com/gpu:
          string: "A100-SXM4-80GB"
        nvidia.com/memory:
          string: "80Gi"
        nvidia.com/health:
          string: "healthy"
        nvidia.com/uuid:
          string: "GPU-12345678-abcd"
      capacity:
        memory:
          value: "80Gi"
    - name: gpu-1
      attributes:
        nvidia.com/gpu:
          string: "A100-SXM4-80GB"
        nvidia.com/memory:
          string: "80Gi"
        nvidia.com/health:
          string: "degraded"   # 注意:这张卡有降级标记
        nvidia.com/uuid:
          string: "GPU-87654321-efgh"
      capacity:
        memory:
          value: "80Gi"

第二步:集群管理员定义设备类别(DeviceClass)

apiVersion: resource.k8s.io/v1
kind: DeviceClass
metadata:
  name: nvidia-a100
spec:
  # 使用 CEL 表达式精确筛选设备
  selectors:
    - cel:
        expression: |-
          device.driver == "gpu.nvidia.com" &&
          device.attributes["gpu.nvidia.com/gpu"] == "A100-SXM4-80GB" &&
          device.attributes["gpu.nvidia.com/health"] == "healthy"
  # 可选:绑定扩展资源名,兼容旧的 resources.limits 语法
  extendedResourceName: nvidia.com/gpu

这里有一个精妙的设计:CEL 表达式过滤。Device Plugin 时代你只能按数量申请(nvidia.com/gpu: 2),现在你可以按任意属性组合过滤——只要驱动程序把属性暴露到 ResourceSlice 里。

第三步:工作负载运维人员创建资源请求

apiVersion: resource.k8s.io/v1
kind: ResourceClaimTemplate
metadata:
  name: gpu-claim-template
spec:
  spec:
    devices:
      requests:
        - name: gpu-request
          exactly:
            deviceClassName: nvidia-a100
            count: 1

第四步:在 Pod 中引用

apiVersion: v1
kind: Pod
metadata:
  name: llm-inference
spec:
  containers:
    - name: inference
      image: my-llm-server:latest
      resources:
        claims:
          - name: gpu-claim
            request: gpu-request
  resourceClaims:
    - name: gpu-claim
      resourceClaimTemplateName: gpu-claim-template

完整的调度流程:

1. Pod 创建 → API Server 接收
2. resourceclaim-controller 根据 ResourceClaimTemplate 生成 ResourceClaim
3. 调度器扫描 ResourceSlice,找到满足 DeviceClass CEL 表达式且未分配的设备
4. 调度器将设备分配信息写入 ResourceClaim.status
5. 调度器将 Pod 绑定到能访问该设备的节点
6. kubelet 通过 DRA 驱动的 gRPC 接口准备设备
7. Pod 启动,容器内可见设备

2.3 按优先级排序列表:让调度器做「备选方案」决策

v1.36 中 GA 的「按优先级排序列表」特性,解决了一个非常实际的问题:首选设备不可用时怎么办?

以前,如果你的 ResourceClaim 要求 A100 但集群里没有空闲的 A100,Pod 就只能 Pending。现在你可以给调度器一个备选方案列表:

apiVersion: resource.k8s.io/v1
kind: ResourceClaimTemplate
metadata:
  name: flexible-gpu-claim
spec:
  spec:
    devices:
      requests:
        - name: gpu
          firstAvailable:    # 关键字段:按优先级排序
            # 优先选择 A100 80GB
            - name: a100-preferred
              deviceClassName: nvidia-a100-80g
              selectors:
                - cel:
                    expression: |-
                      device.attributes["gpu.nvidia.com/memory"] == "80Gi"
            # 其次选择 A100 40GB(需要两张补偿性能差距)
            - name: a100-40g-fallback
              deviceClassName: nvidia-a100-40g
              count: 2
            # 最后选择任何可用的 NVIDIA GPU
            - name: any-nvidia-gpu
              deviceClassName: nvidia-gpu-generic

调度器的决策逻辑:

  1. 依次尝试 firstAvailable 列表中的每个子请求
  2. 第一个能成功分配的子请求被选中
  3. 如果多个节点都能满足不同优先级的子请求,优先选择能满足更高优先级的节点

这就像买票时的「优先高铁,其次飞机,最后大巴」——调度器自动帮你做降级决策,而不是让你卡在 Pending 状态。

2.4 可切分设备(Partitionable Devices):GPU 切片的艺术

这是 v1.36 Beta 特性中最让我兴奋的一个。它解决了 AI 推理场景的核心痛点:GPU 利用率低

在推理场景中,一个模型通常只需要 10-20GB 显存,而 A100 有 80GB。如果不切片,4 个推理服务需要 4 张 A100,但实际显存利用率只有 25%。

DRA 可切分设备的实现基于 CounterSet 机制:

# 第一部分:定义共享计数器(代表物理设备的资源总量)
apiVersion: resource.k8s.io/v1
kind: ResourceSlice
metadata:
  name: gpu-0-counters
spec:
  nodeName: worker-gpu-01
  driver: gpu.nvidia.com
  pool:
    name: nvidia-gpu-pool
    generation: 1
    resourceSliceCount: 2
  # 共享计数器:表示物理 GPU 的资源容量
  sharedCounters:
    - name: gpu-0-memory
      counters:
        memory:
          value: "80Gi"     # A100 80GB 总显存
---
# 第二部分:定义逻辑设备(从计数器中消耗资源)
apiVersion: resource.k8s.io/v1
kind: ResourceSlice
metadata:
  name: gpu-0-partitions
spec:
  nodeName: worker-gpu-01
  driver: gpu.nvidia.com
  pool:
    name: nvidia-gpu-pool
    generation: 1
    resourceSliceCount: 2
  devices:
    # 逻辑设备 1:20GB 切片
    - name: gpu-0-slice-20g-a
      consumesCounters:
        - counterSet: gpu-0-memory
          counters:
            memory:
              value: "20Gi"
    # 逻辑设备 2:20GB 切片
    - name: gpu-0-slice-20g-b
      consumesCounters:
        - counterSet: gpu-0-memory
          counters:
            memory:
              value: "20Gi"
    # 逻辑设备 3:40GB 切片(给更大的模型用)
    - name: gpu-0-slice-40g
      consumesCounters:
        - counterSet: gpu-0-memory
          counters:
            memory:
              value: "40Gi"

关键约束:同一 CounterSet 中所有逻辑设备的消耗量之和不能超过总量。 调度器会自动保证这一点。在上面的例子中:

  • gpu-0-slice-20g-a + gpu-0-slice-20g-b + gpu-0-slice-40g = 80Gi ✅
  • 如果再加一个 20Gi 的切片就超过 80Gi ❌

调度器的互斥逻辑:

如果两个逻辑设备消耗的 CounterSet 有重叠,调度器保证同一时刻只有其中一部分能被分配,总量不超过物理容量。这意味着:

  • slice-20g-a + slice-20g-b + slice-40g 可以同时分配(20+20+40=80)
  • slice-20g-a + slice-40g 可以同时分配(20+40=60 < 80)
  • 但不能同时分配所有切片如果总量超限

实际应用:推理服务的 GPU 共享

apiVersion: apps/v1
kind: Deployment
metadata:
  name: llm-inference-pool
spec:
  replicas: 3   # 3 个推理副本
  template:
    spec:
      containers:
        - name: inference
          image: vllm/vllm-openai:latest
          resources:
            claims:
              - name: gpu-slice
                request: gpu-slice-req
      resourceClaims:
        - name: gpu-slice
          resourceClaimTemplateName: gpu-20g-slice
---
apiVersion: resource.k8s.io/v1
kind: ResourceClaimTemplate
metadata:
  name: gpu-20g-slice
spec:
  spec:
    devices:
      requests:
        - name: gpu-slice-req
          exactly:
            deviceClassName: nvidia-gpu-20g-slice
            count: 1

3 个 Pod,每个需要 20GB,总共 60GB,可以在一张 A100 80GB 上运行(剩余 20GB 给系统开销),而不是占 3 张 GPU。

性能考量: 切片是在显存层面的隔离,不是计算层面的硬隔离。如果多个切片的计算负载同时打满,会争抢 CUDA Core。对于推理场景(计算间歇、IO 密集)这不是大问题;对于训练场景(持续满算力),建议整卡分配。

2.5 可消耗容量(Consumable Capacity):更灵活的共享模式

可切分设备是「预定义切片」,你需要提前在 ResourceSlice 里定义好逻辑设备的边界。可消耗容量则更灵活——它允许设备被动态地按需分配,调度器实时跟踪容量消耗。

典型场景:网络带宽分配

apiVersion: resource.k8s.io/v1
kind: ResourceSlice
metadata:
  name: rdma-nic-worker-01
spec:
  nodeName: worker-gpu-01
  driver: rdma.example.com
  pool:
    name: rdma-pool
    generation: 1
    resourceSliceCount: 1
  devices:
    - name: rdma-nic-0
      allowMultipleAllocations: true   # 允许多个 ResourceClaim 同时使用
      attributes:
        type:
          string: "ConnectX-7"
      capacity:
        bandwidth:
          value: "400Gbps"             # 总带宽
          requestPolicy:
            default: "10Gbps"
            validRange:
              min: "10Gbps"
              step: "10Gbps"           # 以 10Gbps 为单位分配
# 高优先级训练任务:申请 200Gbps
apiVersion: resource.k8s.io/v1
kind: ResourceClaimTemplate
metadata:
  name: high-bandwidth-claim
spec:
  spec:
    devices:
      requests:
        - name: rdma-req
          exactly:
            deviceClassName: rdma-high-perf
            capacity:
              requests:
                bandwidth: 200Gbps    # 从 400Gbps 中消耗 200Gbps

调度器会确保所有 ResourceClaim 消耗的带宽总和不超过 400Gbps。这意味着:

  • 训练任务申请 200Gbps → 剩余 200Gbps
  • 推理任务申请 100Gbps → 剩余 100Gbps
  • 数据同步申请 100Gbps → 剩余 0Gbps
  • 再来一个申请 → Pending,直到有资源释放

对比可切分设备 vs 可消耗容量:

维度可切分设备可消耗容量
分配粒度预定义的逻辑设备按请求动态消耗
适合资源显存、计算单元带宽、IOPS 等连续量
配置方式ResourceSlice 预定义切片ResourceSlice 定义容量 + Claim 指定消耗量
灵活性中(需提前规划切片边界)高(按需分配)
驱动实现复杂度中(需实现容量追踪)

2.6 设备污点与容忍度(Device Taints & Tolerations)

这终于填补了 DRA 生态中最让人头疼的空白——设备故障处理

在 v1.36 之前,如果一张 GPU 出了 ECC 错误,你只能:

  1. 手动删除 ResourceSlice 中的设备条目
  2. 手动驱逐使用该 GPU 的 Pod
  3. 祈祷没有新的 Pod 被调度到这张坏卡上

v1.36 的设备污点机制让这一切自动化了:

驱动自动设置污点:

# 当 GPU 出现 ECC 错误时,驱动自动更新 ResourceSlice
apiVersion: resource.k8s.io/v1
kind: ResourceSlice
metadata:
  name: nvidia-gpu-node-01
spec:
  devices:
    - name: gpu-1
      taints:
        - key: gpu.nvidia.com/ecc-error
          value: "uncorrectable"
          effect: NoExecute   # 驱逐已调度的 Pod + 阻止新调度

管理员手动设置污点:

apiVersion: resource.k8s.io/v1alpha3
kind: DeviceTaintRule
metadata:
  name: maintenance-gpu-driver-upgrade
spec:
  deviceSelector:
    driver: gpu.nvidia.com       # 影响该驱动管理的所有设备
    pool: worker-gpu-01          # 限定在某个节点
  taint:
    key: maintenance.nvidia.com/driver-upgrade
    value: "in-progress"
    effect: NoSchedule           # 阻止新调度,不驱逐已有 Pod

Pod 容忍坏 GPU(用于降级服务):

apiVersion: resource.k8s.io/v1
kind: ResourceClaimTemplate
metadata:
  name: tolerant-gpu-claim
spec:
  spec:
    devices:
      requests:
        - name: gpu-request
          exactly:
            deviceClassName: nvidia-a100
            tolerations:
              # 容忍 ECC 错误的 GPU(降级运行)
              - key: gpu.nvidia.com/ecc-error
                operator: Exists
                effect: NoExecute
                tolerationSeconds: 3600  # 最多容忍 1 小时
              # 容忍维护中的 GPU
              - key: maintenance.nvidia.com/driver-upgrade
                operator: Exists
                effect: NoSchedule

污点效果对照表:

效果阻止新调度驱逐已有 Pod典型场景
NoSchedule驱动升级、计划维护
NoExecute硬件故障、不可恢复错误
None信息性标记(如性能降级)

驱逐流程详解:

当设备被标记 NoExecute 污点时:

  1. 设备污点驱逐控制器(运行在 kube-controller-manager 中)检测到新污点
  2. 找到所有引用该设备的 ResourceClaim
  3. 找到所有引用这些 ResourceClaim 的 Pod
  4. 检查 Pod 的容忍度——不容忍的 Pod 被加入驱逐队列
  5. 驱逐延迟 = tolerationSeconds(如果设置了)
  6. 删除 Pod(不是优雅终止,是直接删除,让上层控制器重建)
# 检查驱逐状态
kubectl describe devicetaintrules maintenance-gpu-driver-upgrade

# 等待驱逐完成
kubectl wait --for=condition=EvictionInProgress=false \
  DeviceTaintRule/maintenance-gpu-driver-upgrade

2.7 设备健康监控:从被动发现到主动感知

v1.36 另一个 Beta 特性是设备健康监控。以前,GPU 坏了你只能通过应用日志或系统日志发现;现在,DRA 驱动可以通过 DRAResourceHealth gRPC 服务将设备健康状况直接报告给 kubelet,并暴露在 Pod 状态中。

# 查看容器的设备健康状态
kubectl get pod llm-inference -o jsonpath='{.status.containerStatuses[0].allocatedResourcesStatus}'

输出示例:

{
  "devices": [
    {
      "name": "gpu.nvidia.com/gpu-0",
      "health": "Healthy"
    },
    {
      "name": "gpu.nvidia.com/gpu-1",
      "health": "Unhealthy",
      "message": "ECC uncorrectable error detected on HBM bank 3"
    }
  ]
}

与健康监控联动的自动化运维:

# 使用 Kubernetes Event 触发自动运维
apiVersion: events.k8s.io/v1
kind: Event
# 当设备状态变为 Unhealthy 时,kubelet 会产生事件
# 可以通过 Event Router 转发到 Prometheus Alertmanager
# Prometheus 告警规则
groups:
  - name: device-health
    rules:
      - alert: UnhealthyGPU
        expr: |
          kube_pod_container_allocated_resources_device_health{health="Unhealthy"} > 0
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "Pod {{ $labels.pod }} 的 GPU 设备不健康"
          description: "设备 {{ $labels.device }} 报告状态: {{ $labels.health }}"

2.8 管理性质访问(Admin Access):调试与故障排查的特权通道

有时候你需要直接访问正在使用的设备来排查问题——比如查看 GPU 的温度、功耗、运行时状态。v1.36 GA 的管理性质访问允许你创建一个特权 ResourceClaim,可以访问已被其他 Pod 使用的设备。

apiVersion: resource.k8s.io/v1
kind: ResourceClaim
metadata:
  name: gpu-debug-claim
  namespace: admin-tools   # 必须在标记了 admin-access 标签的命名空间
spec:
  devices:
    requests:
      - name: debug-gpu
        exactly:
          deviceClassName: nvidia-a100
          allocationMode: All
          adminAccess: true   # 特权模式:可以访问使用中的设备

安全约束:

  • 命名空间必须标记 resource.kubernetes.io/admin-access: "true"
  • 只有有权限在该命名空间创建 ResourceClaim 的用户才能使用
  • 多租户集群中不应向普通用户开放
# 标记命名空间
kubectl label namespace admin-tools resource.kubernetes.io/admin-access=true

三、Agent Sandbox:当 Kubernetes 开始认真对待 AI Agent

3.1 AI Agent 的运行时安全困境

AI Agent 和传统微服务有本质区别。传统服务的行为是确定性的——代码写好了,输入给定了,输出就是可预测的。但 AI Agent 的行为由 LLM 驱动,本质上是非确定性的。一个「帮用户读邮件」的 Agent 可能因为 Prompt 注入而变成「把邮件转发给攻击者」的 Agent。

在 Kubernetes 中,这个问题更加严峻:

  1. API 访问风险:Agent 需要 Kubernetes API 来管理工作负载,但过度授权可能让它修改集群配置
  2. 网络逃逸风险:Agent 需要访问外部 API,恶意 Agent 可能扫描内网
  3. 资源爆炸风险:Agent 可能动态创建子任务,不加限制会耗尽集群资源
  4. 环境依赖地狱:不同 Agent 需要不同的模型文件、Python 环境、系统库

3.2 Agent Sandbox 架构设计

Agent Sandbox 的核心思想是「最小权限 + 深度防御」:

┌─────────────────────────────────────────────┐
│               Kubernetes Cluster             │
│  ┌─────────────────────────────────────────┐ │
│  │           Agent Sandbox Controller       │ │
│  │  (CRD + Operator + Webhook)             │ │
│  └─────────────────────────────────────────┘ │
│                     │                        │
│        ┌────────────┼────────────┐           │
│        ▼            ▼            ▼           │
│  ┌───────────┐ ┌───────────┐ ┌───────────┐  │
│  │ Sandbox A │ │ Sandbox B │ │ Sandbox C │  │
│  │ ┌───────┐ │ │ ┌───────┐ │ │ ┌───────┐ │  │
│  │ │Agent  │ │ │ │Agent  │ │ │ │Agent  │ │  │
│  │ │Code   │ │ │ │Code   │ │ │ │Code   │ │  │
│  │ └───────┘ │ │ └───────┘ │ │ └───────┘ │  │
│  │ ┌───────┐ │ │ ┌───────┐ │ │ ┌───────┐ │  │
│  │ │Security│ │ │ │Security│ │ │ │Security│ │  │
│  │ │Policy │ │ │ │Policy │ │ │ │Policy │ │  │
│  │ └───────┘ │ │ └───────┘ │ │ └───────┘ │  │
│  └───────────┘ └───────────┘ └───────────┘  │
│        │            │            │           │
│        └────────────┼────────────┘           │
│                     ▼                        │
│  ┌─────────────────────────────────────────┐ │
│  │         NetworkPolicy + PSP Layer        │ │
│  │  - 出站白名单                             │ │
│  │  - 禁止特权升级                           │ │
│  │  - 资源配额限制                           │ │
│  └─────────────────────────────────────────┘ │
└─────────────────────────────────────────────┘

3.3 Agent Sandbox 实战部署

启用 Feature Gate:

# kube-apiserver 配置
apiVersion: v1
kind: Pod
metadata:
  name: kube-apiserver
  namespace: kube-system
spec:
  containers:
    - command:
        - kube-apiserver
        - --feature-gates=AgentSandbox=true
        # ... 其他参数

创建 Agent Sandbox 配置:

apiVersion: v1
kind: Pod
metadata:
  name: email-agent-sandbox
  labels:
    app: email-agent
    sandbox: enabled
spec:
  # 安全上下文:最小权限
  securityContext:
    runAsNonRoot: true
    runAsUser: 1000
    runAsGroup: 1000
    fsGroup: 1000
    seccompProfile:
      type: RuntimeDefault
  containers:
    - name: agent
      image: my-email-agent:v1.0
      securityContext:
        allowPrivilegeEscalation: false
        readOnlyRootFilesystem: true
        capabilities:
          drop:
            - ALL
      resources:
        requests:
          memory: "256Mi"
          cpu: "500m"
        limits:
          memory: "512Mi"
          cpu: "1000m"
      env:
        - name: MODEL_NAME
          valueFrom:
            configMapKeyRef:
              name: agent-config
              key: model-name
        - name: API_KEY
          valueFrom:
            secretKeyRef:
              name: agent-secrets
              key: api-key
      volumeMounts:
        - name: config
          mountPath: /etc/agent/config
          readOnly: true
        - name: tmp
          mountPath: /tmp
  volumes:
    - name: config
      configMap:
        name: agent-config
    - name: tmp
      emptyDir:
        sizeLimit: "100Mi"
  # 关键:只允许访问特定的 API 端点
  automountServiceAccountToken: false

网络策略:限制出站访问

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: agent-sandbox-egress
  namespace: default
spec:
  podSelector:
    matchLabels:
      sandbox: enabled
  policyTypes:
    - Egress
  egress:
    # 允许 DNS 查询
    - to:
        - namespaceSelector:
            matchLabels:
              name: kube-system
      ports:
        - protocol: UDP
          port: 53
    # 只允许访问邮件 API(白名单模式)
    - to:
        - ipBlock:
            cidr: 203.0.113.0/24   # 邮件 API 的 IP 范围
      ports:
        - protocol: TCP
          port: 443
    # 允许访问 LLM API
    - to:
        - ipBlock:
            cidr: 198.51.100.0/24  # LLM API 的 IP 范围
      ports:
        - protocol: TCP
          port: 443

资源配额:防止单个 Agent 耗尽集群资源

apiVersion: v1
kind: ResourceQuota
metadata:
  name: agent-sandbox-quota
  namespace: agent-sandbox
spec:
  hard:
    requests.cpu: "4"
    requests.memory: 8Gi
    limits.cpu: "8"
    limits.memory: 16Gi
    pods: "10"
    # 限制 ConfigMap 和 Secret 数量,防止资源泄露
    configmaps: "20"
    secrets: "20"

3.4 Agent Sandbox 与 DRA 联动:GPU 安全分配

AI Agent 如果需要 GPU 加速,可以将 Agent Sandbox 和 DRA 结合使用:

apiVersion: v1
kind: Pod
metadata:
  name: gpu-agent-sandbox
  labels:
    sandbox: enabled
spec:
  securityContext:
    runAsNonRoot: true
    runAsUser: 1000
    seccompProfile:
      type: RuntimeDefault
  containers:
    - name: agent
      image: my-gpu-agent:v1.0
      securityContext:
        allowPrivilegeEscalation: false
        capabilities:
          drop:
            - ALL
      resources:
        claims:
          - name: gpu-claim
            request: gpu-slice
        limits:
          memory: "1Gi"
          cpu: "500m"
      volumeMounts:
        - name: model-cache
          mountPath: /models
          readOnly: true
  resourceClaims:
    - name: gpu-claim
      resourceClaimTemplateName: agent-gpu-slice
  volumes:
    - name: model-cache
      persistentVolumeClaim:
        claimName: model-pvc
        readOnly: true

这样,Agent 既能安全地使用 GPU 切片,又受到 Agent Sandbox 的全面约束。


四、破坏性变更深度拆解

4.1 gitRepo 卷驱动永久移除

gitRepo 卷类型自 v1.11 起弃用,v1.36 终于彻底移除。原因很明确:安全漏洞

gitRepo 允许 Pod 在初始化时自动克隆 Git 仓库,但它的实现方式是在节点上以 root 身份运行 git clone——这意味着恶意用户可以通过构造特殊的仓库 URL,在节点上执行任意代码。

迁移方案 1:initContainer + git-sync(推荐)

apiVersion: v1
kind: Pod
metadata:
  name: app-with-git-sync
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/example/config-repo
        - --branch=main
        - --depth=1
        - --period=30s    # 每 30 秒检查更新
      securityContext:
        runAsNonRoot: true
        readOnlyRootFilesystem: true
      volumeMounts:
        - name: git-data
          mountPath: /tmp/git
  containers:
    - name: app
      image: myapp:latest
      volumeMounts:
        - name: git-data
          mountPath: /etc/config
          readOnly: true

迁移方案 2:ConfigMap + CI/CD 自动同步

"""Git 仓库到 ConfigMap 的自动同步工具"""
import subprocess
import tempfile
from pathlib import Path
import yaml
import hashlib


class GitToConfigMapSync:
    """将 Git 仓库内容同步为 Kubernetes ConfigMap"""

    def __init__(self, repo_url: str, branch: str = "main"):
        self.repo_url = repo_url
        self.branch = branch

    def clone_repo(self, target_dir: str) -> None:
        """浅克隆 Git 仓库"""
        subprocess.run(
            ["git", "clone", "--depth", "1",
             "--branch", self.branch,
             self.repo_url, target_dir],
            check=True,
            capture_output=True
        )

    def generate_configmap(
        self, source_dir: str, name: str,
        namespace: str = "default"
    ) -> dict:
        """从目录生成 ConfigMap 定义"""
        configmap = {
            "apiVersion": "v1",
            "kind": "ConfigMap",
            "metadata": {
                "name": name,
                "namespace": namespace,
            },
            "data": {}
        }

        for file_path in Path(source_dir).rglob("*"):
            if file_path.is_file() and not file_path.name.startswith("."):
                relative_path = str(file_path.relative_to(source_dir))
                try:
                    content = file_path.read_text(encoding="utf-8")
                    configmap["data"][relative_path] = content
                except UnicodeDecodeError:
                    # 二进制文件跳过
                    continue

        return configmap

    def compute_hash(self, configmap: dict) -> str:
        """计算 ConfigMap 内容哈希,用于检测变更"""
        content = yaml.dump(
            configmap["data"],
            allow_unicode=True,
            default_flow_style=False
        )
        return hashlib.sha256(content.encode()).hexdigest()[:12]

    def sync(self, configmap_name: str, namespace: str = "default") -> dict:
        """执行完整同步流程"""
        with tempfile.TemporaryDirectory() as tmpdir:
            self.clone_repo(tmpdir)
            configmap = self.generate_configmap(
                tmpdir, configmap_name, namespace
            )
            # 添加哈希标注,方便追踪版本
            content_hash = self.compute_hash(configmap)
            configmap["metadata"]["annotations"] = {
                "git-sync/hash": content_hash
            }
            return configmap


# 使用示例
if __name__ == "__main__":
    syncer = GitToConfigMapSync(
        repo_url="https://github.com/example/config-repo",
        branch="main"
    )
    configmap = syncer.sync("app-config", "default")
    print(yaml.dump(configmap, allow_unicode=True))

4.2 Ingress NGINX 退役:Gateway API 迁移实战

Ingress NGINX 的退役是 v1.36 最具破坏性的变更。2026 年 3 月 24 日,SIG Network 正式宣布停止维护——不再有 bug 修复、安全补丁和功能更新。

迁移路径:Ingress → Gateway API + Envoy Gateway

# 旧配置:Ingress NGINX
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: app-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /$2
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.ingress.kubernetes.io/proxy-body-size: "50m"
spec:
  tls:
    - hosts:
        - app.example.com
      secretName: tls-secret
  rules:
    - http:
        paths:
          - path: /api(/|$)(.*)
            pathType: Prefix
            backend:
              service:
                name: api-service
                port:
                  number: 8080
          - path: /(.*)
            pathType: Prefix
            backend:
              service:
                name: web-service
                port:
                  number: 80
# 新配置:Gateway API
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: production-gateway
  namespace: infra
spec:
  gatewayClassName: envoy-gateway-class
  listeners:
    - name: http
      protocol: HTTP
      port: 80
      # HTTP → HTTPS 重定向
      allowedRoutes:
        namespaces:
          from: All
    - name: https
      protocol: HTTPS
      port: 443
      tls:
        mode: Terminate
        certificateRefs:
          - name: tls-secret
            namespace: infra
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: app-api-route
spec:
  parentRefs:
    - name: production-gateway
      namespace: infra
  hostnames:
    - "app.example.com"
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /api
      filters:
        - type: URLRewrite
          urlRewrite:
            path:
              type: ReplacePrefixMatch
              replacePrefixMatch: /
      backendRefs:
        - name: api-service
          port: 8080
    - matches:
        - path:
            type: PathPrefix
            value: /
      backendRefs:
        - name: web-service
          port: 80

Gateway API 相比 Ingress 的优势:

  1. 角色分离:Gateway(集群管理员)→ Route(应用开发者),权限边界清晰
  2. 扩展性:不需要通过 annotation 黑魔法来配置高级功能
  3. 多实现:Envoy Gateway、Contour、Traefik 等多种实现可互换
  4. 功能丰富:流量分割、重试、超时、请求镜像等原生支持

4.3 Service.externalIPs 弃用

externalIPs 允许将任意 IP 地址路由到 Service,这可能导致中间人攻击(CVE-2020-8554)。v1.36 开始弃用警告,v1.43 完全移除。

替代方案选择矩阵:

场景替代方案配置示例
云环境入站流量LoadBalancer Servicetype: LoadBalancer + 云厂商注解
金属环境入站流量MetalLB安装 MetalLB,配置 IPAddressPool
内部服务暴露Gateway APIHTTPRoute + Gateway
简单端口暴露NodePorttype: NodePort

五、SELinux 卷标签 GA:Pod 启动加速的底层原理

5.1 问题根源

在启用了 SELinux 的系统上(RHEL/CentOS/Fedora 默认启用),Kubernetes 需要为每个挂载的卷设置正确的 SELinux 标签。在 v1.36 之前,这个过程是递归的——对卷中的每一个文件和目录调用 setfiles 命令。

对于一个包含 100 万个文件的数据卷,这可能需要数分钟。Pod 的启动时间因此被严重拖慢。

5.2 优化原理

v1.36 的 SELinux 卷标签优化(GA)使用 mount -o context=XYZ 选项,在挂载时一次性应用 SELinux 标签。这不需要遍历任何文件,时间复杂度从 O(n) 降为 O(1)。

旧方式(递归重标签):
  mount /dev/sdb1 /data
  setfiles -R /data  ← 遍历所有文件,逐个设置标签
  耗时:与文件数量成正比,大卷可能需要数分钟

新方式(挂载时标签):
  mount -o context=system_u:object_r:container_file_t:s0 /dev/sdb1 /data
  ← 挂载时一次性设置,所有文件自动继承
  耗时:毫秒级

5.3 配置与注意事项

apiVersion: v1
kind: Pod
metadata:
  name: selinux-optimized-pod
spec:
  securityContext:
    seLinuxOptions:
      level: "s0:c123,c456"
    seLinuxChangePolicy: MountOption   # 显式启用挂载时标签
  containers:
    - name: app
      image: myapp:latest
      volumeMounts:
        - name: data
          mountPath: /data
  volumes:
    - name: data
      persistentVolumeClaim:
        claimName: data-pvc
    selinuxMount: true   # 在 Pod 级别启用 SELinux 挂载优化

⚠️ 关键注意:混合使用风险

如果同一个卷被特权 Pod(不用 SELinux)和非特权 Pod(需要 SELinux 标签)共享,使用 MountOption 可能导致冲突。在这种场景下,必须确保所有使用该卷的 Pod 都使用相同的 SELinux 标签策略,或者回退到递归重标签模式。


六、生产环境 DRA 落地最佳实践

6.1 驱动选择与安装

目前 DRA 生态仍在快速发展,以下是主要硬件厂商的 DRA 驱动支持情况:

厂商驱动DRA 支持可切分设备污点
NVIDIAnvidia-k8s-dra-driver
AMDamd-gpu-dra-driver🔄 开发中
Intelintel-dra-driver
RDMAk8s-rdma-dra-driver✅(带宽)

6.2 多租户 GPU 集群设计

# 租户 A:推理团队,需要低成本 GPU 切片
apiVersion: resource.k8s.io/v1
kind: DeviceClass
metadata:
  name: tenant-a-inference-gpu
spec:
  selectors:
    - cel:
        expression: |-
          device.driver == "gpu.nvidia.com" &&
          device.attributes["gpu.nvidia.com/gpu"] == "A100-SXM4-80GB"
  # 通过 ResourceQuota 限制每个租户的设备用量
---
# 租户 B:训练团队,需要整卡独占
apiVersion: resource.k8s.io/v1
kind: DeviceClass
metadata:
  name: tenant-b-training-gpu
spec:
  selectors:
    - cel:
        expression: |-
          device.driver == "gpu.nvidia.com" &&
          device.attributes["gpu.nvidia.com/gpu"] == "H100-SXM5-80GB" &&
          device.allowMultipleAllocations == false  # 只要不可切片的整卡

6.3 监控与可观测性

# Prometheus ServiceMonitor 采集 DRA 指标
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: dra-device-metrics
spec:
  selector:
    matchLabels:
      app: dra-device-exporter
  endpoints:
    - port: metrics
      interval: 30s
      path: /metrics
---
# DRA 设备利用率告警
groups:
  - name: dra-gpu
    rules:
      - alert: GPUUtilizationLow
        expr: |
          dra_device_utilization{device_class="nvidia-a100"} < 0.3
        for: 1h
        labels:
          severity: info
        annotations:
          summary: "GPU 利用率低于 30%"
          description: >
            节点 {{ $labels.node }} 上的 GPU {{ $labels.device }}
            过去 1 小时利用率仅为 {{ $value | humanizePercentage }},
            考虑使用可切分设备共享给其他工作负载。

七、总结与展望

Kubernetes 1.36 不是一个革命性的版本,但它是一个系统性的版本。它做的不是加一个新功能,而是把 AI 时代的硬件管理从一个接一个的补丁,变成了一套完整的架构。

关键收获:

  1. DRA 是 Kubernetes 硬件管理的未来。如果你还在用 Device Plugin 管理 GPU,现在就该开始规划 DRA 迁移。v1.36 的 DRA 已经 Stable,多项子特性进入 Beta,生态驱动正在快速成熟。

  2. 可切分设备和可消耗容量是 GPU 利用率的杀手锏。推理场景用切片,网络场景用可消耗容量——两者结合可以让硬件利用率从 20-30% 提升到 70-80%。

  3. 设备污点填补了 DRA 最后一块运维短板。以前坏 GPU 只能手动处理,现在可以自动化驱逐和隔离。

  4. Agent Sandbox 是 Kubernetes 对 AI Agent 安全问题的正式回应。虽然还在早期阶段,但它代表了正确的方向——非确定性的代码需要确定性的安全边界。

  5. Ingress NGINX 退役是不可避免的阵痛。Gateway API 更现代、更灵活,但迁移需要时间和精力。建议现在就开始在非生产环境测试 Gateway API。

展望 v1.37+:

  • DRA 抢占(Preemption):允许高优先级工作loads 驱逐低优先级工作loads 占用的设备
  • Agent Sandbox GA:更完善的 CRD 和 Operator 支持
  • 设备拓扑感知调度:NUMA + GPU 拓扑的联合优化
  • DRA 驱动标准化:类似 CSI 的标准化测试套件

Kubernetes 正在从一个「容器编排器」进化为一个「AI 基础设施操作系统」。v1.36 是这个进化过程中的重要一步——它可能不让你立刻惊叹,但当你真正在集群上跑 AI 工作负载时,你会感受到这些变化的份量。


参考资料:

复制全文 生成海报 Kubernetes DRA 云原生 AI GPU Agent Sandbox

推荐文章

如何优化网页的 SEO 架构
2024-11-18 14:32:08 +0800 CST
Go 1.23 中的新包:unique
2024-11-18 12:32:57 +0800 CST
API 管理系统售卖系统
2024-11-19 08:54:18 +0800 CST
如何在Rust中使用UUID?
2024-11-19 06:10:59 +0800 CST
js一键生成随机颜色:randomColor
2024-11-18 10:13:44 +0800 CST
阿里云免sdk发送短信代码
2025-01-01 12:22:14 +0800 CST
四舍五入五成双
2024-11-17 05:01:29 +0800 CST
php 统一接受回调的方案
2024-11-19 03:21:07 +0800 CST
Flet 构建跨平台应用的 Python 框架
2025-03-21 08:40:53 +0800 CST
使用Vue 3和Axios进行API数据交互
2024-11-18 22:31:21 +0800 CST
GROMACS:一个美轮美奂的C++库
2024-11-18 19:43:29 +0800 CST
PHP 代码功能与使用说明
2024-11-18 23:08:44 +0800 CST
程序员茄子在线接单