背景

2020 年 12 月初,Kubernetes 在其最新的 Changelog 中宣布,自 Kubernetes 1.20 之后将弃用 Docker 作为容器运行时。

弃用 Docker 带来的,可能是一系列的改变,包括不限于:

  • 容器镜像构建工具
  • 容器 CLI
  • 容器镜像仓库
  • 容器运行时

专题文章《K8S 1.20 弃用 Docker 评估》会从多方面分析由此带来的变动和影响,今天先介绍镜像格式的改变。

Docker 镜像仍然可以使用吗?

是的,可以使用。Docker 较新版本生成的镜像实际上并不是特定于 Docker 的镜像,而是 OCI(Open Container Initiative)镜像。无论你使用什么工具构建镜像,任何符合 OCI 标准的镜像在 Kubernetes 看来都是一样的。containerd 和 CRI-O 都能够提取这些镜像并运行它们。所以您可以仍然使用 Docker 来构建容器镜像,并且可以继续在 containerd 和 CRI-O 上使用。

那为什么发现 Docker 镜像和 Containerd 镜像存在不兼容情况?

具体如下:在 K8S > 1.20 版本中,发现 containerd ctr 上传到镜像仓库的镜像与同版本的 docker 镜像间存在以下问题

  1. 不能被 docker 使用
  2. docker push不能覆盖

根本原因还是在于镜像格式的差别。下面做详细解释。

Docker 和 OCI 镜像格式的差别?

目前有以下几种容器镜像格式:

Docker V1 镜像

{% note danger %}

严重警告

Docker V1 格式早已弃用,请不要再使用!!!

{% note info %}

引用

  1. 版本 1.8.3 增加了一个标志(--disable-legacy-registry=false),用于阻止 docker 守护进程对 v1 镜像注册表进行拉、推和登录操作。尽管默认情况下是启用的,但这表示不赞成 v1 协议。
  2. 自 2017 年 2 月 28 日起,随着 Docker v1.13 的发布,Docker Engine 不再支持 v1 协议
  3. 从 Docker 17.12 开始,对 V1 镜像注册表的支持已经被删除,并且 --disable-legacy-registry 标志不再使用,当设置该标志时 dockerd 时将无法启动。

Docker V2 镜像简介

Docker V2 镜像清单(Image Manifest)是 Docker 的 V2 版本容器映像规范,它允许多架构镜像并支持内容可寻址映像。

从 2017 年 2 月 28 日开始,Docker V2 镜像注册表规范取代了 Docker V1 规范。Docker V1 规范已被弃用,并且 Docker V1 映像不能再用于 Container Registry。

为支持内容可寻址镜像并简化镜像图层的跟踪,Docker V2 对 Docker 镜像格式进行了一系列更改。 Docker V2 镜像清单包含镜像图层的所有内容地址(“摘要”),而 Docker V1 映像则不包含这些信息。

Docker Image Manifest V2 Schema 1

下面简单介绍下 V2 Schema 1 镜像清单的格式。V2 Schema 1 它是一个临时清单,提供与 V1 Image 格式的兼容性,V2 Schema 2 才是当前 Docker 镜像格式的最终格式。

{% note default %}

备注

V2 Schema 1 由于需要与 V1 的向后兼容性原因,它比 V2.2(即 Docker Image Manifest V2 Schema 2) 更复杂。

镜像清单描述了一个 Docker 镜像的各种组成部分。镜像清单可以序列化为 JSON 格式与以下媒体类型:

清单类型(Manifest Type) 媒体类型(Media Type)
manifest “application/vnd.docker.distribution.manifest.v1+json”
signed manifest “application/vnd.docker.distribution.manifest.v1+prettyjws”

清单格式不详细介绍,下面通过示例说明:

示例清单格式

