原力注入

Kubernetes Runtime Class 原理与实现

Kubernetes Runtime Class 原理与实现

1. 引言

在 Kubernetes 集群中,容器运行时(Container Runtime)是支撑整个容器生态系统的核心组件。随着云原生技术的发展,单一的容器运行时已经无法满足所有场景的需求。不同的工作负载可能需要不同的运行时环境:

  • • 标准应用:使用 containerd 或 Docker
  • • 安全敏感应用:使用 gVisor 或 Kata Containers
  • • GPU 计算:使用 NVIDIA Container Runtime
  • • Windows 容器:使用 Windows 特定的运行时

Kubernetes RuntimeClass 正是为了解决这种多运行时需求而设计的 API 资源。它允许用户在 Pod 级别指定容器运行时配置,为不同的工作负载选择最适合的运行时环境。

Image

2. RuntimeClass 基本概念

2.1 什么是 RuntimeClass

RuntimeClass 是 Kubernetes 的一个集群级别的资源,用于定义不同的容器运行时配置。每个 RuntimeClass 包含以下信息:

  1. 1. 运行时处理器(Handler):标识要使用的运行时配置
  2. 2. 调度约束:指定可以运行该 RuntimeClass 的节点
  3. 3. Overhead:定义运行时本身消耗的资源
  4. 4. Scheduling:配置 Pod 调度相关的参数

2.2 核心组件架构

+----------------+     +-----------------+     +----------------+
|     Pod        |     |  RuntimeClass   |     | Container      |
|                |     |                 |     | Runtime        |
| spec.runtime-  |---->| spec.handler    |---->| Implementation |
| ClassName      |     |                 |     | (containerd,   |
+----------------+     +-----------------+     | gVisor, etc.)  |
                                               +----------------+

3. RuntimeClass 配置详解

3.1 基本配置示例

apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
  name: gvisor # RuntimeClass 名称
handler: runsc # 运行时处理器名称
overhead:
  podFixed:
    memory: "128Mi"
    cpu: "250m"
scheduling:
  nodeSelector:
    runtime: gvisor # 选择具有 gVisor 运行时的节点
  tolerations:
    -
 key: "runtime"
      operator: "Equal"
      value: "gvisor"
      effect: "NoSchedule"

3.2 核心字段说明

handler (必需字段)

  • • 作用:标识要使用的运行时配置
  • • 格式:字符串,对应 CRI 运行时配置中的 "handler"
  • • 示例:runsc (gVisor), kata (Kata Containers), nvidia (NVIDIA)

overhead (可选字段)

  • • 作用:定义运行时本身消耗的资源开销
  • • 用途:帮助调度器更准确地进行资源分配
  • • 字段:
    • • podFixed: 固定资源开销(CPU、内存)
  • • 实践建议:在规划资源配额(Quota)时,必须将 Overhead 计算在内。例如,如果 Pod 请求 1G 内存,Overhead 为 100M,则调度器会认为该 Pod 占用 1.1G 内存。如果忽略这一点,可能导致 ResourceQuota 超标或节点资源超卖。

scheduling (可选字段)

  • • 作用:配置 Pod 调度相关的约束
  • • 字段:
    • • nodeSelector: 节点选择器
    • • tolerations: 容忍度配置
  • • 实践建议:建议配合节点标签(Node Labels)和污点(Taints)使用,确保特定运行时的 Pod 仅被调度到已安装并配置好对应运行时的节点上。

4. 运行时处理器配置

4.1 CRI 配置

RuntimeClass 依赖于容器运行时接口(CRI)的实现。需要在每个节点的 CRI 配置中定义处理器:

containerd 配置示例 (/etc/containerd/config.toml)

[plugins."io.containerd.grpc.v1.cri".containerd]
  # 默认运行时

  default_runtime_name = "runc"

  [plugins."io.containerd.grpc.v1.cri".containerd.runtimes]

    # 标准 runc 运行时

    [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]

      runtime_type = "io.containerd.runc.v2"

    # gVisor 运行时

    [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runsc]

      runtime_type = "io.containerd.runsc.v1"

    # Kata Containers 运行时

    [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.kata]

      runtime_type = "io.containerd.kata.v2"

CRI-O 配置示例 (/etc/crio/crio.conf)

[crio.runtime]
# 默认运行时

default_runtime = "runc"

[crio.runtime.runtimes]

# 标准 runc 运行时

