一、OCI (Open Container Initiative / 开放容器倡议)

1. 是什么?

OCI 是一个由 Linux 基金会在 2015 年主导成立的治理委员会和项目。它的诞生背景是,当时 Docker 公司如日中天,几乎成为了容器的代名词。为了避免容器技术被单一厂商(Docker)锁定,并促进容器生态的健康发展,多家行业巨头(包括 Google, Red Hat, IBM, Microsoft, Amazon 等)联合发起了 OCI。

核心目标: 制定容器格式的开放行业标准。让任何人都可以基于这些标准来创建符合规范的容器工具,从而实现不同工具之间的互操作性。

你可以把 OCI 想象成**“容器世界的 ISO 国际标准化组织”**。它不生产任何软件,只负责制定“规则”。

2. 核心规范

OCI 主要发布了两个关键的规范(Specification),它们构成了现代容器技术的基石:

a. 运行时规范
  • 定义了什么? 定义了一个**“解压后的、可运行的容器文件系统包”** 应该是什么样子,以及如何运行它。
  • 核心内容:它规定了一个**config.json** 文件。这个文件是 OCI 运行时规范的灵魂,它描述了运行一个容器所需的所有元数据,包括:
    • 进程信息:要运行的命令、参数、环境变量、工作目录。
    • 根文件系统:指向容器根目录的路径。
    • 用户:以哪个用户身份运行进程。
    • 资源限制:CPU、内存的 cgroups 配置。
    • 安全配置:Linux capabilities、seccomp 规则、namespaces 配置。
    • 挂载点:需要挂载的 proc, sysfs, devpts 等伪文件系统。
  • 作用:任何一个符合 OCI Runtime 规范的工具(称为 OCI Runtime),只要拿到这个 config.json 和对应的文件系统,就能以完全相同的方式启动这个容器。这实现了运行时的标准化
  • 著名实现
    • runc:这是 OCI Runtime 规范的参考实现,也是目前使用最广泛的容器运行时。它由 Docker 贡献出来,现在是 CNCF 的毕业项目。Docker, containerd, CRI-O 底层默认都是用 runc 来实际启动容器的。
    • crun:一个用 C 语言编写的、更轻量级的 OCI Runtime。
    • kata-runtime:一个基于轻量级虚拟机的 OCI Runtime,提供了更强的安全隔离。
b. 镜像规范
  • 定义了什么? 定义了如何创建和打包一个标准的、可移植的容器镜像
  • 核心内容:它规定了一个 OCI 镜像由以下几部分组成:
    • 文件系统层:镜像不是单一的巨大文件,而是由一系列只读的文件系统层 组成。每一层代表了对上一层所做的变更(添加、删除、修改文件)。这种分层设计使得镜像构建、存储和分发都非常高效(可以复用层)。
    • 清单:一个 JSON 文件,可以理解为镜像的“总目录”或“清单”。它引用了镜像的配置文件和所有相关的文件系统层
    • 配置文件:一个 JSON 文件,包含了镜像的元数据,比如架构(amd64, arm64)、操作系统、暴露的端口、环境变量,以及最重要的——运行时需要执行的 config.json 的模板
    • 索引:当同一个镜像有多个版本(比如支持不同 CPU 架构的多架构镜像)时,Index 文件会列出所有可用的 Manifest。
  • 作用:任何一个符合 OCI Image 规范的工具,都可以创建、打包、解包和分发 OCI 镜像。这实现了镜像格式的标准化。我们现在使用的 Docker 镜像格式,已经完全兼容 OCI 镜像规范。
  • 著名实现
    • docker build / buildah:用于构建 OCI 镜像。
    • docker push / podman push:用于将 OCI 镜像推送到镜像仓库。
    • 所有主流的容器镜像仓库(Docker Hub, Quay.io, GCR 等)都支持 OCI 镜像规范。
3. OCI 总结
规范 定义了什么? 核心产物 目标
运行时规范 如何运行一个解压后的容器 config.json + 文件系统 运行时标准化,确保任何 OCI 运行时都能以相同方式运行容器。
镜像规范 如何打包和分发一个容器镜像 Manifest + Config + 文件系统层 镜像格式标准化,确保镜像可以在任何兼容的平台上构建和运行。

一句话总结 OCI:OCI 为容器生态制定了“法律”(规范),确保了“零件”(镜像和运行时)的通用性和互换性。


