OCI 标准中,为描述 OCI Image规范包含了 image manifest,image index(optional),filesystem layers集合以及 configuration配置。通过如上描述使得对于 image 在异构场景下能够被构建、传输且可执行变的通用化。

站在上层应用的视角, image manifest 包含内容可寻址且解压可运行的filesystem layer。而 image configuration 包含例如应用参数、环境变量等。
image index 则是对系列 manifestdescriptors 的描述指向,用于对不同镜像的补充,一般来说是对不同架构或者不同属性的描述。

OCI-Overview

Specification 解读

OCI Image Media Types

OCI Image Media Types 定义了如下的格式:

  • application/vnd.oci.descriptor.v1+json: Content Descriptor
  • application/vnd.oci.layout.header.v1+json: OCI Layout
  • application/vnd.oci.image.index.v1+json: Image Index
  • application/vnd.oci.image.manifest.v1+json: Image manifest
  • application/vnd.oci.image.config.v1+json: Image config
  • application/vnd.oci.image.layer.v1.tar: "Layer", 以 tar 方式压缩
  • application/vnd.oci.image.layer.v1.tar+gzip: "Layer", 以 gzip 方式压缩
  • application/vnd.oci.image.layer.v1.tar+zstd: "Layer", 以 zstd 方式压缩
  • application/vnd.oci.scratch.v1+json: Scratch blob
  • application/vnd.oci.artifact.manifest.v1+json: Artifact manifest

而如下 media types 将被剔除且不建议在未来的版本中使用:

  • application/vnd.oci.image.layer.nondistributable.v1.tar
  • application/vnd.oci.image.layer.nondistributable.v1.tar+gzip
  • application/vnd.oci.image.layer.nondistributable.v1.tar+zstd

对于 media type 的明细配置矩阵可以参考 Compatibility Matrix

Media Type 关联关系

media type reference

通过 Descriptors 描述其中关联关系。其中 image-index 可以理解为是 **fat-manifest**,是对目标架构平台的 image manifests的关联描述入口。其中 image manifest 则是对具体 image configuration 和众多 layers 的描述关联入口。

spec 中上层组件描述

Image Manifest

构建容器镜像的一种文档描述,其作用有如下三点:

  1. 用于构建可内容寻址的镜像,镜像模型中包含可哈希获取镜像配置及其组件
  2. 通过 fat-manifest 来维护各异构平台支持的镜像
  3. 可转换位 OCI 运行时规范

属性介绍

image index 主要是一系列架构平台的信息描述,而 image manifest 则是维护具体镜像配置,以及在具体 operating system 的镜像的系列 layers 的信息。

  • shcemaVersion | int | REQUIRED
  • mediaType | string | REQUIRED
  • artifactType | string | OPTIONAL
  • config | descriptor | REQUIRED
    • mediaType | string
  • layers | array of objects
    其中元素必须是 descriptor,且必须拥有一个入口 entry。
  • subject | descriptor | OPTIONAL
  • annotation | string-string-map | OPTIONAL

Image 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
25
26
27
28
29
30
31
32
33
34
35
{
"schemaVersion": 2,
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"config": {
"mediaType": "application/vnd.oci.image.config.v1+json",
"size": 7023,
"digest": "sha256:b5b2b2c507a0944348e0303114d8d93aaaa081732b86451d9bce1f432a537bc7"
},
"layers": [
{
"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
"size": 32654,
"digest": "sha256:9834876dcfb05cb167a5c24953eba58c4ac89b1adf57f28f2f9d09af107ee8f0"
},
{
"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
"size": 16724,
"digest": "sha256:3c3a4604a545cdc127456d94e421cd355bca5b528f4a9c1905b15da2eb4a4c6b"
},
{
"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
"size": 73109,
"digest": "sha256:ec4b8955958665577945c89419d1af06b5f7636b4ac3da7f12184802ad867736"
}
],
"subject": {
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"size": 7682,
"digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270"
},
"annotations": {
"com.example.key1": "value1",
"com.example.key2": "value2"
}
}

**Note: mediaType 必须与所包含的 digest 相对应,比如当 digest 是通过 ScratchDigestSHA256 生成,那么 media type “必须” 配置为 application/vnd/oci.scratch.v1+json**。

