利用Makisu构建容器镜像


本系列文章深入研究了容器镜像构建的最新技术。我们已经介绍了Podman和BuildahImgKaniko,而这次轮到Makisu了。

Makisu是另一个开源镜像构建工具,由Uber的工程团队构思而成。像许多其他开源项目一样,Makisu也是基于其他类似技术的不足而开发的。 Makisu尤其专注于优化镜像构建时间和大小。

使用Makisu

类似Kaniko,Makisu不会调用容器并依据Dockerfile指令在容器中构建镜像。它既可以作为独立的二进制文件在本地运行,也可以作为沙箱运行在容器内。但是,由于它无法执行RUN Dockerfile指令,因此它作为独立二进制文件的用途受到限制。当然,你也不希望Makisu通过RUN指令更改主机的本地文件系统内容!

实际上Makisu不允许更改本地文件;你需要指定标志--modifyfs = true,以允许使用命令对文件系统进行更改。但请注意,如果使用--modifyfs = true运行独立的Makisu二进制文件,最终将删除主机的许多rootfs。 Makisu被设计为在容器中运行,在容器中更改文件系统内容是安全的。

实际上用于执行构建的Makisu本身的容器镜像很少。它是基于基本图像指令(scratch base image directive)构建的,仅包含Makisu二进制文件和根CA证书的文件。需要使用卷将构建上下文(包括Dockerfile)提供给容器。

Makisu提取Dockerfile中定义的基本镜像,并将其文件系统提取到其容器内。它还将此文件系统的副本存储在内存中。随后的构建步骤针对该文件系统的内容运行,然后对其进行扫描以查找更改。任何更改也会反映在“内存”的副本中,并创建一个包含更改的新“差异层”。 “差异层”层缓存在目录中,以供将来的版本使用,前提是用于存储的卷已经被挂载。

Dockerfile中定义的构建步骤以这种方式执行到完成,然后Makisu将构建的镜像推送到容器镜像仓库(如果已指定)。如果将Docker用作Makisu的容器运行时,则可以使用以下命令调用构建容器:
$ docker run --rm \
  -v $(pwd):/makisu-context \
  -v /tmp/makisu-storage:/makisu-storage \
  gcr.io/makisu-project/makisu:v0.1.12 build \
      --tag=mycorp/my-app:1d03df1 \
      --push=quay.io \
      --modifyfs=true \
      /makisu-context

如果你阅读了本系列的上一篇文章,你将已经得出结论,如Kaniko一样,Makisu将采用几乎相同的方法来构建镜像。你可以执行构建步骤而无需Docker守护程序,也无需运行嵌套容器所需的特权账号。但是 Makisu的突出之处在于其构建缓存实现的方法。

缓存

一旦决定放弃Docker守护进程构建镜像,你将失去其固有的缓存功能。 Docker守护程序提供的构建步骤缓存可能不像许多人所希望的那样具有丰富的功能,但是缓存是镜像构建的基本功能。通过复用以前执行过的相同构建步骤所生成的内容,它有助于优化构建时间。对于Uber来说,这是促使他们决定创建替代容器镜像构建工具的重要因素之一。那么,Makisu通过缓存功能提供了什么?

分布式缓存

在Kubernetes设置中,包含构建容器的Pod在理论上可以存在于集群内的任何节点上。

这带来了一个问题:构建容器如何才能利用先前构建迭代生成的缓存镜像层?

我们可以尝试迫使Pod落在执行了先前构建迭代的节点上,但这会影响调度程序的作用和目的。相反,Makisu利用分布式缓存来解决此问题。

首先,Makisu提供了Dockerfile指令序列和diff层摘要之间的本地映射。这些映射保存在键值存储中,键值存储可以是文件,分布式Redis缓存或基于HTTP的通用缓存。重要的是缓存是分布式的,因此可以由任何有权访问缓存的Makisu构建容器引用。

缓存中的映射使Makisu能够确定是否需要执行现有的构建步骤,或者是否可以使用现有层的内容。如果在缓存中找到匹配项,则可以从Makisu管理的本地存储中解压缩该图层(如果它存在于本地存储中),或者从镜像仓库中提取该图层(如果已推送先前的构建)。

缓存中的密钥是从Dockerfile指令生成的,用于构建步骤并与先前构建步骤相关联。关联值是先前生成的镜像层内容的哈希值。如果构建步骤指令序列与缓存中的现有键匹配,则Makisu将使用摘要(作为键值保存)来定位diff层。

缓存具有可配置的生存时间(TTL),以确保缓存的图层不会过时。

选择性提交(Optional Commits)

在使用Docker守护程序进行镜像构建期间,将为新生构建或有更改内容的每个构建步骤生成diff层。这导致创建images镜像非常臃肿。有时,可以通过合理地使用构建步骤在控制这些中间层,或将大量命令组合到一个Dockerfile指令中来控制这些中间层的数量。 Makisu使用自己独特的技术来缓解此问题。

Makisu的Dockerfile指令解析器引入了一个指令,该指令控制在构建过程中何时提交差异层。语法#!COMMIT注释的任何指令都被解析器解释为生成新层。那些没有该注释的将不会生成新的层。
FROM alpine

RUN apk add --no-cache wget

RUN apk add --no-cache curl #!COMMIT
<SNIP>

在上面的示例中,安装wget的RUN指令未提交为新层,而安装curl的则被提交。它创建了一个图层,其中包含自上一次提交以来或从构建阶段开始以来的所有新内容。

当为Makisu指定--commit = explicit标志时,显示缓存将为构建打开。没有它,#!COMMIT语法将被视为注释,就像Docker守护程序的解析器一样。这样,用于Makisu显式缓存的Dockerfile便与Docker守护程序保持兼容。

显式提交可以为镜像构建提供更大的灵活性;创建的层数更少,通常会使得图像更小,并改善了Dockerfile的可维护性。

结论

Makisu是一种非常强大的容器镜像构建工具,它是出于解决大型生产环境中存在的缺陷而产生的。它的方法消除了在容器构建期间对提升特权的需求(尽管构建是作为root用户执行的),并且它具有一种新颖的方法来构建缓存实现。

它无法解决Dockerfile指令顺序解析中固有的构建效率低下的问题。而且,构建执行并不总是能忠实地反映Docker镜像构建的预期行为。但是,Makisu来自著名的工程师团队,是新型容器镜像构建工具的又一个重要补充。

原文链接:Container Image Building with Makisu(翻译:吴世曦)

0 个评论

要回复文章请先登录注册