OCI Image Specification——Image Configuration

OCI Image Configuration

Posted by PYQ on October 31, 2023

OCI Image Configuration

Terminology

Layer

  • 镜像的文件系统由各镜像层组成
  • 每一层以基于tar的层形式表示此层对于父层的changeset,记录相对于其父层要添加、更改或删除的文件
  • 每一层没有配置元数据(image的config.json),如环境变量或默认参数,这些是整个镜像的属性,而不是任何特定层的属性
  • 通过使用基于层的文件系统或联合文件系统(如 AUFS和overlay2),或通过计算文件系统快照的差异,文件系统changeset可用于显示一系列镜像层,就好像它们是一个完整文件系统

Image JSON

也就是每个镜像的config.json文件。

  • 每个镜像都有一个关联的JSON结构,其中描述了镜像的一些基本信息,如创建日期、作者,以及执行/运行时配置,如入口点、默认参数、网络和卷
  • JSON结构还引用了镜像使用的每个层的加密哈希值,并提供了这些层的历史信息
  • 该JSON被认为是不可变的,因为更改它将改变计算出的ImageID
  • 更改它意味着创建一个新的镜像,而不是更改现有镜像

Layer DiffID

Layer DiffID是镜像层未压缩tar包的摘要,并以描述符摘要格式序列化,如 sha256:a9561eb1b190625c9adb5a9513e72c4dedafc1cb2d4c5236c9a6957ec7dfd5a9。应重复打包和解包图层,以避免更改图Layer DiffID,例如使用tar-split保存tar头文件。

注意:不要将Layer DiffID与layer digest(manifest中的)混淆,后者是压缩或未压缩内容的摘要

也就是说layer digest是对layer的MediaType(也就是说media type是tar+gzip,那digest就是layer的tar包经过gzip压缩后的内容的sha256,如果media type就是tar的话,diffid和digest就会一样)做sha256计算所得,而layer diffid是对其tar包做sha256计算

Layer ChainID

Layer DiffID标识单个changeset,而Layer ChainID则标识这些changeset的后续应用。这就确保了我们既能处理镜像层本身,也能处理一系列changeset的应用结果。在将镜像层应用到根文件系统时,可与rootfs.diff_ids结合使用,以唯一、安全地识别结果。

Definition

layer chainid的计算方式如下:

1
2
ChainID(L₀) =  DiffID(L₀)
ChainID(L₀|...|Lₙ₋₁|Lₙ) = Digest(ChainID(L₀|...|Lₙ₋₁) + " " + DiffID(Lₙ))

|:将右操作数应用于左操作数的结果。例如,给定base layer(基础层)A 和changeset(变更集)B,我们将B应用于A的结果称为A|B。digest为做sha256计算。

Explanation

假如有3层A、B和C,顺序从底到顶,A为最底层,C为最高层,三个层的chainID(准确来说,高层的chainid是其前面多个层相作用的结果,也就是说ChainID(A|B|C)和ChainID(C)是不一样的)计算方式如下:

1
2
3
4
5
ChainID(A) = DiffID(A)
ChainID(A|B) = Digest(ChainID(A) + " " + DiffID(B))
ChainID(A|B|C) = Digest(ChainID(A|B) + " " + DiffID(C))
// 等价于
ChainID(A|B|C) = Digest(Digest(DiffID(A) + " " + DiffID(B)) + " " + DiffID(C))

可以看到单个层(或者说最底层)的chainid等于其diffid,而多个层的chainid是前面多层的结果。

ImageID

每个镜像的ID等于对其config文件做SHA256计算得出的哈希值 。它以256位的十六进制编码表示,例如,sha256:a9561eb1b190625c9adb5a9513e72c4dedafc1cb2d4c5236c9a6957ec7dfd5a9。

镜像配置文件中包含了镜像中每个层的散列值(rootf.diff_ids),ImageID的这种计算方式使镜像具有内容可寻址性。

Properties

  • created string, OPTIONAL:创建镜像的日期和时间
  • author string, OPTIONAL:创建并负责维护镜像的个人或实体的姓名和/或电子邮件地址
  • architecture string, REQUIRED:运行镜像机器的CPU架构,GOARCH
  • os string, REQUIRED:运行镜像的操作系统名称,GOOS
  • os.version string, OPTIONAL:version与宿主机的version不匹配时,镜像可能无法运行
  • os.features array of strings, OPTIONAL
  • variant string, OPTIONAL:指定CPU架构的变体
  • config object, OPTIONAL:使用此镜像运行容器时的执行参数。此字段可以为空,在这种情况下,应在创建容器时指定任何执行参数
    • User string, OPTIONAL:貌似是指定运行镜像的uid
    • ExposedPorts object, OPTIONAL:运行此镜像的容器中暴露的一组端口
    • Env array of strings, OPTIONAL
    • Entrypoint array of strings, OPTIONAL:作为容器启动时要执行的命令的参数列表。这些值是默认值,也可以由创建容器时指定的入口点代替
    • Cmd array of strings, OPTIONAL:容器入口点的默认参数。这些值是默认值,可以用创建容器时指定的任何值代替。如果没有指定entrypoint值,那么Cmd数组的第一个条目应被解释为要运行的可执行文件,这两者也可结合起来一起用。entrypoint和cmd的区别
    • Volumes object, OPTIONAL:挂载卷的目录,也就是持久化数据的位置
    • WorkingDir string, OPTIONAL:设置容器中entrypoint进程的当前工作目录。此值为默认值,可由创建容器时指定的工作目录代替
    • Labels object, OPTIONAL:容器的相关的元数据
    • StopSignal string, OPTIONAL:该字段包含将发送给容器以退出的系统调用信号,例如 SIGKILL 或 SIGRTMIN+3
    • Memory integer, OPTIONAL
    • MemorySwap integer, OPTIONAL
    • CpuShares integer, OPTIONAL
    • Healthcheck object, OPTIONAL
  • rootfs object, REQUIRED:镜像所使用的各镜像层的内容地址(sha256),这也使得镜像配置文件的哈希值依赖于各镜像层的哈希值
    • type string, REQUIRED:固定为layers
    • diff_ids array of strings, REQUIRED:镜像层内容哈希值(DiffID)数组,顺序为从第一个到最后一个
  • history array of objects, OPTIONAL:描述每个镜像层的历史,顺序为从第一个到最后一个
    • created string, OPTIONAL
    • author string, OPTIONAL
    • created_by string, OPTIONAL:创建此镜像层的命令
    • comment string, OPTIONAL
    • empty_layer boolean, OPTIONAL:该字段用于标记历史项是否创建了文件系统差异。如果该历史项与 rootfs 部分的实际层不对应(例如,Dockerfile的ENV命令不会导致文件系统发生变化),则该字段将设为 true
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"
      }
    ]
}