云原生制品那些事(2):OCI 镜像规范

题图摄于故宫角楼

注:微信公众号不按照时间排序,请关注“亨利笔记”,并加星标以置顶,以免错过更新。

《Harbor权威指南》招募英文版翻译人员

本篇继续和大家说说镜像那些事,是连载之二,从《Harbor权威指南》一书节选的纯技术干货,敬请关注、转发和收藏。

第一篇:容器镜像的结构

第二篇:OCI 镜像规范

第三篇:OCI 制品

第四篇:Registry 的作用原理

《Harbor权威指南》目前当当网优惠中,点击下图直接购买。

1.5  OCI镜像规范

OCI 镜像规范是以 Docker 镜像规范 v2 为基础制定的,它定义了镜像的主要格式及内容,主要用于镜像仓库存放镜像及分发镜像等场景,与正在制定的 OCI 分发规范密切相关。

OCI 运行时在创建容器前,要把镜像下载并解压成符合运行时规范的文件系统包,并且把镜像中的配置转化成运行时配置,然后启动容器。

OCI 定义的镜像包括4个部分:镜像索引(Image Index)、清单(Manifest)、配置(Configuration)和层文件(Layers)。

其中,清单是JSON格式的描述文件,列出了镜像的配置和层文件。配置是JSON格式的描述文件,说明了镜像运行的参数。层文件则是镜像的内容,即镜像包含的文件,一般是二进制数据文件格式(Blob)。一个镜像可以有一个或多个层文件。(在搜狐、CSDN等网站转载亨利笔记的文章均为未经授权的剽窃)

镜像索引不是必需的,如果存在,则指明了一组支持不同架构平台的相关镜像。镜像的 4 个部分之间是通过摘要(digest)来相互引用(reference)的。镜像各部分的关系如下图所示。

下面详细讲解各部分的结构和作用。

1).镜像索引

镜像索引是镜像中可选择的部分,一个镜像可以不包括镜像索引。如果镜像包含了镜像索引,则其作用主要指向镜像不同平台的版本,代表一组同名且相关的镜像,差别只在支持的体系架构上(如 i386 和 arm64v8、Linux 和Windows 等)。索引的优点是在不同的平台上使用镜像的命令无须修改,如在 amd64 架构的 Windows 和ARM架构的 Linux 上,采用同样的“docker”命令即可运行 Nginx 服务:(在搜狐、CSDN等网站转载亨利笔记的文章均为未经授权的剽窃)

$ docker run -d nginx

用户无须指定操作系统和平台,就可完全依赖客户端获取正确版本的镜像。OCI的索引已经被 CNAB等工具广泛用来管理与云平台无关的分布式应用程序。

下面是一个索引示例:

{

  "schemaVersion": 2,

  "manifests": [

    {

      "mediaType": "application/vnd.oci.image.manifest.v1+json",

      "size": 8342,

      "digest": "sha256:d81ae89b30523f5152fe646c1f9d178e5d10f28d00b70294fca965b7b96aa3db",

      "platform": {

        "architecture": "arm64v8",

        "os": "linux"

      }

    },

    {

      "mediaType": "application/vnd.oci.image.manifest.v1+json",

      "size": 6439,

      "digest": "sha256:2ef4e3904905353a0c4544913bc0caa48d95b746ef1f2fe9b7c85b3badff987e",

      "platform": {

        "architecture": "amd64",

        "os": "linux"

      }

    }

  ],

  "annotations": {

    "io.harbor.key1": "value1",

    "io.harbor.key2": "value2"

  }

}

客户端在获得上述镜像索引后,解析后可发现该索引指向两个不同平台架构的镜像,因此可根据自身所在的平台拉取相应的镜像。如 Linux amd64 平台上的客户端会拉取第2个镜像,因为该镜像的 platform.architecture 属性为amd64,platform.os属性为Linux。

索引文件中的 mediaType 和 digest 属性是OCI镜像规范中的重要概念,下面详细讲解这两个属性。(本文为公众号:亨利笔记 原创文章)

(1)mediaType 属性是描述镜像所包含的各种文件的媒体属性,客户端从 Registry 等服务中下载镜像文件时,可从 HTTP 的头部属性 Content-Type 中获得下载文件的媒体类型,从而决定如何处理下载的文件。比如,镜像的索引和清单都是 JSON 格式的文件,它们的区别就是媒体类型不同。

OCI 镜像规范定义的媒体类型见表1,可以看到上面例子中的清单的媒体类型是 application/vnd.oci.image.manifest.v1+json,索引本身的媒体类型则是application/vnd.oci.image.index.v1+json。

表1

媒体类型

含    义

