概述
OCI镜像规范定义一个镜像包含一个manifest、一个image index(可选)、一组文件系统layer和一个配置文件。
OCI 镜像规范的主要目的:
统一标准化容器镜像格式,让标准镜像能够在各容器软件下构建、传递及准备容器镜像运行。
Image 规范的高层组件包含:
-
Image Manifest 清单文件是组成一个容器 image 的组件描述文档
-
Image Index (可选) 更高层的manifest文件,指向一组 manifests 和 descriptors 列表
-
Image Layout 镜像布局,表示一个镜像的内容布局
-
Filesystem Layer 描述一个容器文件系统的一个"变化集合"
-
Image Configuration 配置文件包含如应用参数、环境等信息
-
Conversion 如何解析的描述文档
-
Descriptor 描述了类型、元数据和内容寻址的格式描述文件
-
Signatures (可选 ) 签名是基于签名的图像内容的地址
-
Naming (可选 ) 命名是联合基于DNS,可以授权
OCI Media Type 文件类型:
Media Type | 说明 |
---|---|
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 | Image Layer 镜像层文件 |
application/vnd.oci.image.layer.v1.tar+gzip | Image Layer 镜像层文件gzip压缩 |
application/vnd.oci.image.layer.nondistributable.v1.tar | Image Layer 非内容寻址管理 |
application/vnd.oci.image.layer.nondistributable.v1.tar+gzip | Image Layer, gzip压缩 非内容寻址管理 |
类型关系图
media-types.pngOCI Content Descriptor
OCI 镜像由几部分组成,每个组件都是存储在一个目录结构中。每个组件之间都是通过内容寻址的方式互相引用。Content Descriptor 描述了一个目标内容的位置。Content Descriptor 包含:内容的类型、内容的标识符、内容的大小。Content Descriptor 必须通过嵌入到其他格式中使用。
Content Descriptor 属性
- mediaType string
- digest string
- size int64
- urls array of strings
- annotations string-string map
- data string (保留字段)
摘要和校验
digest属性是Descriptor的核心,扮演了内容的标识符的角色。digest使用了防碰撞的哈希算法唯一的标识了内容。如果标识符能够以安全的获取。那么就算内容通过了不安全的来源获取,也能独立计算出标识符对内容的正确性进行确认。
digest的格式要求与示例:(sha256已经是一个广泛使用的Hash算法)
digest := algorithm ":" hex
algorithm := /[a-z0-9_+.-]+/
hex := /[a-f0-9]+/
sha256:6c3c624b58dbbcd3c0dd82b4c53f04194d1247c6eebdaab7c610cf7d66709b3b
Content Descriptor 示例
这个 Content Descriptor 描述了一个Manifest
{
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"size": 7682,
"digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270"
}
OCI Image Manifest 镜像清单格式规范
Manifest 的三大主要目标:
-
内容可寻址(通过 hash 算法为镜像和它的组件生成唯一ID)
-
支持多种平台的架构镜像(由一个更上层的 manifest 说明包含的镜像 manifests 其具体平台的版本情况)
-
能被解析成为 OCI 运行时规范
image manifest 包括一个 Image Config 和 一组 Image Layers 。
application/vnd.oci.image.manifest.v1+json
定义:
字段名 | 类型 | 必须 | 说明 |
---|---|---|---|
schemaVersion | int | 必须 | 当前版本 |
mediaType | string | 保留 | 用于标识当前文件是什么类型的 |
config | descriptor | 必须 | 用于标识镜像的配置文件的位置<br />application/vnd.oci.image.config.v1+json |
layers | array of descriptor | 列表中的每一个成员都是一个镜像层,最底层的镜像在列表的第一项,其他各层按照堆叠顺序依次排列在后面。最终得到的文件系统应该和在一个空文件夹上堆叠各层得到的结果一样。用于堆叠的空文件夹的各种权限是不确定的。镜像层的mediaType,有比较多的选择: application/vnd.oci.image.layer.v1.tar application/vnd.oci.image.layer.v1.tar+gzip application/vnd.oci.image.layer.nondistributable.v1.tar application/vnd.oci.image.layer.nondistributable.v1.tar+gzip |
|
annotations | string-string map | 注释 |
manifest 示例:
{
"schemaVersion": 2,
"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:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f"
},
{
"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"
}
],
"annotations": {
"com.example.key1": "value1",
"com.example.key2": "value2"
}
}
OCI Image Index 镜像索引格式规范
Image Index是一个更高层次的Manifest,一般在一个镜像需要提供多个平台支持时使用。
MediaType:application/vnd.oci.image.index.v1+json
字段名 | 类型 | 必须 | 说明 |
---|---|---|---|
schemaVersion | int | 必须 | 必须填2(为了兼容老版本的Docker) |
mediaType | string | 保留 | 用于标识当前文件是什么类型的 |
manifests | array of objects | 必须 | 给出了对于特定平台的Manifests列表 |
mediaType string Manifests | string | application/vnd.oci.image.manifest.v1+json | |
platform | object | 可选 | 描述了Manifest中描述的镜像是运行 在哪一个指定的平台 |
architecture | string | 必须 | 指定了CPU的架构类型 |
os | string | 必须 | 指定了操作系统的类型 |
os.version | string | 可选 | 指定操作系统的版本要求 |
os.features | array of strings | 可选 | 指定了一些对于系统的特殊要求 |
variant | string | 可选 | 指定了CPU的版本要求 |
features | array of strings | 可选 | 指定了一些对于CPU指令集的特殊要求。 (比如sse4、aes) |
annotations | string-string map | 注解 |
index 示例:
{
"schemaVersion": 2,
"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",
"os.features": [
"sse4"
]
}
}
],
"annotations": {
"com.example.key1": "value1",
"com.example.key2": "value2"
}
}
OCI Image Layout 镜像布局规范
OCI的镜像布局是指内容寻址(blobs)和位置寻址(refs)的目录结构。这个布局可以使用多种方式传输:归档文件如tar/zip、共享文件系统环境如nfs、网络传输如http/ftp/rsync等
镜像布局有以下部分组成:
- blobs directory 内容寻址的块文件,目录必须存在,但是可以为空。
- oci-layout file 文件必须存在、必须是JSON格式,文件中必须包含一个imageLayoutVersion字段文件中可以有其他字段作为扩展使用
- index.json file 文件必须存在、必须是JSON格式,文件中必须包含镜像Index的基本属性
示例
$ cd example.com/app/
$ find . -type f
./index.json
./oci-layout
./blobs/sha256/3588d02542238316759cbf24502f4344ffcc8a60c803870022f335d1390c13b4
./blobs/sha256/4b0bc1c4050b03c95ef2a8e36e25feac42fd31283e8c30b3ee5df6b043155d3c
./blobs/sha256/7968321274dc6b6171697c33df7815310468e694ac5be0ec03ff053bb135e768
blob文件名命名是通过内容计算的shasum
$ shasum -a 256 ./blobs/sha256/afff3924849e458c5ef237db5f89539274d5e609db5db935ed3959c90f1f2d51
afff3924849e458c5ef237db5f89539274d5e609db5db935ed3959c90f1f2d51 ./blobs/sha256/afff3924849e458c5ef237db5f89539274d5e609db5db935ed3959c90f1f2d51
Index.json 文件
index.json 文件相当于整个镜像的入口。从这个文件可以获取整个镜像依赖到的所有文件的信息。 每一个在manifests 字段中的 descriptor 都指向一个 application/vnd.oci.image.index.v1+json 或 application/vnd.oci.image.manifest.v1+json 类型的文件。
一个通用的做法,org.opencontainers.image.ref.name 注解被认为是镜像 Tag 的含义。表示镜像的不同版本。
index.json 示例
{
"schemaVersion": 2,
"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"
}
}
OCI-layout 文件
在镜像规范中,这个文件特别简单,只有一个布局版本的字段。
oci-layout 示例
{
"imageLayoutVersion": "1.0.0"
}
Blobs 文件夹
blobs文件夹下的子文件夹是以Hash算法的名称来命名的,这些子文件夹下包含了真正的实体文件。
一个块被digest引用时(:,descriptor),这个块必须存放在blobs/<alg>/<hex>目录。
blobs文件夹下面可能会存放很多已经没有任何引用的块文件。
blobs文件夹下可以缺失一些被引用的块文件,只要被其他额外的块填满就可以了。
Blobs 示例 Image Index
$ 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"
}
},
...
Blobs 示例 Image Manifest
$ 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:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f"
},
...
Blobs 示例 Image Config
$ 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",
...
Image Layer Blob
$ cat ./blobs/sha256/e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f
[tar stream]
Image Layer Filesystem Changeset 镜像层规范
如何序列化 文件系统 以及 文件系统的改变(如删除文件) 到一个 blob 文件称之为" layer ",由一个或多个layer层叠加构成一个完整的文件系统。
Image Filesystem Changeset文件系统改变集
每一个layer都会有对它相对于父layer增、删、改的文件的一个存档,使用诸如AUFS等联合挂载文件系统,可以将一系列的image layer以单一的文件系统的形式呈现出来。
Change type 文件系统改变类型
Additions
Modifications
Removals
创建 Image Filesystem Changeset
-
创建根文件系统
根文件系统是基础或父 layer , 此处初始为空目录结构,使用唯一的目录名rootfs-c9d-v1
rootfs-c9d-v1/
- 创建初始文件与目录
rootfs-c9d-v1/
etc/
my-app-config
bin/
my-app-binary
my-app-tools
rootfs-c9d-v1 目录会被作为以下文件的打包存档的入口(tar)
./
./etc/
./etc/my-app-config
./bin/
./bin/my-app-binary
./bin/my-app-tools
-
复制或快照初始文件系统
以前面的根文件系统复制或快照,创建一个新的目录和初始化文件系统,目录名为rootfs-c9d-v1.s1
rootfs-c9d-v1.s1/
etc/
my-app-config
bin/
my-app-binary
my-app-tools
增加一个./etc/my-app.d目录包含default.cfg文件,删除存在的my-app-config文件。与此同时,改变./bin/my-app-tools二进制文件内容或属性。
rootfs-c9d-v1.s1/
etc/
my-app.d/
default.cfg
bin/
my-app-binary
my-app-tools
-
检测变化和表示变化项
通过两个目录进行比较("rootfs-c9d-v1" VS "rootfs-c9d-v1.s1"),查询文件是否有被增加、修改、删除操作
Added: /etc/my-app.d/
Added: /etc/my-app.d/default.cfg
Modified: /bin/my-app-tools
Deleted: /etc/my-app-config
- 创建 changeset 文件
打包 tar 文件仅包含 changeset :
- 增加与修改的文件和目录完整打包
- 删除的文件或目录由whiteout文件(.wh.命名)来标识
./etc/my-app.d/
./etc/my-app.d/default.cfg
./bin/my-app-tools
./etc/.wh.my-app-config
OCI Runtime Filesystem Bundle 运行时文件系统包
OCI Image 可通过镜像名称发现、下载、hash值来验证、签名受信以及解包成 OCI 运行时 Bundle。一个标准的容器 bundle 包含容器加载和运行所需的所有信息,其包含如下:
- config.json 容器配置数据文件,必须与文件系统同目录下,文件名必须为config.json
- root filesystem 容器 root 文件系统(启动的只读文件),在 config.json 文件内有指定 root.path。
config.json 配置文件说明
config样例:
{
"ociVersion": "1.0.1",
"process": {
"terminal": true,
"user": {
"uid": 1,
"gid": 1,
"additionalGids": [
5,
6
]
},
"args": [
"sh"
],
"env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"TERM=xterm"
],
"cwd": "/",
"capabilities": {
"bounding": [
"CAP_AUDIT_WRITE",
"CAP_KILL",
"CAP_NET_BIND_SERVICE"
],
"permitted": [
"CAP_AUDIT_WRITE",
"CAP_KILL",
"CAP_NET_BIND_SERVICE"
],
"inheritable": [
"CAP_AUDIT_WRITE",
"CAP_KILL",
"CAP_NET_BIND_SERVICE"
],
"effective": [
"CAP_AUDIT_WRITE",
"CAP_KILL"
],
"ambient": [
"CAP_NET_BIND_SERVICE"
]
},
"rlimits": [
{
"type": "RLIMIT_CORE",
"hard": 1024,
"soft": 1024
},
{
"type": "RLIMIT_NOFILE",
"hard": 1024,
"soft": 1024
}
],
"apparmorProfile": "acme_secure_profile",
"oomScoreAdj": 100,
"selinuxLabel": "system_u:system_r:svirt_lxc_net_t:s0:c124,c675",
"noNewPrivileges": true
},
"root": {
"path": "rootfs",
"readonly": true
},
"hostname": "slartibartfast",
"mounts": [
{
"destination": "/proc",
"type": "proc",
"source": "proc"
},
{
"destination": "/dev",
"type": "tmpfs",
"source": "tmpfs",
"options": [
"nosuid",
"strictatime",
"mode=755",
"size=65536k"
]
},
{
"destination": "/dev/pts",
"type": "devpts",
"source": "devpts",
"options": [
"nosuid",
"noexec",
"newinstance",
"ptmxmode=0666",
"mode=0620",
"gid=5"
]
},
{
"destination": "/dev/shm",
"type": "tmpfs",
"source": "shm",
"options": [
"nosuid",
"noexec",
"nodev",
"mode=1777",
"size=65536k"
]
},
{
"destination": "/dev/mqueue",
"type": "mqueue",
"source": "mqueue",
"options": [
"nosuid",
"noexec",
"nodev"
]
},
{
"destination": "/sys",
"type": "sysfs",
"source": "sysfs",
"options": [
"nosuid",
"noexec",
"nodev"
]
},
{
"destination": "/sys/fs/cgroup",
"type": "cgroup",
"source": "cgroup",
"options": [
"nosuid",
"noexec",
"nodev",
"relatime",
"ro"
]
}
],
"hooks": {
"prestart": [
{
"path": "/usr/bin/fix-mounts",
"args": [
"fix-mounts",
"arg1",
"arg2"
],
"env": [
"key1=value1"
]
},
{
"path": "/usr/bin/setup-network"
}
],
"poststart": [
{
"path": "/usr/bin/notify-start",
"timeout": 5
}
],
"poststop": [
{
"path": "/usr/sbin/cleanup.sh",
"args": [
"cleanup.sh",
"-f"
]
}
]
},
"linux": {
"devices": [
{
"path": "/dev/fuse",
"type": "c",
"major": 10,
"minor": 229,
"fileMode": 438,
"uid": 0,
"gid": 0
},
{
"path": "/dev/sda",
"type": "b",
"major": 8,
"minor": 0,
"fileMode": 432,
"uid": 0,
"gid": 0
}
],
"uidMappings": [
{
"containerID": 0,
"hostID": 1000,
"size": 32000
}
],
"gidMappings": [
{
"containerID": 0,
"hostID": 1000,
"size": 32000
}
],
"sysctl": {
"net.ipv4.ip_forward": "1",
"net.core.somaxconn": "256"
},
"cgroupsPath": "/myRuntime/myContainer",
"resources": {
"network": {
"classID": 1048577,
"priorities": [
{
"name": "eth0",
"priority": 500
},
{
"name": "eth1",
"priority": 1000
}
]
},
"pids": {
"limit": 32771
},
"hugepageLimits": [
{
"pageSize": "2MB",
"limit": 9223372036854772000
},
{
"pageSize": "64KB",
"limit": 1000000
}
],
"memory": {
"limit": 536870912,
"reservation": 536870912,
"swap": 536870912,
"kernel": -1,
"kernelTCP": -1,
"swappiness": 0,
"disableOOMKiller": false
},
"cpu": {
"shares": 1024,
"quota": 1000000,
"period": 500000,
"realtimeRuntime": 950000,
"realtimePeriod": 1000000,
"cpus": "2-3",
"mems": "0-7"
},
"devices": [
{
"allow": false,
"access": "rwm"
},
{
"allow": true,
"type": "c",
"major": 10,
"minor": 229,
"access": "rw"
},
{
"allow": true,
"type": "b",
"major": 8,
"minor": 0,
"access": "r"
}
],
"blockIO": {
"weight": 10,
"leafWeight": 10,
"weightDevice": [
{
"major": 8,
"minor": 0,
"weight": 500,
"leafWeight": 300
},
{
"major": 8,
"minor": 16,
"weight": 500
}
],
"throttleReadBpsDevice": [
{
"major": 8,
"minor": 0,
"rate": 600
}
],
"throttleWriteIOPSDevice": [
{
"major": 8,
"minor": 16,
"rate": 300
}
]
}
},
"rootfsPropagation": "slave",
"seccomp": {
"defaultAction": "SCMP_ACT_ALLOW",
"architectures": [
"SCMP_ARCH_X86",
"SCMP_ARCH_X32"
],
"syscalls": [
{
"names": [
"getcwd",
"chmod"
],
"action": "SCMP_ACT_ERRNO"
}
]
},
"namespaces": [
{
"type": "pid"
},
{
"type": "network"
},
{
"type": "ipc"
},
{
"type": "uts"
},
{
"type": "mount"
},
{
"type": "user"
},
{
"type": "cgroup"
}
],
"maskedPaths": [
"/proc/kcore",
"/proc/latency_stats",
"/proc/timer_stats",
"/proc/sched_debug"
],
"readonlyPaths": [
"/proc/asound",
"/proc/bus",
"/proc/fs",
"/proc/irq",
"/proc/sys",
"/proc/sysrq-trigger"
],
"mountLabel": "system_u:object_r:svirt_sandbox_file_t:s0:c715,c811"
},
"annotations": {
"com.example.key1": "value1",
"com.example.key2": "value2"
}
}
~~ 本文 END ~~
网友评论