{
"name": "hello-world",
"tag": "latest",
"architecture": "amd64",
"fsLayers": [
{
"blobSum": "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef"
},
{
"blobSum": "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef"
},
{
"blobSum": "sha256:cc8567d70002e957612902a8e985ea129d831ebe04057d88fb644857caa45d11"
},
{
"blobSum": "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef"
}
],
"history": [
{
"v1Compatibility": "{\"id\":\"e45a5af57b00862e5ef5782a9925979a02ba2b12dff832fd0991335f4a11e5c5\",\"parent\":\"31cbccb51277105ba3ae35ce33c22b69c9e3f1002e76e4c736a2e8ebff9d7b5d\",\"created\":\"2014-12-31T22:57:59.178729048Z\",\"container\":\"27b45f8fb11795b52e9605b686159729b0d9ca92f76d40fb4f05a62e19c46b4f\",\"container_config\":{\"Hostname\":\"8ce6509d66e2\",\"Domainname\":\"\",\"User\":\"\",\"Memory\":0,\"MemorySwap\":0,\"CpuShares\":0,\"Cpuset\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\"],\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) CMD [/hello]\"],\"Image\":\"31cbccb51277105ba3ae35ce33c22b69c9e3f1002e76e4c736a2e8ebff9d7b5d\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":[],\"SecurityOpt\":null,\"Labels\":null},\"docker_version\":\"1.4.1\",\"config\":{\"Hostname\":\"8ce6509d66e2\",\"Domainname\":\"\",\"User\":\"\",\"Memory\":0,\"MemorySwap\":0,\"CpuShares\":0,\"Cpuset\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\"],\"Cmd\":[\"/hello\"],\"Image\":\"31cbccb51277105ba3ae35ce33c22b69c9e3f1002e76e4c736a2e8ebff9d7b5d\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":[],\"SecurityOpt\":null,\"Labels\":null},\"architecture\":\"amd64\",\"os\":\"linux\",\"Size\":0}\n"
},
{
"v1Compatibility": "{\"id\":\"e45a5af57b00862e5ef5782a9925979a02ba2b12dff832fd0991335f4a11e5c5\",\"parent\":\"31cbccb51277105ba3ae35ce33c22b69c9e3f1002e76e4c736a2e8ebff9d7b5d\",\"created\":\"2014-12-31T22:57:59.178729048Z\",\"container\":\"27b45f8fb11795b52e9605b686159729b0d9ca92f76d40fb4f05a62e19c46b4f\",\"container_config\":{\"Hostname\":\"8ce6509d66e2\",\"Domainname\":\"\",\"User\":\"\",\"Memory\":0,\"MemorySwap\":0,\"CpuShares\":0,\"Cpuset\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\"],\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) CMD [/hello]\"],\"Image\":\"31cbccb51277105ba3ae35ce33c22b69c9e3f1002e76e4c736a2e8ebff9d7b5d\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":[],\"SecurityOpt\":null,\"Labels\":null},\"docker_version\":\"1.4.1\",\"config\":{\"Hostname\":\"8ce6509d66e2\",\"Domainname\":\"\",\"User\":\"\",\"Memory\":0,\"MemorySwap\":0,\"CpuShares\":0,\"Cpuset\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\"],\"Cmd\":[\"/hello\"],\"Image\":\"31cbccb51277105ba3ae35ce33c22b69c9e3f1002e76e4c736a2e8ebff9d7b5d\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":[],\"SecurityOpt\":null,\"Labels\":null},\"architecture\":\"amd64\",\"os\":\"linux\",\"Size\":0}\n"
},
],
"schemaVersion": 1,
"signatures": [
{
"header": {
"jwk": {
"crv": "P-256",
"kid": "OD6I:6DRK:JXEJ:KBM4:255X:NSAA:MUSF:E4VM:ZI6W:CUN2:L4Z6:LSF4",
"kty": "EC",
"x": "3gAwX48IQ5oaYQAYSxor6rYYc_6yjuLCjtQ9LUakg4A",
"y": "t72ge6kIA1XOjqjVoEOiPPAURltJFBMGDSQvEGVB010"
},
"alg": "ES256"
},
"signature": "XREm0L8WNn27Ga_iE_vRnTxVMhhYY0Zst_FfkKopg6gWSoTOZTuW4rK0fg_IqnKkEKlbD83tD46LKEGi5aIVFg",
"protected": "eyJmb3JtYXRMZW5ndGgiOjY2MjgsImZvcm1hdFRhaWwiOiJDbjAiLCJ0aW1lIjoiMjAxNS0wNC0wOFQxODo1Mjo1OVoifQ"
}
]
}

