WatchStor.com — 领先的中文存储网络媒体 | 51CTO旗下网站

新闻资讯 > 公有云 > 正文
五种方式:构建小巧Docker容器的学问
作者: 核子可乐编译 2018-07-30 14:26 【51CTO.com】

在本文中,我们将共同了解五种优化Linux容器大小并构建小巧镜像的方法。

几年之前,Docker的爆炸式发展将容器与容器镜像概念引入了大众视野。尽管之前已经存在Linux容器,但Docker凭借着用户友好的命令行界面以及易于理解的Dockerfile格式显著降低了镜像的构建门槛。但必须承认的是,尽管上手难度已经有所下降,其中仍存在着一些细微的差别与技巧,能够帮助我们构建功能强大但却体积小巧的容器镜像。

五种方式:构建小巧Docker容器的学问

第一关:清理内容

下面列举的部分示例采取与传统服务器类似的清理方式,只是具体要求更为严格。镜像的体积对于快速移动而言至关重要,而且在磁盘之上存储多套不必要的数据副本无疑将浪费大量资源。因此,我们有必要尽可能利用技术控制容器镜像的“身材”。

下面来看如何从镜像中删除缓存文件,从而节约存储空间。首先利用dnf以包含及不包含元数据的方式安装Nginx,查看二者之间的镜像大小区别; 而后利用yum进行缓存清理:

# Dockerfile with cache

FROM fedora:28

LABEL maintainer Chris Collins

RUN dnf install -y nginx

-----

# Dockerfile w/o cache

FROM fedora:28

LABEL maintainer Chris Collins

RUN dnf install -y nginx \

&& dnf clean all \

&& rm -rf /var/cache/yum

-----

[chris@krang] $ docker build -t cache -f Dockerfile .

[chris@krang] $ docker images --format "{{.Repository}}: {{.Size}}"

| head -n 1

cache: 464 MB

[chris@krang] $ docker build -t no-cache -f Dockerfile-wo-cache .

[chris@krang] $ docker images --format "{{.Repository}}: {{.Size}}" | head -n 1

no-cache: 271 MB

可以看到,二者之间的体积存在显著差异。包含dnf缓存的版本几乎是不包含元数据及缓存的镜像大小的两倍。事实上,工具包管理器缓存、Ruby gem临时文件、nodejs缓存、甚至是已下载的源代码压缩包都是清理工作的主要对象。

分层——一个潜在问题

遗憾的是(或者可以说幸运的是,具体如后文所述),由于容器以分层方式使用,因此大家无法简单将RUN rm -rf /var/cache/yum 添加到Dockerfile当中并就此作罢。Dockerfile中的每条指令都存储在一个层中,各层之间的变更最终应用于顶层。所以即使您进行如下操作:

RUN dnf install -y nginx

RUN dnf clean all

RUN rm -rf /var/cache/yum

……最终仍会得到三层,其中一层包含所有缓存,两个中间层则从镜像中“移除”缓存。然而,缓存仍然实际存在,正如当您将某一文件系统安装在另一文件系统之上时,文件就在这里——只是我们无法查看或者访问。

需要注意的是,上一节中的示例将缓存清理链接到了生存缓存的同一Dockerfile指令当中:

RUN dnf install -y nginx \

&& dnf clean all \

&& rm -rf /var/cache/yum

这是一条单独指令,最终会成为镜像中的一层。通过这种方式,您会丢弃一部分Docker缓存——这意味着镜像重构时间会稍长,但缓存数据仍将出现在最终镜像当中。作为一种良好的折衷方案,我们只需链接相关命令(例如hum install与hum clean all,或者下载、释放及移除源tarball等)即可帮助最终镜像显著瘦身,同时继续利用Docker缓存加快开发速度。

然而,这里的层将比前文中提到的更加微妙。因为镜像各层记录了每个层的具体变化——因此除了添加的文件之外,一切文件修改都将被纳入其中。例如,即使更改了文件模式,镜像中也会有新层出现以创建该文件的副本。

举例来说,以下docker images输出结果显示出与两套镜像相关的信息。第一套layer_test_1通过将单一1 GB文件添加至基础CentOS镜像的方式得出。第二套镜像layer_test_2则直接由layer_test_1创建而来,只是利用chmod u+x命令变更了该1 GB文件的模式。

layer_test_2 latest e11b5e58e2fc 7 seconds ago 2.35 GB

layer_test_1 latest 6eca792a4ebe 2 minutes ago 1.27 GB

如大家所见,新的镜像较前一套镜像大出1 GB有余。尽管layer_test_1 实际上只代表着layer_test_2的前两层,但第二套镜像中仍然隐藏着另一个1 GB的文件。在镜像构建过程当中,一切与文件相关的删除、移除或更改都会造成这样的结果。

专用镜像与灵活镜像

一则轶事:当初我们大量采用Ruby on Rails应用程序时,同事们开始慢慢接受容器这种新鲜事物。我们的第一项工作就是为所有团队创建一套官方的Ruby基础镜像。为了简单起见,我们利用rebenv将四套最新的Ruby版本安装到了镜像当中,从而允许我们的开发人员能够利用单一版本将所有应用程序迁移到容器镜像当中。这实际上带来了一套非常庞大但却比较灵活(至少我们认为)的镜像,其中涵盖我们各合作团队间的一切工作基础。

但事实证明,这一切都是在浪费时间。维护特定镜像的单一修改版本能够比较轻松地实现自动化,这是因为为特定镜像选择特定版本实际上有助于在引入突破性变更之前意识到原有应用程序已经不合适接下来的需求,从而避免由此发生严重破坏。此外,过大的镜像也造成了资源浪费:当我们对不同Ruby版本进行拆分时,我们最终得到了多套共享同一基础的镜像。如果将其同时保存在服务器之上,相较于包含多个版本的巨型镜像,其占用的额外空间其实并不大,但传输速度却要快得多。

这并不是说构建灵活性镜像没有意义。只是在我们的情况下,创建专用型镜像最终节约了存储空间与维护时间,同时也确保各团队在享受好处的同时能够对共有基础镜像做出必要的修改。

从头开始:将需要的内容添加至空白镜像中

与Dockerfile的用户友好与易用性类似,还有其他一些工具能够以极为灵活的方式创建小巧的Docker兼容容器镜像且无需完整的操作系统——其小巧程度甚至堪比标准Docker基础镜像。

我在之前曾经写过关于Buildah的文章,这里我也会再次提及,因为其相当灵活且可利用主机中的工具从零开始创建镜像,同时安装打包软件并修改镜像内容。更重要的是,这些工具将永远存在于镜像之外,因此不会增加镜像本身的体积。

Buildah取代了docker build命令。有了它,您可以将容器镜像的文件