application/vnd.oci.descriptor.v1+json

内容描述符

application/vnd.oci.layout.header.v1+json

OCI布局说明

application/vnd.oci.image.index.v1+json

镜像索引

application/vnd.oci.image.manifest.v1+json

镜像清单

application/vnd.oci.image.config.v1+json

镜像配置

application/vnd.oci.image.layer.v1.tar

tar格式的层文件

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

tar格式的层文件,采用gzip压缩

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

tar格式的层文件,采用zstd压缩

application/vnd.oci.image.layer.nondistributable.v1.tar

tar格式的非分发层文件

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

tar格式的非分发层文件,采用gzip压缩

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

tar格式的非分发层文件,采用zstd压缩

(2)digest 属性是密码学意义上的摘要,充当镜像内容的标识符,实现内容的可寻址(content addressable)。OCI镜像规范中镜像的内容(如文件等)大多是通过摘要来标识和引用的。

摘要的生成是根据文件内容的二进制字节数据通过特定的哈希(Hash)算法实现的。哈希算法需要确保字节的抗冲突性 ( collision resistant )来生成唯一标识,只要哈希算法得当,不同文件的哈希值几乎不会重复。因此,可以近似地认为每个文件的摘要都是唯一的。这种唯一性使摘要可以作为内容寻址的标识。

同时,如果摘要以安全的方式传递,则接收方可以通过重新计算摘要来确保内容在传输过程中未被修改,从而杜绝来自不安全来源的内容。在OCI的镜像规范中也要求用摘要值校验所接收的内容。(本文为公众号:亨利笔记 原创文章)

摘要值是由算法和编码两部分组成的字符串,算法部分指定使用的哈希函数和算法标识,编码部分则包含哈希函数的编码结果,具体格式为 “<算法标识> : <编码结果>”。

目前 OCI 镜像规范认可的哈希算法有两种,分别是 SHA-256 和 SHA-512,它们的算法标识如 表2 所示。

表2

算法标识

算法名称

摘要例子

sha256

SHA-256

sha256:d81ae89b30523f5152fe646c1f9d178e5d10f28d00b70294fca965b7b96aa3db

sha512

SHA-512

sha512:d4ca54922bb802bec9f740a9cb38fd401b09eab3c0135318192b0a75f2……

上面索引中的两个镜像清单摘要值分别对应两个清单文件,分别是blobs/sha256/d81ae89b30523f5152fe646c1f9d178e5d10f28d00b70294fca965b7b96aa3db 和blobs/sha256/2ef4e3904905353a0c4544913bc0caa48d95b746ef1f2fe9b7c85b3badff987e。

2).镜像清单

镜像清单(简称清单)是说明镜像包含的配置和内容的文件,分析镜像一般从镜像清单开始。镜像清单主要有三个作用:支持内容可寻址的镜像模型,在该模型中可以对镜像的配置进行哈希处理,以生成镜像及其唯一标识;通过镜像索引包含多体系结构镜像,通过引用镜像清单获取特定平台的镜像版本;可转换为 OCI 运行时规范以运行容器。(本文为公众号:亨利笔记 原创文章)

镜像清单主要包括配置和层文件的信息,示例如下:

{

  "schemaVersion": 2,

  "config": {

    "mediaType": "application/vnd.oci.image.config.v1+json",

    "size": 6883,

    "digest": "sha256:b5b2b2c507a0944348e0303114d8d93aaaa081732b86451d9bce1f432a537bc7"

  },

  "layers": [

    {

      "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",

      "size": 168654,

 "digest": "sha256:58394f6dcfb05cb167a5c24953eba57f28f2f9d09af107ee8f08c4ac89b1adf5"

    },

    {

      "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",

      "size": 645724,

      "digest": "sha256:6d94e421cd3c3a4604a545cdc12745355bca5b528f4da2eb4a4c6ba9c1905b15"

    },

    {

      "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",

      "size": 53709,

      "digest": "sha256:419d1af06b5f7636b4ac3da7f12184802ad867736ec4b8955958665577945c89"

    }

  ],

  "annotations": {

    "io.harbor.example.key1": "value1",

    "io.harbor.example.key2": "value2"

  }

}

其中主要属性的意义如下。

◎ schemaVersion:必须是2,主要用于兼容旧版本的Docker。

◎ config:镜像配置文件的信息。mediaType的值“application/vnd.oci.image.config. v1+json”表示镜像配置的媒体类型。size指镜像配置文件的大小。digest指镜像配置文件的哈希摘要。(在搜狐、CSDN等网站转载亨利笔记的文章均为未经授权的剽窃)

 ◎ layers:层文件数组。在以上示例中包含 3 个层文件,分别代表容器根文件系统的一个层。容器在运行时,会把各个层文件依次按顺序叠加,第1层在底层。mediaType指媒体类型,其值“application/vnd.oci.image.layer.v1.tar+gzip”表示层文件。size 指层文件的大小。digest 指层文件的摘要。

