在容器技术发展的早期,Docker 镜像是事实上的标准。随着容器生态的爆发,为了确保不同容器运行时(如 Docker, containerd, CRI-O, Podman 等)和构建工具之间的互操作性,Open Container Initiative (OCI) 成立并推出了 OCI Image Specification

本文将基于最新的 OCI Image Specification 详细解读 OCI 镜像的构成及其背后的技术细节。

OCI 镜像规范概览

OCI 镜像规范定义了 OCI 镜像的结构。简单来说,一个 OCI 镜像包含以下几个核心部分:

  1. Image Manifest (镜像清单): 描述构成镜像的组件(包括配置和层)。
  2. Image Index (镜像索引): (可选) 指向多个 Manifest 的列表,通常用于支持多架构(如 amd64, arm64)。
  3. Image Layout (镜像布局): 镜像在文件系统上的目录结构。
  4. Filesystem Layer (文件系统层): 包含了容器文件系统的更改集 (Changeset)。
  5. Image Configuration (镜像配置): 包含了镜像的元数据(如启动命令、环境变量)以及层级顺序。

这些组件共同工作,使得镜像可以被构建、传输、校验并最终运行。

核心概念:Content Descriptors (内容描述符)

在深入各个组件之前,必须先理解 Content Descriptor。它是 OCI 规范中用来引用内容的通用方式。

一个描述符(Descriptor)就是一个 JSON 对象,它告诉我们“去哪里找内容”、“内容长什么样”以及“内容的指纹是什么”。核心字段包括:

  • mediaType: 内容的类型(例如:application/vnd.oci.image.manifest.v1+json)。
  • digest: 内容的哈希摘要(通常是 sha256),用于唯一标识和校验内容。
  • size: 内容的大小(字节)。
1
2
3
4
5
{
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"size": 7682,
"digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270"
}

OCI 广泛使用了 Merkle Directed Acyclic Graph (DAG) 结构,描述符就是图中的边,连接了不同的组件。

组件结构详解

OCI 镜像的结构关系如下图所示:

1. Image Manifest (镜像清单)

Manifest 是镜像的“物料清单”。对于一个特定的架构和操作系统,Manifest 定义了镜像由哪些层组成,以及配置文件的位置。

一个典型的 Manifest JSON 结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
"schemaVersion": 2,
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"config": {
"mediaType": "application/vnd.oci.image.config.v1+json",
"digest": "sha256:b5b2b2...",
"size": 7023
},
"layers": [
{
"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
"digest": "sha256:983487...",
"size": 32654
},
{
"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
"digest": "sha256:3c3a46...",
"size": 16724
}
],
"annotations": {
"com.example.key1": "value1"
}
}
  • config: 引用镜像配置 Blob。
  • layers: 一个有序数组,引用了组成文件系统的各个层 Blob。数组索引 0 是底层 (Base Layer),后续层依次叠加。

2. Image Configuration (镜像配置)

Configuration 是一个 JSON 文档,包含了将镜像转换为运行时 Bundle 所需的信息。它不包含文件系统的实际内容,而是包含元数据。

主要包含:

  • architecture / os: 适用的 CPU 架构和操作系统。
  • config: 运行时配置,如 Env (环境变量), Cmd (默认命令), Entrypoint, User, WorkingDir 等。
  • rootfs: 引用了由于层叠加而产生的 Diff IDs,用于校验文件系统完整性。
  • history: 描述了每一层是如何构建出来的(例如 Dockerfile 中的指令)。

3. Filesystem Layers (文件系统层)

Layers 包含了文件系统的实际变化。每一个层通常是一个 .tar.tar.gz 归档文件。

OCI 镜像采用分层存储写时复制 (CoW) 策略。每一层只记录相对于上一层的 变化 (Changeset) 。变化主要有三种类型:

  1. Add (新增): 包含了完整的文件或目录内容。
  2. Modify (修改): 包含了修改后文件的完整内容。在 Tar 包中,增加和修改的表现形式是一样的(即存在该文件)。
  3. Delete (删除): OCI 使用 Whiteouts (遮盖文件) 来表示删除。

关于 Whiteouts (遮盖文件)

如果我们在上层删除了下层的一个文件 /etc/my-app-config,我们不能直接从 Tar 包里把这个文件拿掉(因为下层是只读的)。相反,我们在新层中创建一个特殊的文件,名字叫 .wh.my-app-config

运行时在联合挂载(Union Mount)时,看到 .wh. 前缀的文件,就会知道要“遮挡”住下层的同名文件,从而实现删除效果。

还有一个特殊的 Opaque Whiteout (.wh..wh..opq),用于隐藏父目录下的所有子项。

1
2
3
4
5
# 示例:层内容
/etc/my-app.d/
/etc/my-app.d/default.cfg
/bin/my-app-tools
/etc/.wh.my-app-config <-- 表示删除了 /etc/my-app-config

4. Image Index (镜像索引)

Image Index 是一个更高层级的 Manifest。它本身不包含层或配置,而是包含了一个指向其他 Manifest 的列表。

这主要用于多平台支持。当你 docker pull my-image:latest 时,客户端会先获取 Index,然后根据当前的机器架构(如 linux/amd64),找到列表中对应的 Manifest 并下载。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
"schemaVersion": 2,
"mediaType": "application/vnd.oci.image.index.v1+json",
"manifests": [
{
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"digest": "sha256:e69241...",
"size": 7143,
"platform": {
"architecture": "ppc64le",
"os": "linux"
}
},
{
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"digest": "sha256:5b0bca...",
"size": 7682,
"platform": {
"architecture": "amd64",
"os": "linux"
}
}
]
}

