OCI 与下一代镜像构建工具

大家好,我是张晋涛。

这篇文章中我将介绍 OCI 及 Docker 镜像相关的内容,欢迎留言讨论。

OCI 的前世今生

2013 年 3 月 dotCloud 公司在 PyCon 上进行了 Docker 的首次展示,随后宣布开源。自此 Docker 开始被众人知晓,随后掀起了一股容器化的热潮。

在 2014 年 6 月 Docker 1.0 正式发布,有近 460 位贡献者和超过 8700 次提交,这也标志着 Docker 达到了生产可用的状态。

在当时,提到容器化第一想法就是用 Docker 。而当时 Docker 的实现或者说发展方向主要是由 Docker Inc. 公司控制的,并没有一个统一的工业标准。这对于一些头部公司而言,显然是不能接受的,没有统一的工业标准意味着如果选择了使用 Docker 的容器化技术,便会被 Docker Inc. 公司所绑定;加上随着 Docker 软件的升级,某些功能或者特性必然会进行变动,没人能保证不发生破坏性变更。

所以,为了推进容器化技术的工业标准化,2015 年 6 月在 DockerCon 上 Linux 基金会与 Google,华为,惠普,IBM,Docker,Red Hat,VMware 等公司共同宣布成立开放容器项目(OCP),后更名为 OCI。它的主要目标便是 建立容器格式和运行时的工业开放通用标准

发展至今, OCI 制定的主要标准有三个分别是 runtime-specimage-specdistribution-spec 这三个标准分别定义了容器运行时,容器镜像还有分发的规范,后面会展开介绍。

为了支持 OCI 容器运行时标准的推进,Docker 公司起草了镜像格式和运行时规范的草案,并将 Docker 项目的相关实现捐献给了 OCI 作为容器运行时的基础实现,现在项目名为 runc

后来 Docker 将其容器运行时独立成了一个项目,名为 containerd 并将此项目捐献给了 CNCF ,现在已经是 CNCF 毕业项目了。

OCI image vs Docker image

OCI 的建立推动了容器技术的工业标准化,但是否此标准就是唯一呢?其实不然。在成立 OCI 并制定 image-spec 标准的时候 Docker 已经空前繁荣,并得到了广泛的应用。

由于标准只定义了最基本的内容,想要将 Docker 的实现全部按照标准进行改造的话,会对 Docker 造成破坏性变更,也不利于 Docker 功能的迭代。

所以,Docker 为了支持 OCI 标准的普及,已经推进了 registry 对 OCI 镜像的支持,现在也正在给 Docker 自身增加适配中,目标是让 Docker 支持两种镜像格式,分别是符合 Docker 标准的镜像和符合 OCI 标准的镜像。

那这两者有什么异同呢? 我们来逐步看下。

Docker Image 和 OCI Image 的区别和联系

在我以前的文章中我们已经详细的从根本上介绍了 Docker image 是什么,这里我们就快速的介绍下。

每个 Docker 镜像都是由一系列的配置清单和相应的层进行组织的。每个层一般都是 tar 格式的归档,配置清单中描述了对应的层应该按何种顺序进行组织,以及镜像的一些元属性。比如镜像所支持的架构,例如 amd64 之类的,还有 ENV 等提前配置好的一些参数等。

当然,在 Docker Image 中也包含着构建镜像时候所用的 Docker 版本 docker_version 以及构建镜像的历史记录 history 等信息。所以你在 DockerHub 或者其他的镜像仓库上可以看到构建镜像所用的 Docker 版本, 或者可通过 docker history <IMAGE> 的方式来查看构建历史。

那么 OCI Image 是什么呢? 首先我们需要有一个 OCI Image 才好探究它到底是什么。

这里介绍一个工具 skopeo 可以很方便的从镜像仓库或者本地 Docker daemon 甚至是通过 docker save 保存的 Docker Image tar 文件转换为 OCI Image 。

关于 skopeo 的安装过程就不再赘述了,参考项目主页的文档说明即可。这里直接开始使用。

我们使用 debian 的镜像为例。