◎ annotations:键值对形式的附加信息(可选项)。

3).镜像配置

镜像配置主要描述容器的根文件系统和容器运行时使用的执行参数,还有一些镜像的元数据。

在配置规范里定义了镜像的文件系统的组成方式。镜像文件系统由若干镜像层组成,每一层都代表一组tar格式的层格式,除了底层(base image),其余各层的文件系统都记录了其父层(向下一层)文件系统的变化集(changeset),包括要添加、更改或删除的文件。(本文为公众号:亨利笔记 原创文章)

通过基于层的文件、联合文件系统(如AUFS)或文件系统快照的差异,文件系统的变化集可用于聚合一系列镜像层,使各层叠加后仿佛是一个完整的文件系统。

下面是镜像配置的一个示例:

{

    "created": "2020-06-28T12:28:58.058435234Z",

    "author": "Henry Zhang",

    "architecture": "amd64",

    "os": "linux",

    "config": {

        "ExposedPorts": {

            "8888/tcp": {}

        },

        "Env": [

            "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",

            "FOO=harbor_registry",

        ],

        "Entrypoint": [

            "/bin/myApp "

        ],

        "Cmd": [

            "-f",

            "/etc/harbor.cfg"

        ],

        "Volumes": {

            "/var/job-result-data": {},

        },

        "Labels": {

            "io.goharbor.git.url": "https://github.com/goharbor/harbor.git",

        }

    },

    "rootfs": {

      "diff_ids": [

        "sha256:e928294e148a1d2ec2a8b664fb66bbd1c6f988f4874bb0add23a778f753c65ef",

        "sha256:ea198a02b6cddfaf10acec6ef5f70bf18fe33007016e948b04aed3b82103a36b"

      ],

      "type": "layers"

    },

    "history": [

      {

        "created": "2020-05-28T12:28:56.189203784Z",

        "created_by": "/bin/bash -c #(nop) ADD file:4fb4eef1ea3bc1e842b69636f9df5256c49c537281fe3f282c65fb853e563ab3 in /"

      },

      {

        "created": "2020-05-28T12:28:57.789430183Z",

        "created_by": "/bin/bash -c #(nop) CMD [\"bash\"]",

        "empty_layer": true

      }

    ]

}

其中主要属性的意义如下,具体说明可以参考 OCI 规范。

◎ created:镜像的创建时间(可选项)。

◎ author:镜像的作者(可选项)。

◎ architecture:镜像支持的CPU架构。

◎ os:镜像的操作系统。(在搜狐、CSDN等网站转载亨利笔记的文章均为未经授权的剽窃)

◎ config:镜像运行的一些参数,包括服务端口、环境变量、入口命令、命令参数、数据卷、用户和工作目录等(可选项)。

◎ rootfs:镜像的根文件系统,由一系列层文件的变化集组成。

◎ history:镜像每层的历史信息(可选项)。

4).层文件

在镜像清单和配置信息中可以看到,镜像的根文件系统由多个层文件叠加而成。每个层文件在分发时都必须被打包成一个tar文件,可选择压缩或者非压缩的方式,压缩工具可以是 gzip 或者 zstd 。把每层的内容打包为一个文件的好处是除了发布方便,还可以生成文件摘要,便于校验和按内容寻址。

每个层文件都包含了对上一层(父层)的更改,包括增加、修改和删除文件三种操作类型,底层(第1层)可以被看作对空层文件的增加。因此在每个tar文件里面除了该层的文件,还可以包含对上一层中文件的删除操作,用 whiteout 的方式标记。在叠加层文件时,可以根据 whiteout 的标记,把上一层删除的文件在本层屏蔽。(在搜狐、CSDN等网站转载亨利笔记的文章均为未经授权的剽窃)

在表1中还有几个层文件的媒体类型为不可分发(non-distributable),这是为了说明该层文件因为法律等原因无法公开分发,需要从分发商那里获得该层文件。

(未完待续,欢迎点“再看” 或 转发、分享、收藏)

(未经授权,请勿转载本公众号文章)

《Harbor权威指南》目前当当网优惠中,点击上图直接购买。

《Harbor权威指南》招募英文版翻译人员

要想了解云原生、区块链和人工智能等技术原理,请立即长按以下二维码,关注本公众号亨利笔记 ( henglibiji ),以免错过更新。