三个技巧,将Docker镜像体积减小90%【面试+工作】

三个技巧,将Docker镜像体积减小90%【面试+工作】

在构建Docker容器时,应该尽量想办法获得体积更小的镜像,因为传输和部署体积较小的镜像速度更快。

但RUN语句总是会创建一个新层,而且在生成镜像之前还需要使用很多中间文件,在这种情况下,该如何获得体积更小的镜像呢?

你可能已经注意到了,大多数Dockerfiles都使用了一些奇怪的技巧:

为什么使用&&?而不是使用两个RUN语句代替呢?比如:

从Docker 1.10开始,COPY、ADD和RUN语句会向镜像中添加新层。前面的示例创建了两个层而不是一个。

镜像的层就像Git的提交(commit)一样。

Docker的层用于保存镜像的上一版本和当前版本之间的差异。就像Git的提交一样,如果你与其他存储库或镜像共享它们,就会很方便。

实际上,当你向注册表请求镜像时,只是下载你尚未拥有的层。这是一种非常高效地共享镜像的方式。

但额外的层并不是没有代价的。

层仍然会占用空间,你拥有的层越多,最终的镜像就越大。Git存储库在这方面也是类似的,存储库的大小随着层数的增加而增加,因为Git必须保存提交之间的所有变更。

过去,将多个RUN语句组合在一行命令中或许是一种很好的做法,就像上面的第一个例子那样,但在现在看来,这样做并不妥。

1. 通过Docker多阶段构建将多个层压缩为一个

当Git存储库变大时,你可以选择将历史提交记录压缩为单个提交。

事实证明,在Docker中也可以使用多阶段构建达到类似的目的。

在这个示例中,你将构建一个Node.js容器。

让我们从index.js开始:

和package.json:

你可以使用下面的Dockerfile来打包这个应用程序:

然后开始构建镜像:

然后用以下方法验证它是否可以正常运行:

你应该能访问http://localhost:3000,并收到“Hello World!”。

Dockerfile中使用了一个COPY语句和一个RUN语句,所以按照预期,新镜像应该比基础镜像多出至少两个层:

但实际上,生成的镜像多了五个新层:每一个层对应Dockerfile里的一个语句。

现在,让我们来试试Docker的多阶段构建。

你可以继续使用与上面相同的Dockerfile,只是现在要调用两次:

Dockerfile的第一部分创建了三个层,然后这些层被合并并复制到第二个阶段。在第二阶段,镜像顶部又添加了额外的两个层,所以总共是三个层。

现在来验证一下。首先,构建容器:

查看镜像的历史:

文件大小是否已发生改变?

最后一个镜像(node-multi-stage)更小一些。

你已经将镜像的体积减小了,即使它已经是一个很小的应用程序。

但整个镜像仍然很大!

有什么办法可以让它变得更小吗?

2. 用distroless去除容器中所有不必要的东西

这个镜像包含了Node.js以及yarn、npm、bash和其他的二进制文件。因为它也是基于Ubuntu的,所以你等于拥有了一个完整的操作系统,其中包括所有的小型二进制文件和实用程序。

但在运行容器时是不需要这些东西的,你需要的只是Node.js。

Docker容器应该只包含一个进程以及用于运行这个进程所需的最少的文件,你不需要整个操作系统。

实际上,你可以删除Node.js之外的所有内容。

但要怎么做?

所幸的是,谷歌为我们提供了distroless。

以下是distroless存储库的描述:

这正是你所需要的!

你可以对Dockerfile进行调整,以利用新的基础镜像,如下所示:

你可以像往常一样编译镜像:

这个镜像应该能正常运行。要验证它,可以像这样运行容器:

现在可以访问http://localhost:3000页面。

不包含其他额外二进制文件的镜像是不是小多了?

只有76.7MB!

比之前的镜像小了600MB!

但在使用distroless时有一些事项需要注意。

当容器在运行时,如果你想要检查它,可以使用以下命令attach到正在运行的容器上:

attach到正在运行的容器并运行bash命令就像是建立了一个SSH会话一样。

但distroless版本是原始操作系统的精简版,没有了额外的二进制文件,所以容器里没有shell!

在没有shell的情况下,如何attach到正在运行的容器呢?

答案是,你做不到。这既是个坏消息,也是个好消息。

之所以说是坏消息,因为你只能在容器中执行二进制文件。你可以运行的唯一的二进制文件是Node.js:

说它是个好消息,是因为如果攻击者利用你的应用程序获得对容器的访问权限将无法像访问shell那样造成太多破坏。换句话说,更少的二进制文件意味着更小的体积和更高的安全性,不过这是以痛苦的调试为代价的。

但如果你确实需要调试,又想保持小体积该怎么办?

3. 小体积的Alpine基础镜像

你可以使用Alpine基础镜像替换distroless基础镜像。

Alpine Linux是:

换句话说,它是一个体积更小也更安全的Linux发行版。

不过你不应该理所当然地认为他们声称的就一定是事实,让我们来看看它的镜像是否更小。

先修改Dockerfile,让它使用node:8-alpine:

使用下面的命令构建镜像:

现在可以检查一下镜像大小:

69.7MB!

甚至比distrless镜像还小!

现在可以attach到正在运行的容器吗?让我们来试试。

让我们先启动容器:

你可以使用以下命令attach到运行中的容器:

看来不行,但或许可以使用shell?

成功了!现在可以attach到正在运行的容器中了。

看起来很有希望,但还有一个问题。

Alpine基础镜像是基于muslc的——C语言的一个替代标准库,而大多数Linux发行版如Ubuntu、Debian和CentOS都是基于glibc的。这两个库应该实现相同的内核接口。

但它们的目的是不一样的:

  • glibc更常见,速度也更快;
  • muslc使用较少的空间,并侧重于安全性。

在编译应用程序时,大部分都是针对特定的libc进行编译的。如果你要将它们与另一个libc一起使用,则必须重新编译它们。

换句话说,基于Alpine基础镜像构建容器可能会导致非预期的行为,因为标准C库是不一样的。

你可能会注意到差异,特别是当你处理预编译的二进制文件(如Node.js C++扩展)时。

例如,PhantomJS的预构建包就不能在Alpine上运行。

你应该选择哪个基础镜像?

你应该使用Alpine、distroless还是原始镜像?

如果你是在生产环境中运行容器,并且更关心安全性,那么可能distroless镜像更合适。

添加到Docker镜像的每个二进制文件都会给整个应用程序增加一定的风险。

只在容器中安装一个二进制文件可以降低总体风险。

例如,如果攻击者能够利用运行在distroless上的应用程序的漏洞,他们将无法在容器中使用shell,因为那里根本就没有shell!

如果你只关心更小的镜像体积,那么可以考虑基于Alpine的镜像。

它们的体积非常小,但代价是兼容性较差。Alpine使用了略微不同的标准C库——muslc。你可能会时不时地遇到一些兼容性问题。

原始基础镜像非常适合用于测试和开发。

它虽然体积很大,但提供了与Ubuntu工作站一样的体验。此外,你还可以访问操作系统的所有二进制文件。

再回顾一下各个镜像的大小:

node:8 681MB

node:8 使用多阶段构建为678MB

gcr.io/distroless/nodejs 76.7MB

node:8-alpine 69.7MB

原文发布于微信公众号 - Java帮帮(javahelp)

原文发表时间:2018-09-10

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏性能与架构

Docker容器跨主机互联

当两个Docker容器在同一主机时,可以通过--link命令让两者直接互相访问。 如果要跨主机实现容器互联,则往往需要容器知道其他物理主机的IP地址 利用Amb...

4224
来自专栏云计算教程系列

如何在Ubuntu 14.04上使用Docker数据卷

在本文中,我们将介绍Docker数据卷的概念:它们是什么,它们有用的原因,不同类型的卷,如何使用它们以及何时使用它们。我们还将通过docker命令行工具介绍如何...

1093
来自专栏宝哥的专栏

Docker系列学习文章 - docker镜像基本操作(五)

| 导语上一篇文章我们讲解了如何简单运行一个Nginx、Mysql、Redis容器服务。我们运行的很顺利,因为我们就用了一条命令就搞定了。确实,docker就是...

1.3K25
来自专栏流柯技术学院

CentOS 6.8下安装docker并使用

Docker是一个开源的应用容器引擎,可以轻松的为任何应用创建一个轻量级的、可移植的、自给自足的容器。利用Linux的LXC、AUFS、Go语言、cgroup实...

3702
来自专栏架构师小秘圈

\bdocker容器极简教程

一,小王对于容器的困惑 小王刚开始学习Docker的时候,找资料在网上看到最多的是Docker的好处。比如: 1、Docker 容器的启动可以在秒级实现,这相比...

3865
来自专栏云计算教程系列

如何在Ubuntu 16.04上使用dry管理和监控Docker容器

dry是一个简单但广泛的终端应用程序,用于与Docker容器及其映像交互。使用dry会删除执行常规Docker Engine命令时所涉及的重复,并且还提供了更原...

1356
来自专栏漫漫全栈路

Docker 循序渐进

上一篇大致介绍了什么是Docker和其安装(以Ubuntu为例)。这篇来说说,Docker的基本操作。 非Root用户授权 上一篇的演示中使用的都是默认登...

3978
来自专栏宝哥的专栏

Docker系列学习文章 - 专业化定制镜像dockerfile(六)

| 导语上一篇我们跟大家讲了docker镜像,关于镜像的特点和作用我想大家都明白了。那么如何比较专业的去定制我们需要的镜像呢?用commit?不对,我们说过这个...

1.4K26
来自专栏CSDN技术头条

在Docker容器中实现安全与隔离

随着容器技术的发展,它的安全、隔离和资源控制的功能也在不断进步。本文中,我们将回顾Docker容器如何仅仅使用linux的原始功能来实现安全与隔离,比如name...

26110
来自专栏Laoqi's Linux运维专列

docker容器跨服务器的迁移方式export和save

1673

扫码关注云+社区