(MoeLove) ➜  skopeo copy docker://debian:stretch oci:debian:stretch    
Getting image source signatures
Copying blob a4d8138d0f6b done
Copying config 45f82268e3 done
Writing manifest to image destination
Storing signatures

通过上面的命令便会得到一个 OCI Image 了, 我们看下它的目录结构。

(MoeLove) ➜  tree debian 
debian
├── blobs
│   └── sha256
│       ├── 0043cd2a654fe86258f43f5b1dbbb4e6c582cc4bb6e505e9c5171c124150d155
│       ├── 45f82268e32180cb1839f90467d9b8a8258953d68b7221199976653308d92ef5
│       └── a4d8138d0f6b5a441aaa533faf5fe0c3996a6ca42643c46f4402c7e8bda53742
├── index.json
└── oci-layout

2 directories, 5 files

是不是有种似曾相识的感觉?没错 OCI Image 的规范是在 Docker Image 的基础上建立的,所以大致看起来差异不是特别大。我们看看其中具体的内容。

oci-layout

这个文件是 OCI Image 的布局文件,也是用于说明它所使用或者遵循的镜像规范。

(MoeLove) ➜  debian cat oci-layout| jq
{
  "imageLayoutVersion": "1.0.0"
}

可以看到此处的内容写的是 1.0.0 这便说明该镜像遵循 OCI 1.0.0 版本的布局规范。

index.json

index.json 文件中的 manifest 字段类似于 Docker Image 中的 manifest.json 作为 OCI Image 的顶级配置, 也是镜像的一个入口配置。

(MoeLove) ➜  debian cat index.json | jq
{
  "schemaVersion": 2,
  "manifests": [
    {
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "digest": "sha256:0043cd2a654fe86258f43f5b1dbbb4e6c582cc4bb6e505e9c5171c124150d155",
      "size": 349,
      "annotations": {
        "org.opencontainers.image.ref.name": "stretch"
      },
      "platform": {
        "architecture": "amd64",
        "os": "linux"
      }
    }
  ]
}

从它的内容可以看到它其中的 mediaType 字段与 Docker Image 中的类型形式相同,但是将 docker 都换成了 oci。从这个配置文件,我们可以找到第一个 blob 是 sha256:0043cd2a654fe86258f43f5b1dbbb4e6c582cc4bb6e505e9c5171c124150d155 我们看看它的内容。

(MoeLove) ➜  debian cat blobs/sha256/0043cd2a654fe86258f43f5b1dbbb4e6c582cc4bb6e505e9c5171c124150d155 | jq
{
  "schemaVersion": 2,
  "config": {
    "mediaType": "application/vnd.oci.image.config.v1+json",
    "digest": "sha256:45f82268e32180cb1839f90467d9b8a8258953d68b7221199976653308d92ef5",
    "size": 579
  },
  "layers": [
    {
      "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
      "digest": "sha256:a4d8138d0f6b5a441aaa533faf5fe0c3996a6ca42643c46f4402c7e8bda53742",
      "size": 45337510
    }
  ]
}

这个入口文件描述了 OCI 镜像的实际配置和其中的 Layer 配置。如果有多层那 layers 也会相应增加。

注意:layers 中 mediaType 使用了 application/vnd.oci.image.layer.v1.tar+gzip 说明数据内容是经过 gzip 压缩的 如果有兴趣你可以将它用 tar 解压一下,你会发现很有趣的内容。

这里先将结果说出来,解压后你会得到一个 rootfs 这与 Docker Image 是类似的。

小结

我们通过 skopeo 工具,从本地的 Docker daemon 中由 debian 的 Docker Image 得到了 OCI Image,并分析了它其中的内容。

最主要的区别在于它们的目录结构不完全相同,配置信息尤其是 mediaType 的规范是不相同的。

而它们的联系也在于此,OCI Image 的规范是由 Docker Image 的规范修改而来的,所以类似它们的 blob 的组织形式大致是相同的,配置文件中很多的参数也相似。

另外,我们也可以很容易的得到另一个结论,那便是我们可以很方便的将 Docker Image 转换为 OCI Image 。