二、CRI (Container Runtime Interface / 容器运行时接口)

1. 是什么?

CRI 是 Kubernetes 项目在 v1.5 版本中引入的一个插件接口

核心目标: 将 Kubernetes(kubelet)与具体的容器运行时解耦

在 CRI 出现之前,Kubernetes 的 kubelet 是硬编码地直接调用 Docker 的 API 来管理容器。这带来了几个严重问题:

  1. 强依赖:Kubernetes 的命运和 Docker 强绑定。如果 Docker 出问题或者有更好的运行时出现,Kubernetes 很难切换。
  2. 功能冗余:Docker 本身是一个功能非常庞杂的“全家桶”(包含镜像构建、镜像管理、容器网络、容器存储、容器运行时等)。Kubernetes 其实只需要它的一部分功能(主要是容器生命周期管理),但不得不依赖整个 Docker 引擎。
  3. 架构不清晰:kubelet 的代码里混杂了大量与 Docker 交互的逻辑,变得臃肿且难以维护。

为了解决这个问题,Kubernetes 团队设计了 CRI。CRI 是一套 gRPC API。它定义了一组接口,包括如何创建、启动、停止、删除容器,如何拉取镜像,如何管理 Pod 的沙箱环境等。

你可以把 CRI 想象成**“Kubernetes 的‘USB接口’”**。Kubernetes(kubelet)是电脑主机,它不关心你插的是什么牌子的 U 盘(容器运行时),只要你的 U 盘符合 USB 标准(实现了 CRI 接口),它就能识别和使用。

2. CRI 的工作原理

kubelet 内置了一个 CRI 客户端,它会通过 gRPC 连接到一个实现了 CRI 服务端接口的容器运行时服务。这个服务通常以一个守护进程 的形式运行在同一个节点上。

CRI 主要分为两大服务:

  1. Image Service (镜像服务)

    • 负责所有与镜像相关的操作。
    • 主要接口:PullImage, ListImages, ImageStatus, RemoveImage
  2. Runtime Service (运行时服务)

    • 负责所有与 Pod 和容器生命周期相关的操作。
    • 主要接口:RunPodSandbox, StopPodSandbox, RemovePodSandbox, CreateContainer, StartContainer, StopContainer, RemoveContainer 等。

关键概念 PodSandbox (Pod 沙箱)
在 CRI 模型中,Kubernetes 管理的最小单元是 Pod。一个 Pod 包含一个或多个容器,它们共享网络和存储命名空间。PodSandbox 就是这个共享环境的抽象。在 Linux 上,它通常就是一个 paused 容器,它的唯一作用就是 hold 住 namespaces,让 Pod 内的其他容器可以加入进来。

3. 著名的 CRI 实现

一个软件要能被 Kubernetes 用作容器运行时,它必须实现 CRI 接口。目前主流的 CRI 实现有:

  • containerd

    • 这是目前 Kubernetes 的事实标准默认的 CRI 运行时。
    • 它本身是一个高级运行时,从 Docker 引擎中剥离出来,成为了一个 CNCF 的毕业项目。
    • 它的架构非常清晰:containerd (实现了 CRI) -> runc (OCI Runtime) -> 内核
    • 它负责镜像管理、存储管理、网络管理,并调用底层的 runc 来实际启动容器。
  • CRI-O

    • 这是 Red Hat 主导开发的一个轻量级的 CRI 实现。
    • 它的设计哲学是**“只做 Kubernetes 需要的事”**,没有任何多余的功能。
    • 它的架构同样清晰:CRI-O (实现了 CRI) -> runc/crun (OCI Runtime) -> 内核
    • 因为轻量,所以启动速度快,资源占用少,非常适合对性能和资源敏感的场景。
  • Docker Engine (通过 dockershim)

    • 这是 Kubernetes 早期使用的方式。kubelet 内部有一个名为 dockershim 的组件,它的作用就是将 CRI 调用“翻译”成 Docker API 调用
    • dockershim 是一个临时方案,它让 Kubernetes 能够在 CRI 标准成熟之前继续使用 Docker。
    • 重要提示:从 Kubernetes v1.20 开始,dockershim 被标记为废弃(Deprecated),并在 v1.24 版本中被正式移除。这意味着 Kubernetes 不再直接支持 Docker 作为容器运行时。你仍然可以在 K8s 节点上安装 Docker 来构建镜像,但 K8s 本身管理容器会通过 containerdCRI-O