{% note warning %}

注意

随着 Docker Image Manifest V2 Schema 2 的发布,Docker Image Manifest V2 Schema 1 已被弃用。这可能导致尚未更新到 Docker Image Manifest V2 Schema 2 的镜像存在兼容性和漏洞问题。

Docker Image Manifest V2 Schema 2

然后介绍 V2 Schema 2 的格式。

V2 Schema 2 版本有两个主要目标。第一种是允许多架构镜像,通过“胖清单”引用特定于平台版本的镜像的镜像清单。第二种方法是将 Docker 引擎转向可内容寻址的图像,方法是支持一个镜像模型,在该模型中,可以对镜像的配置进行哈希,以生成镜像的 ID。

清单列表(Manifest List)

清单列表是 Docker V2 Schema 2 和 OCI 镜像的一部分。

利用清单列表,您可以使用单个摘要或标记来表示映像的多种形式。

示例清单列表(Manifest List)

{
"schemaVersion": 2,
"mediaType": "application/vnd.docker.distribution.manifest.list.v2+json",
"manifests": [
{
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"size": 7143,
"digest": "sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f",
"platform": {
"architecture": "ppc64le",
"os": "linux",
}
},
{
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"size": 7682,
"digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270",
"platform": {
"architecture": "amd64",
"os": "linux",
"features": [
"sse4"
]
}
}
]
}
镜像清单

示例镜像清单

{
"schemaVersion": 2,
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"config": {
"mediaType": "application/vnd.docker.container.image.v1+json",
"size": 7023,
"digest": "sha256:b5b2b2c507a0944348e0303114d8d93aaaa081732b86451d9bce1f432a537bc7"
},
"layers": [
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 32654,
"digest": "sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f"
},
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 16724,
"digest": "sha256:3c3a4604a545cdc127456d94e421cd355bca5b528f4a9c1905b15da2eb4a4c6b"
},
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 73109,
"digest": "sha256:ec4b8955958665577945c89419d1af06b5f7636b4ac3da7f12184802ad867736"
}
]
}

如何将 Docker V1 或 Manifest V2 Schema 1 升级到:Docker Image Manifest V2 Schema 2 ?

  1. 将老的 Docker V1 或 Manifest V2 Schema 1 使用 docker pull 下来;
  2. 然后用新版本的 Docker docker push 到镜像仓库即可

这样做将自动将镜像转换为使用最新的镜像清单规范。

或者也可以这样升级:

  1. 可以通过更新 Dockerfile 中的 FROM 语句来重新 build 镜像。如果您的镜像清单过期了,那么从 Dockerfile 中的 from 语句中提取的镜像也有可能过期。

OCI 格式

OCI 格式是基于 Docker Image Manifest Version 2 Schema 2 格式的容器镜像规范。该规范定义了如何创建 OCI Image(通常由构建系统完成),并输出镜像清单文件系统(镜像层)序列化镜像配置。下面简要拿镜像清单做一个和 Docker 的对比说明,更多就不详细展开了。

镜像索引(Image Index)

镜像索引(Image Index)相当于 OCI 映像中的清单列表(Manifest List)。

与清单列表一样,镜像索引清单指的是多个镜像清单。镜像索引对多平台镜像很有用。

示例镜像索引

{
"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"
}
}
],
"annotations": {
"com.example.key1": "value1",
"com.example.key2": "value2"
}
}

镜像清单

示例镜像清单

{
"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: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"
}
],
"annotations": {
"com.example.key1": "value1",
"com.example.key2": "value2"
}
}

镜像格式差别总结

首先是 Docker V1 镜像,这是非常老旧且已经弃用的镜像格式,是 Docker 刚出来的时候,没有考虑多架构多平台(如:x86 和 arm 镜像),所以通常 Docker V1 镜像只有 x86 平台的镜像。️ 注意:请不要再使用,如果使用,请尽快升级到 Docker Image Manifest V2 Schema 2 或 OCI 格式