OCI image 和 Docker image 的转换

上面我们已经看到,使用 skopeo 工具,可以将 Docker Image 转换为 OCI Image ,当然它也可以将 OCI Image 转换为 Docker Image 。下面给出了方法:

# 从 DockerHub 将 debian 的 Docker Image 拉取并转换为 OCI Image
(MoeLove) ➜  skopeo copy docker://debian:stretch oci:debian:stretch    
Getting image source signatures
Copying blob a4d8138d0f6b done
Copying config 45f82268e3 done
Writing manifest to image destination
Storing signatures


# 将当前目录下的 debian 的 OCI Image 转换为 Docker Image 并存储到本地 docker daemon 中
(MoeLove) ➜  skopeo copy  oci:debian:stretch docker-daemon:local/debian:oci
Getting image source signatures
Copying blob 0e350e141713 done
Copying config aae58a37cf done
Writing manifest to image destination
Storing signatures
# 验证
(MoeLove) ➜  oci docker images local/debian
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
local/debian        oci                 ac6bcf605d82        6 months ago        101MB

镜像构建工具

在 CI/CD 环境中,虽然我们可以使用 DinD (Docker in Docker) 的方式启动一个 docker daemon 或者使用挂载的方式,将外部的 /var/run/docker.sock 挂载进容器内部,亦或者将 Docker API 使用 HTTP 的方式暴露出来,直接使用该地址进行构建。

但这些方式你是否会觉得比较重?是否有考虑安全问题,或者压力及负载的问题?

这里的压力及负载主要是指当所有的任务都共用同一个 docker daemon 提供服务的话,对该 docker daemon 造成的压力。

这里我们来介绍一些其他的镜像构建工具,使用这些工具可以让你在无 Docker 的环境下构建出镜像并上传至 Docker 镜像仓库中。

到目前为止,我们可以有很多种选择:

这些工具侧重点各有不同,当然也不仅有上面列到的这些工具,只是这些工具比较典型罢了。

通常情况下,在网络上比较容易见到宣传为下一代镜像构建工具的是 buildah ,最主要原因是因为它可以直接构建 OCI 标准的镜像或 Docker 镜像,也可以直接使用 Dockerfile 。并且它还可以 pull/push 镜像,可以说在镜像构建方面与 Docker 是完全兼容,甚至可以说它在构建镜像方面可以作为 Docker 的替代品了。

并且 buildah 构建镜像的时候不需要任何 root 权限,也不依赖 Docker, 它使用了简单的 fork-exec 模型,同时它也可以作为一个库包含在其他的工具中。它的最终目标便是提供一个更低层次的核心工具集,来完成构建镜像相关的事情。

说完这个典型的替代品,我们再来说下 BuildKitimg , img 这个工具是构建在 BuildKit 之上的,所以有很多相似性。它们使用非 root 用户来构建镜像。当然 BuildKit 我在之前的文章中详细介绍过了,它是 Docker 内置的下一代构建工具,独立使用也是可以的。称它为“下一代镜像构建工具” 也并不为过。

kaniko 是 Google 推出的,它主要的宣传语为 “在 Kubernetes 中构建容器镜像” 实际上无论是在 K8S 集群中或者在容器中它都是可以工作的。它也可以使用 Dockerfile 构建镜像。当然还有很重要的一点,它所有的构建命令都是运行在用户态的,并且也可以很好的与 Kubernetes 结合,在云原生时代下,它也占据了一定的优势。

以上工具都只是大致介绍了下,如果对它们感兴趣,可直接进入项目主页查看 README.md 基础使用都有比较详细的说明,这里不再进行赘述了。

总结

本篇为大家介绍了 OCI 的前世今生,以及 OCI Image 的规范和特点,另外也介绍了一个可用于在 OCI Image 和 Docker Image 之间镜像转换的工具 skopeo 。另外介绍了一些可用于在 CI 环境或其他有特定场景环境下替代 Docker build 的工具,请大家按实际需求进行选择。


欢迎订阅我的文章公众号【MoeLove】

TheMoeLove

加载评论