OCI 容器镜像格式标准

FreeGuideOnline 最新 2026-07-09

mermaid graph TD I[Image Index 索引] --> M1[Manifest 清单] I --> M2[Manifest 清单] M1 --> C[Config 配置] M1 --> L1[Layer 层] M1 --> L2[Layer 层] M1 --> L3[Layer 层]


### 1. 清单(Manifest)

清单文件(`manifest.json`)是整个镜像的**目录**。当你拉取一个镜像时,容器引擎会首先获取这个文件。

一个典型的 OCI 清单包含三项核心信息:

- **`config`**:指向镜像配置文件的描述符(descriptor)。
- **`layers`**:一个描述符数组,按顺序列出了所有文件系统层。
- **`mediaType`**:通常为 `application/vnd.oci.image.manifest.v1+json`。

**描述符(Descriptor)** 是 OCI 中反复使用的重要概念,它包含:

| 字段        | 含义                   |
| ----------- | ---------------------- |
| `mediaType` | 内容的媒体类型         |
| `digest`    | 内容的唯一标识(哈希) |
| `size`      | 内容的字节大小         |
| `urls`      | (可选)备选下载地址   |

清单本身也是一个内容可寻址的 JSON 文件,通过其 SHA256 摘要来唯一引用。

### 2. 配置(Config)

配置文件描述了**如何实例化一个运行中的容器**,相当于容器的“DNA”。它包含:

- **镜像创建时间和作者**(`created`, `author`)。
- **容器运行时的启动命令和参数**:`Entrypoint`、`Cmd`。
- **环境变量**:`Env`。
- **工作目录**:`WorkingDir`。
- **暴露的端口和挂载的卷**:`ExposedPorts`、`Volumes`。
- **层的历史记录**(可选):`history` 字段记录了每一层是如何产生的(如 `RUN`、`COPY` 指令)。

配置文件也遵循内容寻址原则,通过其 SHA256 摘要被清单引用。

### 3. 层(Layers)

层是容器镜像**文件系统的增量变化**,每一层都是一个打包后的文件系统变更集。

- 每一层通常是一个 **tar 压缩包**(常用 gzip 或 zstd 压缩)。
- 层与层之间采用**联合挂载(Union Mount)** 的方式叠加,形成最终的文件系统视图。
- 不同镜像可以共享相同的层,这是镜像快速分发和节约存储空间的基础。

OCI 镜像的层必须是**不可变的**。一旦构建完成,层的摘要就固定不变。

---

## 🔍 内容寻址与摘要:为什么安全可靠

OCI 镜像**完全基于内容寻址(Content Addressable)**。所有组件(清单、配置、每一层)都通过 **SHA256 摘要**来唯一标识。

一个典型的摘要字符串格式为:

sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855


**这带来了两个巨大优势**:

1. **防篡改**:任何内容改变都会导致摘要变化,从而打破引用链。引擎在拉取时会校验摘要,防止中间人攻击。
2. **去重存储**:相同的层(摘要一致)只会在存储中保存一份,无论被多少个镜像引用。

> ⚠️ 注意:摘要是指**未压缩**内容的校验和,而不是压缩后 tar 包的校验和。这种设计避免了因压缩算法差异导致的摘要变化。

---

## 📦 多架构支持:镜像索引(Image Index)

一个简单的 OCI 镜像只能代表**单一平台**(如 `linux/amd64`)。为了让一个镜像标签(如 `nginx:latest`)自动适配不同的 CPU 架构和操作系统,OCI 提供了**镜像索引(Image Index)** 或叫“胖清单”(Fat Manifest)。

索引文件的媒体类型为 `application/vnd.oci.image.index.v1+json`。

它的结构是一个**清单列表**,每个条目包含:

- 一个平台描述(`platform` 字段,指定 `architecture` 和 `os`)。
- 指向具体**平台镜像清单**的描述符。

示例简化逻辑:

```json
{
  "mediaType": "application/vnd.oci.image.index.v1+json",
  "manifests": [
    {
      "platform": { "architecture": "amd64", "os": "linux" },
      "digest": "sha256:aaa..."
    },
    {
      "platform": { "architecture": "arm64", "os": "linux" },
      "digest": "sha256:bbb..."
    }
  ]
}

当你在 ARM 服务器上执行 docker pull 时,引擎会自动根据当前平台选取对应的清单。


🧪 动手实践:解构一个真实的 OCI 镜像

下面我们用工具将一个镜像保存到本地,然后逐步拆解它的内部结构。

前置条件

  • 安装 dockerpodman,以及 skopeo 工具。
  • 一个测试镜像:这里以 alpine:latest 为例。

步骤 1:使用 skopeo 下载镜像到目录

skopeo 可以干净地将远程镜像拷贝到本地目录,而不依赖 Docker 守护进程。

skopeo copy docker://alpine:latest oci:alpine-oci

执行后会生成一个 alpine-oci 目录,结构如下:

alpine-oci/
├── blobs
│   └── sha256
│       ├── <config-hash>
│       ├── <layer1-hash>
│       └── ...
├── index.json
└── oci-layout

步骤 2:查看顶层索引

打开 index.json,它就是这个镜像的入口:

cat alpine-oci/index.json

你将看到一个索引结构,包含指向实际清单的摘要。

步骤 3:追踪清单文件

根据索引中 manifests 里的摘要,去 blobs/sha256/ 目录下找到同名文件。它就是镜像清单。

# 假设摘要为 abc123...
cat alpine-oci/blobs/sha256/abc123...

你会看到 layers 数组和 config 的描述符。

步骤 4:查看配置内容

再根据清单中 config.digest 的值找到对应的 blob 文件,输出即为容器配置 JSON。

cat alpine-oci/blobs/sha256/<config-digest>

你会看到 EnvCmd 等熟悉的字段。

步骤 5:查看层的内容

清单中的每个层都对应一个 blob 文件。这些文件是 tar+gzip 存档。

# 复制层并解压查看
cp alpine-oci/blobs/sha256/<layer-digest> layer.tar.gz
tar -xzf layer.tar.gz -C layer_root
ls layer_root   # 你会看到经典的 Linux 目录结构