然后是 Docker Image Manifest V2 Schema 1,这个是过过渡形态的格式,兼容 Docker V1 和 Docker Image Manifest V2 使得它更为复杂,它的作用也仅仅是为了过渡而非长期使用。️ 注意:请不要再使用,如果使用,请尽快升级到 Docker Image Manifest V2 Schema 2 或 OCI 格式

最后是 Docker Image Manifest V2 Schema 2 和 OCI 格式,OCI 主要参考的就是 Docker Image Manifest V2 Schema 2 格式,二者是兼容的,这也就回答了上文所说的:「Docker 镜像仍然可以使用吗?-- 是的,可以使用。」

但是二者在媒体类型(Media Type)、压缩方式等细节上存在不同,部分举例如:

  • 清单(manifest)上:

    • Docker Image Manifest V2 Schema 2 的 Media Type 是:"application/vnd.docker.distribution.manifest.list.v2+json"
    • 而 OCI 是:"application/vnd.oci.image.manifest.v1+json"
  • 每一层镜像层上:
    • Docker Image Manifest V2 Schema 2 的 Media Type 是:"application/vnd.docker.image.rootfs.diff.tar.gzip"
    • OCI 的 Media Type 是:application/vnd.oci.image.layer.v1.tar+gzipapplication/vnd.oci.image.layer.v1.tar 等类型。

也正是因为这些差异,最终导致即使是完全相同的镜像,二者的 digest、镜像大小不尽相同。

所以这也正好解释了「那为什么发现 Docker 镜像和 Containerd 镜像存在不兼容情况?」

  1. 「不能被 docker 使用?」 - 可能是 docker 版本过低导致的;
  2. 「docker push 不能覆盖?」- 因为即使是完全相同的镜像,二者的 digest、镜像大小不尽相同。所以无法覆盖。

我的建议

针对镜像,因为 OCI 主要参考了 Docker Image Manifest V2 Schema 2,而 Docker Image Manifest V2 Schema 2 是 OCI 的一种实现。所以 Docker 较新版本生成的镜像,containerd 和 CRI-O 都能够提取这些镜像并运行它们。

两种办法都可以:

  1. 镜像构建方式保持不变,仍然是采用 Docker 构建镜像(注意 Docker 版本要较新,确保构建的镜像是:Docker Image Manifest V2 Schema 2 格式)
  2. 变更镜像构建工具,不再使用 Docker,而是使用可以构建 OCI 格式的镜像构建工具。

具体您可以视实际情况进行选择。

三人行, 必有我师; 知识共享, 天下为公. 本文由东风微鸣技术博客 EWhisper.cn 编写.

K8S 1.20 弃用 Docker 评估之 Docker 和 OCI 镜像格式的差别的更多相关文章

  1. 关于Kubernetes(简称K8S)的开启及基本使用,基于Docker Desktop & WSL2

    背景介绍 Kubernetes(简称k8s)已成为目前业界容器编排的事实标准,其搭配Docker可建立非常高效便捷的高可扩展.高可用应用服务架构. Kubernetes的名字来自希腊语,意思是&quo ...

  2. [k8s]docker calico网络&docker cluster-store

    docker cluster-store选项 etcd-calico(bgp)实现docker夸主机通信 配置calico网络 - 启动etcd etcd --listen-client-urls h ...

  3. jenkins流水线部署springboot应用到k8s集群(k3s+jenkins+gitee+maven+docker)(2)

    前言:上篇已介绍了jenkins在k3s环境部署,本篇继续上篇讲述流水线构建部署流程 1.从gitlab上拉取代码步骤 在jenkins中,新建一个凭证:Manage Jenkins -> Ma ...

  4. 温故知新Docker概念及Docker Desktop For Windows v3.1.0安装

    Docker 简介 什么是Docker? Docker是一个开放源代码软件项目,项目主要代码在2013年开源于GitHub.它是云服务技术上的一次创新,让应用程序布署在软件容器下的工作可以自动化进行, ...

  5. 建立docker私有库(docker registry)(转)

    建立docker私有库(docker registry) 博客分类: docker   我的目标还是无互联网安装,部署内部的docker私有库,目前docker镜像的获得还是需要互联网,将下载好的do ...

  6. Windows 下安装使用docker swarm machine docker toolbox

    下载docker 集成安装环境 http://get.daocloud.io/#install-toolbox 这个网站很不错,下载 这个集成了 docker docker-machine ,还有gi ...

  7. Docker Architecture、Docker Usage

    目录 . 引言 - 为什么要有Docker技术 . Docker简介 . Docker安装.部署.使用 . Docker安全 . Docker底层实现 . Docker网络配置 . Dockerfil ...

  8. 【云计算】Docker云平台—Docker进阶

    Docker云平台系列共三讲,此为第二讲:Docker进阶 参考资料: 五个Docker监控工具的对比:http://www.open-open.com/lib/view/open1433897177 ...

  9. Docker学习笔记 — Docker私有仓库搭建【转载】

    标签: Docker 2015-03-10 21:08 24190人阅读 评论(0) 收藏 举报  分类: Docker(26)    目录(?)[+]   和Mavan的管理一样,Dockers不仅 ...

  10. Docker(四):Docker 三剑客之 Docker Compose

    前两篇文章我们介绍了 Dockerfile 的使用Docker(二):Dockerfile 使用介绍,我们知道使用一个 Dockerfile 模板文件可以定义一个单独的应用容器,如果需要定义多个容器就 ...