OCI 生态工具推荐

了解 OCI 规范后,值得深入了解两个在云原生生态中被广泛使用的工具和库,它们完美体现了 OCI 标准带来的互操作性。

Skopeo

Skopeo 是一个功能强大的命令行工具,用于对容器镜像和镜像库执行各种操作。与其他工具不同,Skopeo 的最大优势在于它无需运行容器守护进程(如 Docker Daemon) 即可工作。

常用场景示例:

  1. 远程检视 (skopeo inspect)
    在不下载(Pull)镜像层的情况下,直接读取远程仓库中镜像的 Manifest 和配置信息。这在 CI/CD 流水线中非常实用,可以快速检查镜像标签、架构或创建时间。

    命令:

    1
    skopeo inspect docker://hub.example.io/library/alpine:latest

    输出示例 (JSON):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    skopeo inspect docker://hub.example.io/library/alpine:latest --tls-verify=false
    {
    "Name": "hub.example.io/library/alpine",
    "Digest": "sha256:9d04ae17046f42ec0cd37d0429fff0edd799d7159242938cc5a964dcd38c1b64",
    "RepoTags": [
    "3.10",
    "3.22",
    "latest"
    ],
    "Created": "2025-10-08T11:04:56Z",
    "DockerVersion": "",
    "Labels": null,
    "Architecture": "amd64",
    "Os": "linux",
    "Layers": [
    "sha256:2d35ebdb57d9971fea0cac1582aa78935adf8058b2cc32db163c98822e5dfa1b"
    ],
    "LayersData": [
    {
    "MIMEType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
    "Digest": "sha256:2d35ebdb57d9971fea0cac1582aa78935adf8058b2cc32db163c98822e5dfa1b",
    "Size": 3802452,
    "Annotations": null
    }
    ],
    "Env": [
    "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
    ]
    }
  2. 镜像复制与格式转换 (skopeo copy)
    支持在不同的存储机制之间直接复制镜像。例如,将 Docker Hub 上的镜像直接下载并转换为符合 OCI Layout 标准的本地文件目录,而无需中间环节。

    命令:

    1
    2
    # 将 Docker 在线镜像复制为本地 OCI 目录结构
    skopeo copy docker://docker.io/library/alpine:latest oci:/tmp/alpine-layout:latest

    输出:

    1
    2
    3
    4
    5
    Getting image source signatures
    Copying blob 05455a5d5d3e done
    Copying config 124c7d2707 done
    Writing manifest to image destination
    Storing signatures

containers/image 库

containers/image(也被称为 image library)是上述 Skopeo 以及 Podman、Buildah 等下一代容器工具背后的核心库。

这是一个 Go 语言库,提供了一套通用的 API 来处理容器镜像的传输、签名、及其元数据管理。它抽象了底层的存储细节,支持多种 传输协议 (Transports)

  • docker://:访问标准的远程 Docker/OCI 注册表。
  • docker-daemon://:直接操作 Docker 守护进程的本地存储。
  • oci://:操作符合 OCI 目录布局(OCI Layout)的本地镜像。
  • dir://tarball:// 等:支持本地目录和归档文件。

正是因为有了 containers/image 库的底层支持,OCI 生态中的工具才能实现跨平台、跨存储后端的无缝互操作,真正践行了 OCI “一次构建,到处运行” 的理念。

代码示例 (Go)
以下代码展示了如何使用该库编程将一个远程 Docker 镜像复制到本地 OCI 目录中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package main

import (
"context"
"os"
"github.com/containers/image/v5/copy"
"github.com/containers/image/v5/signature"
"github.com/containers/image/v5/transports/alltransports"
)

func main() {
src := "docker-daemon://alpine:latest"
dest := "oci:/tmp/my-alpine-layout:latest"

// 1. 解析源和目标引用
srcRef, err := alltransports.ParseImageName(src)
if err != nil {
panic(err)
}
destRef, err := alltransports.ParseImageName(dest)
if err != nil {
panic(err)
}

// 2. 配置策略上下文 (示例中使用宽松策略)
policy := &signature.Policy{Default: []signature.PolicyRequirement{signature.NewPRInsecureAcceptAnything()}}
policyContext, _ := signature.NewPolicyContext(policy)

// 3. 执行 Image Copy 操作
// 这相当于运行 `skopeo copy`
_, err = copy.Image(context.Background(), policyContext, destRef, srcRef, &copy.Options{
ReportWriter: os.Stdout, // 将进度输出到控制台
})

if err != nil {
panic(err)
}
}

执行结果:

1
2
3
4
Getting image source signatures
Copying blob 989e799e6349 done |
Copying config b956011c2e done |
Writing manifest to image destination

总结

OCI 镜像规范不仅仅是一个文件格式标准,它构成了现代云原生基础设施的基石。通过解耦 Manifest (清单)Config (配置)Layers (数据) ,它实现了高度的灵活性和互操作性。

本文的核心要点回顾:

  1. 内容寻址 (Content Addressable): 一切皆通过 Digest 引用,确保内容不可篡改且易于去重。
  2. 分层机制: 极大地优化了存储和传输效率,公共的基础镜像层只需存储一份。
  3. 多架构支持: Image Index 使得同一个标签可以无缝支持多种硬件平台。
  4. 生态互通: 借助 Skopeo 和 containers/image 等工具,我们可以轻松在不同环境(Docker, OCI Layout, Remote Registry)之间迁移和操作镜像。

理解这些底层细节,有助于我们更好地进行容器镜像的构建优化、安全扫描以及故障排查,从而构建更高效、更安全的云原生应用。


本文参考自 Open Container Initiative Image Specification