OCI 与容器镜像构建

云原生实验室

共 6688字,需浏览 14分钟

 · 2021-11-15

大家好,我是张晋涛。

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

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 的方式来查看构建历史。

那么 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 镜像仓库中。

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

  • BuildKit
  • img
  • orca-build
  • umoci
  • buildah
  • kaniko
  • FTL
  • Bazel rules_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 的工具,请大家按实际需求进行选择。

浏览 20
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报