随机推荐

  1. 基础css样式

    目录 css层叠样式表 css选择器 伪类选择器 选择器生效优先级 css字体颜色背景 设置宽高 边框 display属性 div盒子模型 float漂浮 溢出overflow 定位(position ...

  2. python注释、变量、数据类型详细

    目录 1.python注释 2.PEP8规范 3.变量与常量 1.python中的变量 2.变量名的命名规范 3.常量的基本使用 1.python注释 什么是注释? 注释是对代码的解释说明,写注释是为 ...

  3. go的grpc环境源码编译安装

    go的grpc环境安装 参考grpc-go官方文档:https://grpc.io/docs/languages/go/quickstart/ 视频教程:https://www.bilibili.co ...

  4. 初探富文本之OT协同算法

    初探富文本之OT协同算法 OT的英文全称是Operational Transformation,是一种处理协同编辑的算法.当前OT算法用的比较多的地方就是富文本编辑器领域了,常用于作为实现文档协同的底 ...

  5. 题解CF893C Rumor

    思路 竟然朋友之间可以传递故事,那么,我们设两两有间接或直接的朋友关系的为一个友好集合,那么我们只要每一个友好集合买一次就好了. 那应该怎么买呢?由于题面让我们求的是[最少的价钱],那我们可以考虑每一 ...

  6. 使用Zolom内存解析运行python脚本(不落地)

    在目标机器运行python工具 好多工具都是python写的,如果目标机器是linux的话自带python环境可以很方便的运行这些工具,但是windows下是不自带python环境的,所以一种办法是直 ...

  7. (16)go-micro微服务jaeger链路追踪

    目录 一 jaeger链路追踪介绍 什么是链路追踪: 链路追踪主要功能: 二 jaeger链路追踪作用 三 jaeger链路追踪主要特性 四 jaeger链路追踪原理图 1.链路调用原理 2. 一次调 ...

  8. 基础分类算法_KNN算法

    KNN(K-NearestNeighbor)算法 KNN算法是有监督学习中的分类算法. KNN算法很特殊,可以被认为是没有模型的算法,也可以认为其训练数据集就是模型本身. KNN算法的原理 KNN的原 ...

  9. 反射_Class对象功能概述-反射_Class对象功能_获取Field

    反射_Class对象功能概述 * Class对象功能: * 获取功能: 1. 获取成员变量们 * Field[] getFields() :获取所有public修饰的成员变量 * Field getF ...

  10. 字符串拼接输出-Predicate接口

    字符串拼接输出 下面的字符串数组当中存有多条信息,请按照格式""\姓名∶XX.性别:xx."的格式将信息打印出来.要求将打印姓名的动作作为第一个Consumer 接口的L ...