1
2
3
4
5
{
"mediaType": "application/vnd.oci.scratch.v1+json",
"size": 2,
"digest": "sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a"
}

OCI Image Index 解读

image index 的配置设置是作为 image manifests 的上层引用。

具体属性解读

  • schemaVersion | int | REQUIRED
  • mediaType | string
  • manifests | array of objects | REQUIRED
    manifests 包含系列 带有如下属性的descriptor properties
  • mediaType | string
  • platform | obeject | OPTIONAL
    当具体 platform 被提供时,需要进行如下运行时资源声明
    • architecture | string | REQUIRED
    • os | string | REQUIRED
    • os.version | string | OPTIONAL
    • os.features | array of strings | OPTIONAL
    • variant | string | OPTIONAL
    • features | array of strings
  • annotations | string-string-map | OPTIONAL

Platform Variants

ISA/ABI architecture variant
ARM 32-bit, v6 arm
ARM 32-bit, v7 arm
ARM 32-bit, v8 arm
ARM 64-bit, v8 arm64

OCI Image Index 示例

  • simple image index with two platforms
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
{
"schemaVersion": 2,
"mediaType": "application/vnd.oci.image.index.v1+json",
"manifests": [
{
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"size": 7143,
"digest": "sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f",
"platform": {
"architecture": "ppc64le",
"os": "linux"
}
},
{
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"size": 7682,
"digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270",
"platform": {
"architecture": "amd64",
"os": "linux"
}
}
],
"annotations": {
"com.example.key1": "value1",
"com.example.key2": "value2"
}
}
  • image index with multiple media types
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",
"size": 7143,
"digest": "sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f",
"platform": {
"architecture": "ppc64le",
"os": "linux"
}
},
{
"mediaType": "application/vnd.oci.image.index.v1+json",
"size": 7682,
"digest": "sha256:601570aaff1b68a61eb9c85b8beca1644e698003e0cdb5bce960f193d265a8b7"
}
],
"annotations": {
"com.example.key1": "value1",
"com.example.key2": "value2"
}
}

OCI Image Layout Specification 解读

通过 image layout 及 ref 即可构建一个 OCI Runtime Specification bundle:

  • 通过 image index 查询 manifest
  • 通过定义的 layers 使用 filesystem layers
  • 通过 image-confing.json 转换为 OCI Runtime Specification

内容解读

image layout 包含如下内容:

  • blogs 目录:
    • content-addressable blobs
    • blog has no schema
    • 目录必须存在但可能为空
  • oci-layout 文件
    • 必须存在
    • 必须为 JSON 对象
    • 必须包含 imageLayoutVersion 字段
    • 可能还包含其他字段
  • index.json 文件
    • 必须存在
    • 必须为 image index 对象

示例

1
2
3
4
5
6
7
$ cd example.com/app/
$ find . -type f
./index.json
./oci-layout
./blobs/sha256/3588d02542238316759cbf24502f4344ffcc8a60c803870022f335d1390c13b4
./blobs/sha256/4b0bc1c4050b03c95ef2a8e36e25feac42fd31283e8c30b3ee5df6b043155d3c
./blobs/sha256/7968321274dc6b6171697c33df7815310468e694ac5be0ec03ff053bb135e768

Blogs 解读

  • blogs 的目录中包含由SHA算法生成的各个包含实际内容的子目录组织而成。
  • blog/<alg>/<encoded>必须与 digest <alg>:<encoded> 相匹配,比如 content blobs/sha256/da39a3ee5e6b4b0d3255bfef95601890afd80709 必须与 digest sha256:da39a3ee5e6b4b0d3255bfef95601890afd80709 相匹配.