runc = {
  runtime_path = "/usr/bin/runc"
}

# gVisor 运行时

runsc = {
  runtime_path = "/usr/bin/runsc"
}

# Kata Containers 运行时

kata = {
  runtime_path = "/usr/bin/kata-runtime"
}

5. 常见运行时与多运行时配置实战

5.1 常见运行时实现

下表总结了 Kubernetes 生态中常见的容器运行时及其特点:

运行时名称典型 Handler核心特点适用场景
gVisorrunsc
用户空间内核,提供强隔离;兼容 OCI 标准;存在一定性能开销
多租户环境;不可信代码执行;安全敏感应用
Kata Containerskata
基于轻量级虚拟机;硬件级别隔离;性能开销较高(内存、CPU)
金融、医疗等合规要求严格的场景;需要最强安全隔离的环境
NVIDIA Runtimenvidia
专为 GPU 工作负载优化;提供 GPU 资源隔离;支持多 GPU 设备分配
AI/ML 训练;图形渲染;科学计算
CRI-Ocrio
轻量级,Kubernetes 原生
与 runc/CRI-O 组合的默认运行场景
Firecrackerfc
微虚拟机,快速启动
Serverless 函数计算
Windows Containersrunhcs
Windows 容器支持
Windows 工作负载

5.2 多运行时环境配置实战(以 runc + NVIDIA 为例)

在实际生产环境中,我们经常需要在一个节点上同时支持普通应用和 GPU 加速应用。本节将演示如何在一个节点上配置 containerd,使其同时支持标准的 runc 和 nvidia-container-runtime。

5.2.1 环境准备

确保节点已安装:

  • • NVIDIA 显卡驱动
  • • Containerd
  • • NVIDIA Container Toolkit
  • • NVIDIA Device Plugin(Kubernetes 设备插件):用于在集群中暴露 nvidia.com/gpu 资源,建议以 DaemonSet 部署。
# 部署 NVIDIA Device Plugin(请按集群版本选择合适的清单文件)
kubectl apply -f https://raw.githubusercontent.com/NVIDIA/k8s-device-plugin/v0.14.3/nvidia-device-plugin.yml
# 说明:

# - 插件在每个 GPU 节点上运行,向调度器与 kubelet 报告可用的 GPU。

# - 如需启用 MIG 或自定义资源声明,请参考官方文档调整参数。

5.2.2 配置 Containerd 支持多运行时

我们需要修改 containerd 的配置文件 /etc/containerd/config.toml,定义两个运行时:

  1. 1. 默认运行时:使用 runc,用于普通 Pod。
  2. 2. NVIDIA 运行时:使用 nvidia-container-runtime,用于 GPU Pod。
version = 2

[plugins]

  [plugins."io.containerd.grpc.v1.cri"]

    [plugins."io.containerd.grpc.v1.cri".containerd]

      # 设置默认运行时为 runc

      default_runtime_name = "runc"

      [plugins."io.containerd.grpc.v1.cri".containerd.runtimes]

        # 1. 配置标准 runc 运行时

        [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]

          runtime_type = "io.containerd.runc.v2"
          [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]

            BinaryName = ""
            Root = ""
            # 如使用 systemd cgroup,建议设置 SystemdCgroup = true


        # 2. 配置 NVIDIA 运行时

        [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.nvidia]

          runtime_type = "io.containerd.runc.v2" # 注意:这里类型仍然是 runc.v2
          [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.nvidia.options]

            BinaryName = "/usr/bin/nvidia-container-runtime" # 指定 NVIDIA 二进制文件

5.2.3 验证配置

修改完成后,重启 containerd 并验证:

# 重启 containerd
systemctl restart containerd

# 验证运行时是否已注册

crictl info | grep -A 10 "runtimes"
# 输出应包含 "runc" 和 "nvidia"

5.2.4 对应的 RuntimeClass 定义

配置好节点后,我们需要在 Kubernetes 中定义对应的 RuntimeClass:

apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
  name: nvidia
handler: nvidia # 对应 config.toml 中的 [plugins...runtimes.nvidia]
scheduling:
  nodeSelector:
    accelerator: nvidia-gpu # 确保调度到有 GPU 的节点