4. CRI 总结
组件 角色 作用
Kubelet CRI 客户端 Kubernetes 的节点代理,负责管理 Pod 和容器。它通过 CRI 接口与运行时通信。
CRI 接口 标准/契约 一套 gRPC API,定义了 kubelet 如何与容器运行时交互。
CRI 实现 CRI 服务端 containerd, CRI-O 等软件。它们实现了 CRI 接口,负责解析 kubelet 的请求并执行具体操作。
OCI 运行时 底层执行者 runc, crun 等。它们被 CRI 实现调用,根据 OCI Runtime 规范来实际创建和运行容器。

一句话总结 CRI:CRI 是 Kubernetes 的“USB接口”,它让 K8s 可以灵活地插拔不同的容器运行时,而无需修改自己的核心代码。


三、OCI 和 CRI 的关系:它们如何协同工作?

这是最关键的部分。OCI 和 CRI 并不是竞争关系,而是分层协作、相辅相成的关系。

一个形象的比喻:汽车制造业

  • OCI (标准)
    • 运行时规范:定义了“发动机”的工作原理(四冲程、点火顺序等)。只要符合这个标准,无论是 V6 还是 V8 发动机,都能让汽车跑起来。这就像 runccrun
    • 镜像规范:定义了“汽车零件”的标准化尺寸和接口(如螺丝型号、轮胎规格)。任何工厂生产的零件,只要符合标准,就能组装到一辆车上。这就像 OCI 镜像。
  • CRI (接口)
    • 定义了“汽车总装线”的控制面板和操作流程。工人(kubelet)只需要按按钮(调用 CRI API),总装线(CRI 实现,如 containerd)就会自动去抓取标准化的零件(OCI 镜像),并使用标准化的发动机(OCI Runtime,如 runc)来组装一辆完整的汽车。

流程解析:

  1. 用户通过 kubectl 创建一个 Pod。
  2. Kubernetes API Server 将 Pod 调度到某个节点。
  3. 该节点的 Kubelet 接收到任务,它需要创建一个 Pod。
  4. Kubelet 通过 CRI (gRPC) 接口,调用 CRI 实现(如 containerdRunPodSandbox API,要求它创建一个 Pod 沙箱环境。
  5. containerd 收到请求后:
    • 首先,它会去拉取 Pod 中容器所需的镜像。它拉取的镜像格式是符合 OCI Image Spec 的。
    • 然后,它准备好 Pod 的网络和存储。
    • 最后,它调用底层的 OCI Runtime(如 runc 来实际创建并启动 Pod 的沙箱容器(通常是 pause 容器)。
  6. runc 被调用时,containerd 会给它准备好一个目录,里面包含了解压后的镜像文件系统和一个符合 OCI Runtime Specconfig.json 文件。
  7. runc 读取 config.json,根据里面的配置,直接向 Linux 内核 发起系统调用(如 clone() 创建 namespace,setrlimit() 设置 cgroups),最终启动容器进程。
  8. 一旦 Pod 沙箱准备好,Kubelet 就会继续通过 CRI 接口,让 containerd 在这个沙箱里创建和启动真正的业务容器。这个过程同样会经历 containerd -> runc -> 内核的调用链。

最终结论

特性 OCI (开放容器倡议) CRI (容器运行时接口)
性质 标准 接口
制定者 Linux 基金会下的 OCI 组织 Kubernetes 社区 (CNCF)
目标 整个容器生态的标准化和互操作性 Kubernetes 与容器运行时的解耦
关注点 “物”的规范:定义了容器镜像长什么样,容器运行时应该怎么工作。 “事”的流程:定义了 Kubernetes 如何向运行时下达指令(创建、启动、停止容器等)。
关系 CRI 的实现依赖于 OCI 规范containerd (CRI实现) 在底层必须调用一个符合 OCI Runtime Spec 的 runc (OCI实现) 来干活。containerd 管理的镜像也必须是符合 OCI Image Spec 的。

简单来说:OCI 负责定义“什么是容器”,CRI 负责定义“如何管理容器”。它们共同构成了现代云原生容器化世界的基石,使得生态既开放标准,又灵活可插拔。

Logo

加入「COC·上海城市开发者社区」,成就更好的自己!

更多推荐