示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ cat ./blobs/sha256/9b97579de92b1c195b85bb42a11011378ee549b02d7fe9c17bf2a6b35d5cb079 | jq
{
"schemaVersion": 2,
"manifests": [
{
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"size": 7143,
"digest": "sha256:afff3924849e458c5ef237db5f89539274d5e609db5db935ed3959c90f1f2d51",
"platform": {
"architecture": "ppc64le",
"os": "linux"
}
},
...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ cat ./blobs/sha256/afff3924849e458c5ef237db5f89539274d5e609db5db935ed3959c90f1f2d51 | jq
{
"schemaVersion": 2,
"config": {
"mediaType": "application/vnd.oci.image.config.v1+json",
"size": 7023,
"digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270"
},
"layers": [
{
"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
"size": 32654,
"digest": "sha256:9834876dcfb05cb167a5c24953eba58c4ac89b1adf57f28f2f9d09af107ee8f0"
},
...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ cat ./blobs/sha256/5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270 | jq
{
"architecture": "amd64",
"author": "Alyssa P. Hacker <alyspdev@example.com>",
"config": {
"Hostname": "8dfe43d80430",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": false,
"AttachStderr": false,
"Tty": false,
"OpenStdin": false,
"StdinOnce": false,
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
],
"Cmd": null,
"Image": "sha256:6986ae504bbf843512d680cc959484452034965db15f75ee8bdd1b107f61500b",
...
}
}

oci-layout file

示例
1
2
3
{
"imageLayoutVersion": "1.0.0"
}

index.json file

示例
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
{
"schemaVersion": 2,
"mediaType": "application/vnd.oci.image.index.v1+json",
"manifests": [
{
"mediaType": "application/vnd.oci.image.index.v1+json",
"size": 7143,
"digest": "sha256:0228f90e926ba6b96e4f39cf294b2586d38fbb5a1e385c05cd1ee40ea54fe7fd",
"annotations": {
"org.opencontainers.image.ref.name": "stable-release"
}
},
{
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"size": 7143,
"digest": "sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f",
"platform": {
"architecture": "ppc64le",
"os": "linux"
},
"annotations": {
"org.opencontainers.image.ref.name": "v1.0"
}
},
{
"mediaType": "application/xml",
"size": 7143,
"digest": "sha256:b3d63d132d21c3ff4c35a061adf23cf43da8ae054247e32faa95494d904a007e",
"annotations": {
"org.freedesktop.specifications.metainfo.version": "1.0",
"org.freedesktop.specifications.metainfo.type": "AppStream"
}
}
],
"annotations": {
"com.example.index.revision": "r124356"
}
}

Image Layer Filesystem Changeset

+gzip Media Types

  • application/vnd.oci.image.layer.v1.tar+gzip
  • application/vnd.oci.image.layer.nondistributable.v1.tar+gzip

+zstd Media Types

  • application/vnd.oci.image.layer.v1.tar+zstd
  • application/vnd.oci.image.layer.nondistributable.v1.tar

Change Types

changes 类型:

  • Additions
  • Modifications
  • Removals

其中 Additions 和 Modifications 在改变集中是相同的表现,而 Removals 则通过 “whiteout” 标识。

File Types

  • regular files
  • directories
  • sockets
  • symbolic links
  • block devices
  • character devices
  • FIFOs

File Attributes

Additions 和 Modifications 必须包含额属性:

  • Modification Time(mtime)
  • User ID(uid)
    • User Name(uname)
  • Group ID(gid)
    • Group Name(gname)
  • Mode(mode)
  • Extended Attributes(xattrs)
  • Symlink reference(linkname + symbolic link type)
  • Hardlink reference(linkname)

Creating

Initial Root Filesystem

initial root 作为基础或者父 layer。如下示例只是为了配合演示,其中的 root filesystem 只是一个初始化状态,为一个空目录。
rootfs_c9d_v1/

Populate Initial Filesystem

目录和文件创建:

1
2
3
4
5
6
rootfs-c9d-v1/
etc/
my-app-config
bin/
my-app-binary
my-app-tools

rootfs-c9d_v1/ 归档为 tar 包,包含如下内容:

1
2
3
4
5
6
./
./etc/
./etc/my-app-config
./bin/
./bin/my-app-binary
./bin/my-app-tools

Populate a Comparison Filesystem

创建一个新的目录,其中初始化内容为 rootfs-c9d_v1/。能够保留文件属性的示例命令如下:

  • cp(1): cp -a rootfs-c9d-v1/ rootfs-c9d-v1.s1/
  • rsync(1): rsync -aHAX rootfs-c9d-v1/ rootfs-c9d-v1.s1/
  • tar(1): mkdir rootfs-c9d-v1.s1 && tar –acls –xattrs -C rootfs-c9d-v1/ -c . | tar -C rootfs-c9d-v1.s1/ –acls –xattrs -x (including –selinux where supported)

对 snapshot 的任何改变都不能改变或影响它所复制的目录。

如上 rootfs-c9d-v1.s1 作为 rootfs-c9d-v1 一个相同的快照,其将作为更新和改变做好准备。

Note: 写时复制或者联合文件系统可以高效地处理目录 snapshots

1
2
3
4
5
6
rootfs-c9d-v1.s1/
etc/
my-app-config
bin/
my-app-binary
my-app-tools

在此演示中,向 /etc/my-app.d 中添加一个默认的配置文件,同时移除现有的配置文件。另外对 ./bin/my-app-tools 的二进制文件做出改变(文件属性或者文件内容)来演示 layout 变化,变化后内容如下:

1
2
3
4
5
6
7
rootfs-c9d-v1.s1/
etc/
my-app.d/
default.cfg
bin/
my-app-binary
my-app-tools

Determining Changes

当两目录进行比对时,相对路径根目录是顶层目录,查找哪些内容被添加、修改或者删除。

上述演示比对结果如下:

1
2
3
4
Added:      /etc/my-app.d/
Added: /etc/my-app.d/default.cfg
Modified: /bin/my-app-tools
Deleted: /etc/my-app-config

Representing Changes

tar 归档文件将被创建且其中只会包含更改的文件集

其中 rootfs-c9d-v1.s1 显示如下:

1
2
3
4
./etc/my-app.d/
./etc/my-app.d/default.cfg
./bin/my-app-tools
./etc/.wh.my-app-config

需要注意的是当更改集生效的时候需要保证 ./etc/my-app-config 已经被删除了,其文件前缀为 .wh.

OCI Image Configuration

OCI image 是对 root filesystem 变化的有序集合且其中包含相应执行参数,可供容器运行使用。

关键术语

Layer
  • 镜像文件系统由 layers 构成
  • 每个 layer 对代表一组文件系统的变化,其格式为 tar 包,记录着文件的添加、修改或者删除
  • layers 中并不包含配置的元数据,比如环境变量或者参数,这些将作为一个完整镜像时的属性,而不是在每个layer中有体现
  • 使用基于层的或联合的文件系统,如AUFS,或通过计算文件系统快照的差异,文件系统的变化集可以被用来呈现一系列的image layers,就像它们是一个完整的文件系统
Image JSON
  • 每个镜像将有一个关联的 JSON 结构体用来描述一些关于镜像的基础信息,如创建日期、作者,同时也可以包含一些运行时的配置例如执行入口、默认参数、网络和volume卷
  • JSON 描述包含对每个 layer 加密哈希后的引用,用于对该镜像的历史信息的维护
  • JSON 应该是不可变的
  • 改变的话应该意味着一个新的派生镜像,而不是在原有的镜像上做出改变
Layer DiffID

layer 的 DiffID 是该层未压缩的 tar 归档的摘要,并以描述符摘要的格式进行序列化。

Layer ChainID

为方便起见,有时用一个标识符来指代一叠加的 layer 是很有用的。

ImageID

每个镜像的的ID是由其配置JSON的SHA256哈希值给出的。

属性

  • created | string | OPTIONAL
  • author | string | OPTIONAL
  • architecture | string | REQUIRED
  • os | string | REQUIRED
  • os.version | string | OPTIONAL
  • os.features | *array of strings | OPTIONAL
  • variant | string | OPTIONAL
  • config | object | OPTIONAL
    • User | string | OPTIONAL
    • ExposedPorts | object | OPTIONAL
    • Env | array of strings | OPTIONAL
    • Entrypoint | array of strings | OPTIONAL
    • Cmd | array of strings | OPTIONAL
    • Volume | object | OPTIONAL
    • WorkingDir | string | OPTIONAL
    • Labels | object | OPTIONAL
    • StopSignal | string | OPTIONAL
    • Memory | integer | OPTIONAL
    • CpuShares | integer | OPTIONAL
    • Healthcheck | object | OPTIONAL
  • rootfs | object | REQUIRED
    • created | string | OPTIONAL
    • author | string | OPTIONAL
    • created_by | string | OPTIONAL
    • comment | string | OPTIONAL
    • empty_layer | boolean | OPTIONAL

示例

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
{
"created": "2015-10-31T22:22:56.015925234Z",
"author": "Alyssa P. Hacker <alyspdev@example.com>",
"architecture": "amd64",
"os": "linux",
"config": {
"User": "alice",
"ExposedPorts": {
"8080/tcp": {}
},
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"FOO=oci_is_a",
"BAR=well_written_spec"
],
"Entrypoint": [
"/bin/my-app-binary"
],
"Cmd": [
"--foreground",
"--config",
"/etc/my-app.d/default.cfg"
],
"Volumes": {
"/var/job-result-data": {},
"/var/log/my-app-logs": {}
},
"WorkingDir": "/home/alice",
"Labels": {
"com.example.project.git.url": "https://example.com/project.git",
"com.example.project.git.commit": "45a939b2999782a3f005621a8d0f29aa387e1d6b"
}
},
"rootfs": {
"diff_ids": [
"sha256:c6f988f4874bb0add23a778f753c65efe992244e148a1d2ec2a8b664fb66bbd1",
"sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef"
],
"type": "layers"
},
"history": [
{
"created": "2015-10-31T22:22:54.690851953Z",
"created_by": "/bin/sh -c #(nop) ADD file:a3bc1e842b69636f9df5256c49c5374fb4eef1e281fe3f282c65fb853ee171c5 in /"
},
{
"created": "2015-10-31T22:22:55.613815829Z",
"created_by": "/bin/sh -c #(nop) CMD [\"sh\"]",
"empty_layer": true
},
{
"created": "2015-10-31T22:22:56.329850019Z",
"created_by": "/bin/sh -c apk add curl"
}
]
}

Conversion to OCI Runtime Configuration

  • 从 filesystem layers 提取 root filesystem
  • 将 image configuration blob 转换为 OCI Runtime configuration blob

属性比对表

Image Field Runtime Field Notes
Config.WorkingDir process.cwd
Config.Env process.env 1
Config.Entrypoint process.args 2
Config.Cmd process.args 2
Annotations Fields
Image Field Runtime Field Notes
os annotations 1,2
architecture annotations 1,3
variant annotations 1,4
os.version annotations 1,5
os.features annotations 1,6
author annotations 1,7
created annotations 1,8
Config.Labels annotations
Config.StopSignal annotations 1,9

Parsed Fields

Image Field Runtime Field
Config.User process.user.*

Optional Fields

Image Field Runtime Field Notes
Config.ExposedPorts annotations 1
Config.Volumes mounts 2

Annotations

关于注解的三种模式

  • Config.Labels -> configuration
  • annotations -> manifest
  • annotations -> image index

Descriptor

属性

  • mediaType | string | REQUIRED
  • digest | string | REQUIRED
  • size | int64 | REQUIRED
  • urls | array of strings | OPTIONAL
  • annotations | string-string-map | OPTIONAL
  • data | string | OPTIONAL
  • artifactType | string | OPTIONAL

Digests 摘要

摘要命名规范

1
2
3
4
5
digest                ::= algorithm ":" encoded
algorithm ::= algorithm-component (algorithm-separator algorithm-component)*
algorithm-component ::= [a-z0-9]+
algorithm-separator ::= [+._-]
encoded ::= [a-zA-Z0-9=_-]+
Digest 计算规则
1
2
3
4
let ID(C) = Descriptor.digest
let C = <bytes>
let D = '<alg>:' + Encode(H(C))
let verified = ID(C) == D

其中 H 为具体 哈希算法

已注册的算法
algorithm identifier algorithm
sha256 sha-256
SHA512 sha-512

示例

  • 包含基础信息的 Manifest
1
2
3
4
5
{
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"size": 7682,
"digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270"
}
  • 带有指定 url 的 Manifest
1
2
3
4
5
6
7
8
{
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"size": 7682,
"digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270",
"urls": [
"https://example.com/example-manifest"
]
}
  • 带有 artifact 的 manifest
1
2
3
4
5
6
{
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"size": 123,
"digest": "sha256:87923725d74f4bfb94c9e86d64170f7521aad8221a5de834851470ca142da630",
"artifactType": "application/vnd.example.sbom.v1"
}

最后的话

其实个人把此作为 containerd 源码分析的番外篇,但是对于 OCI Image 部分的理解还是很有必要的,除了 containerd 相关的 OCI Interface 的定义,下沉到更加底层的操作,其实就是对此标准的封装。