5.2.5 版本与兼容性提示

  • • 容器运行时版本差异:containerd 在 v1.6 与 v1.7+ 的 CRI 配置结构与字段存在细微差异,尤其在 runtimes.*.options 子项命名与默认行为上。请对照 containerd 官方文档确认当前版本推荐写法。
  • • NVIDIA 集成差异:不同版本的 NVIDIA Container Toolkit(nvidia-container-runtime)在安装路径与与 containerd 的集成方式上可能存在差异;请以 NVIDIA 官方安装指南与发布说明为准。
  • • 设备插件版本匹配:k8s-device-plugin 的 YAML 与功能随 Kubernetes 版本更新,使用前请核对插件版本与集群版本的兼容矩阵。
  • • 验证方法:在更新配置后,先使用 ctr/crictl 层面的最小化容器进行拉起测试,再在 Kubernetes 中以最小化 Pod 验证,以快速定位问题(运行时或调度层)。

6. 使用示例

6.1 创建 RuntimeClass

在创建 RuntimeClass 之前,首先需要确保目标节点已经安装了对应的运行时,并打上了正确的标签。

# 1. 为支持 gVisor 的节点打上标签
kubectl label nodes node-1 runtime=gvisor

# 2. 创建 gVisor RuntimeClass

cat
 <<EOF | kubectl apply -f -
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
  name: gvisor
handler: runsc
overhead:
  podFixed:
    memory: "128Mi"
    cpu: "250m"
scheduling:
  nodeSelector:
    runtime: gvisor
  tolerations:
  - key: "runtime"
    operator: "Equal"
    value: "gvisor"
    effect: "NoSchedule"
EOF


# 1. 为支持 Kata 的节点打上标签

kubectl label nodes node-2 runtime=kata

# 2. 创建 Kata Containers RuntimeClass

cat
 <<EOF | kubectl apply -f -
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
  name: kata
handler: kata
overhead:
  podFixed:
    memory: "256Mi"
    cpu: "500m"
scheduling:
  nodeSelector:
    runtime: kata
  tolerations:
  - key: "runtime"
    operator: "Equal"
    value: "kata"
    effect: "NoSchedule"
EOF

6.2 在 Pod 中使用 RuntimeClass

提示: handler 名称需与 containerd 注册一致;scheduling.nodeSelector 与节点标签需匹配。

apiVersion: v1
kind: Pod
metadata:
  name: secure-app
spec:
  runtimeClassName: gvisor # 使用 gVisor 运行时
  containers:
    -
 name: app
      image: nginx:alpine
      resources:
        requests:
          memory: "64Mi"
          cpu: "100m"
        limits:
          memory: "128Mi"
          cpu: "200m"
---

apiVersion: v1
kind: Pod
metadata:
  name: vm-app
spec:
  runtimeClassName: kata # 使用 Kata Containers 运行时
  containers:
    -
 name: app
      image: nginx:alpine
      resources:
        requests:
          memory: "256Mi"
          cpu: "500m"
        limits:
          memory: "512Mi"
          cpu: "1000m"
# 示例 3:使用 NVIDIA RuntimeClass 运行 GPU 应用
apiVersion: v1
kind: Pod
metadata:
  name: gpu-test
spec:
  runtimeClassName: nvidia # 使用 NVIDIA 运行时
  containers:
    -
 name: cuda-vector-add
      image: nvcr.io/nvidia/k8s/cuda-sample:vectoradd-cuda11.8
      resources:
        limits:
          nvidia.com/gpu: 1
      command: ["/bin/bash", "-c"]
      args: ["nvidia-smi && ./vectorAdd"]
# 注释:

# - 需要在 GPU 节点上部署 NVIDIA Device Plugin,以提供 nvidia.com/gpu 资源。

# - 该镜像包含 CUDA 样例程序,适合进行基础功能验证。
# 示例 4:带有 requests/limits 的 GPU 工作负载(更严格的资源声明)
apiVersion: v1
kind: Pod
metadata:
  name: gpu-strict
spec:
  runtimeClassName: nvidia
  containers:
    -
 name: app
      image: nvidia/cuda:12.4.1-runtime-ubuntu22.04
      resources:
        requests:
          nvidia.com/gpu: 1
        limits:
          nvidia.com/gpu: 1
      command: ["/bin/bash", "-c"]
      args: ["nvidia-smi && sleep 3600"]
# 注释:

# - 同时声明 requests 与 limits 有助于更准确的调度与资源配额控制。

# - 若启用了 MIG,请参考设备插件文档对资源键进行相应调整。

7. 工作原理与源码分析

7.1 API 服务器处理

RuntimeClass 的 API 处理逻辑位于 pkg/registry/node/runtimeclass:

// pkg/registry/node/runtimeclass/storage/storage.go
func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, error) {
    store := &genericregistry.Store{
        NewFunc:func() runtime.Object { return &nodeapi.RuntimeClass{} },
        NewListFunc:func() runtime.Object { return &nodeapi.RuntimeClassList{} },
        // ... 其他配置

    }
    return
 &REST{store}, nil
}

7.2 准入控制

RuntimeClass 的验证逻辑通过准入控制器实现:

// pkg/kubeapiserver/admission/runtimeclass/runtimeclass.go
func (c *runtimeClass) Admit(ctx context.Context, attr admission.Attributes, o admission.ObjectInterfaces) error {
    // 验证 RuntimeClass 是否存在

    if
 _, err := c.lister.Get(rcName); err != nil {
        return
 admission.NewForbidden(attr, fmt.Errorf("runtimeclass %q not found", rcName))
    }

    // 验证 Pod 资源请求是否满足 overhead 要求

    if
 err := c.validateOverhead(attr, rc); err != nil {
        return
 err
    }

    return
 nil
}

7.3 调度器集成

调度器需要考虑 RuntimeClass 的 overhead:

// pkg/scheduler/framework/plugins/noderesources/fit.go
func (f *Fit) PreFilter(ctx context.Context, state *framework.CycleState, pod *v1.Pod) (*framework.PreFilterResult, *framework.Status) {
    // 获取 RuntimeClass

    rcName := pod.Spec.RuntimeClassName
    if
 rcName != nil {
        rc, err := f.runtimeClassLister.Get(*rcName)
        if
 err == nil && rc.Overhead != nil {
            // 将 overhead 添加到 Pod 的资源需求中

            pod = addOverhead(pod, rc.Overhead)
        }
    }

    // 计算节点资源是否足够

    return
 f.calculateResource(pod, nodeInfo)
}

7.4 Kubelet 运行时选择

Kubelet 根据 RuntimeClass 选择对应的运行时:

// pkg/kubelet/kuberuntime/kuberuntime_manager.go
func (m *kubeGenericRuntimeManager) GetRuntime(pod *v1.Pod) (runtimeName string, err error) {
    if
 pod.Spec.RuntimeClassName != nil {
        // 获取 RuntimeClass 配置

        rc, err := m.runtimeClassLister.Get(*pod.Spec.RuntimeClassName)
        if
 err != nil {
            return
 "", err
        }
        return
 rc.Handler, nil
    }

    // 使用默认运行时

    return
 m.defaultRuntime, nil
}

8. 总结

Kubernetes RuntimeClass 为多运行时环境提供了统一的管理接口,使得用户可以根据工作负载的特性选择最合适的容器运行时。通过合理的 RuntimeClass 配置,可以实现:

  1. 1. 安全隔离:为敏感应用提供更强的安全边界。
  2. 2. 性能优化:为特殊工作负载选择专用运行时。
  3. 3. 资源管理:准确计算运行时开销,优化资源分配。
  4. 4. 灵活调度:根据运行时需求进行智能节点选择。

在生产环境落地时,建议运维团队建立统一的运行时管理规范,明确不同 RuntimeClass 的适用场景(如 gvisor 用于不可信代码,nvidia 用于 AI 训练),并通过监控系统持续关注 Overhead 对集群资源的影响。随着云原生技术的不断发展,RuntimeClass 将在混合运行时环境中发挥越来越重要的作用,为构建安全、高效、灵活的 Kubernetes 平台提供坚实基础。

9. 参考资料

  • • Kubernetes:RuntimeClass 设计与用法(官方文档)https://kubernetes.io/docs/concepts/containers/runtime-class/
  • • Kubernetes:Pod Overhead(官方文档)https://kubernetes.io/docs/concepts/scheduling-eviction/pod-overhead/
  • • containerd:CRI 配置参考与示例 https://github.com/containerd/containerd/blob/main/docs/cri/config.md
  • • NVIDIA Container Toolkit 安装指南 https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html
  • • NVIDIA Kubernetes Device Plugin https://github.com/NVIDIA/k8s-device-plugin
  • • gVisor 文档 https://gvisor.dev/docs/
  • • Kata Containers 文档 https://katacontainers.io/docs/