您现在的位置是:首页 > 文章详情

如何构建尽可能小的容器镜像?

日期:2018-09-18点击:346
这是Google Developer Advocate Sandeep Dinesh 关于如何充分利用Kubernetes环境的七部分视频和博客系列的第一部分。 主要讲保持容器镜像尽可能小的理论和实用性。Docker使构建容器镜像变得轻而易举。只需将标准 Dockerfile 放入您的文件夹,运行 docker build 命令,然后运行,芝麻开门!您的容器镜像已构建成功!这种简单性的缺点是,很容易构建出大体积的容器镜像,其中包含您不需要的东西——包括潜在的安全漏洞。在本期“Kubernetes最佳实践”中,让我们探讨如何使用Alpine Linux 和 Docker builder 模式创建生产就绪的容器镜像,再做一些基准测试,然后确定这些容器在Kubernetes集群中运行方式。根据您使用的是解释型语言还是编译型语言,创建容器镜像的过程会有所不同。让我们一起来深入了解!解释型语言的容器化解释型语言,如Ruby,Python,Node.js,PHP和其他语言通过发送源代码到解释器来运行代码。 这样的好处是可以跳过编译步骤,但其缺点是要求您将解释器与代码一起丢进去。幸运的是,大多数这些语言都提供了预构建的Docker容器,其中包含一个轻量级环境,允许您运行更小的容器。我们来看一个Node.js应用程序并对其进行容器化。 首先,让我们使用node:onbuild镜像作为基础。 Docker容器的onbuild版本预先打包了您需要的所有内容,因此无需执行大量配置即可搞定。 这意味着Dockerfile非常简单(只有两行!)。 但是你要付出的磁盘大小代价——差不多700MB!
 

dockerfile

FROM node:onbuild

EXPOSE 8080

8a3fbd328306dd1c41d95da6af327f533c8414ed
通过使用较小的基础镜像(如Alpine),您可以显著减少容器的大小。Alpine Linux是一款体积小,轻量级的Linux发行版,在Docker用户中非常受欢迎,因为它与许多应用程序兼容,同时仍然保持小体积。幸运的是,Node.js(以及其他流行语言)有一个官方的Alpine图像,可以满足您的一切需求。与默认的node镜像不同,node:alpine会删除许多文件和程序,只留下足以运行您的应用程序的部分。基于Alpine Linux的Dockerfile创建起来有点复杂,因为你必须运行一些针对onbuild的命令。
 

dockerfile

FROM node:alpine

WORKDIR /app

COPY package.json /app/package.json

RUN npm install --production

COPY server.js /app/server.js

EXPOSE 8080

CMD npm start


但是,这是值得的,因为产生的镜像只有65MB!

834b832b20e3bdf4f3bcea0bb0a4ea7b88cb0095

编译型语言的容器化诸如Go,C,C ++,Rust,Haskell等编译型语言可以创建在没有许多外部依赖性的情况下运行的二进制文件。 这意味着你可以提前构建二进制文件并将其投入生产,而无需把创建二进制文件(如编译器)放进去。借助Docker对多阶段构建的支持,您可以轻松地打包二进制文件和最少量的脚手架。 让我们学习一下怎么做。让我们采用Go应用程序并使用此模式对其进行容器化。 首先,让我们使用golang:onbuild镜像作为基础。 和以前一样,Dockerfile只有两行,但你再次付出磁盘大小超过700MB的代价!
 

dockerfile

FROM golang:onbuild

EXPOSE 8080

ea4c45a7be05135ec609d3652a51aaa4e62dc6a1
下一步是使用更小的基础镜像,也就是golang:alpine镜像。 到目前为止,这与我们针对解释型语言所遵循的过程相同。同样,使用Alpine基础镜像创建Dockerfile有点复杂,因为您必须运行一些执行onbuild镜像相关的命令。
 

dockerfile

FROM golang:alpine

WORKDIR /app

ADD . /app

RUN cd /app && go build -o goapp

EXPOSE 8080

ENTRYPOINT ./goapp


但同样,由此产生的镜像要小得多,大小只有256MB!

a8988ddc4b7d89c572542c72a22f6fec6cd55c21

但是,我们可以使镜像更小:您不需要Go附带的任何编译器或其他构建和调试工具,因此您可以从最终容器中删除它们。让我们使用多阶段构建来获取由golang:alpine容器创建的二进制文件并将其自行打包。
 

dockerfile

FROM golang:alpine AS build-env

WORKDIR /app

ADD . /app

RUN cd /app && go build -o goapp


FROM alpine

RUN apk update && \

apk add ca-certificates && \

update-ca-certificates && \

rm -rf /var/cache/apk/*

WORKDIR /app

COPY --from=build-env /app/goapp /app

EXPOSE 8080

ENTRYPOINT ./goapp

你看看现在! 这个容器镜像只有12MB大小!

3dcddc421b1b0e52376309a99c795f454bbf5b39

在构建此容器时,您可能会注意到Dockerfile会执行一些奇怪的操作,例如手动将HTTPS证书安装到容器中。这是因为基础的Alpine Linux几乎没有预安装任何东西。 因此,即使您需要手动安装任何依赖项,最终结果也还是超小容器镜像!注意:如果您想节省更多空间,可以静态编译应用程序并使用scratch容器镜像。使用scratch作为基础容器镜像意味着您从头开始,根本没有基础层。 但是,我建议使用Alpine作为基础镜像而不是scratch,因为Alpine镜像中的仅仅增加少量额外MB的大小却可以使得使用标准工具和安装依赖项变得更加容易。在哪构建和存储您的容器镜像?为了构建和存储镜像,我强烈推荐Google Container Builder和Google Container Registry的组合。 Google Container Builder非常快,并自动将镜像推送到Google Container Registry。 大多数开发人员应该可以轻松地在GCP的免费套餐中完成所有工作,而Google Container Registry与原始Google云端存储的价格相同(便宜!)。Google Kubernetes Engine等平台可以安全地从Google Container Registry中拉取镜像而无需任何其他配置,让您轻松上手!此外,Google Container Registry还为您提供漏洞扫描工具和开箱即用的IAM支持。 这些工具可以使您更轻松地保护和锁定容器。评估小体积容器的性能人们声称小体积容器的巨大优势是缩短了时间——建立时间和拉取时间。 让我们测试一下,使用onbuild创建的容器,对比在多阶段过程中使用Alpine创建的容器,看看真实情况如何。TL;DR:对于功能强大的计算机或Container Builder没有显著差异,但对于性能没有那么强大的计算机和共享系统(如许多CI/CD系统)而言则有明显差异。 就绝对性能而言,小镜像总是更好。在大型机器上构建镜像对于第一次测试,我将使用笔记本电脑进行构建。 我正在使用我们的办公室WiFi,所以下载速度非常快! 37f39133d5d1442cbaa9f10d2d29c4e8d94d0d07
对于每次构建,我都会删除缓存中的所有Docker镜像。构建(Build):
 

Go Onbuild: 35 Seconds

Go Multistage: 23 Seconds

对于较大的容器镜像,构建需要的时间大约是10秒左右。 虽然这个代价仅在初始构建时需要付出,但是如果您使用持续集成系统,则在每次构建时都要付出这个代价。下一个测试是将容器镜像推送到远程镜像仓库。 对于此测试,我使用Google Container Registry来存储镜像。推送(Push):
 

Go Onbuild: 15 Seconds

Go Multistage: 14 Seconds

这很有趣! 为什么需要花费相同的时间来推送12MB镜像和700MB镜像? 事实证明,Google Container Registry在幕后使用了很多技巧,包括许多流行基础镜像的全局缓存。最后,我想测试将镜像从远程镜像仓库库拉到本地计算机所需的时间。拉取(Pull):
 

Go Onbuild: 26 Seconds

Go Multistage: 6 Seconds

20秒的差距,这是使用两个不同容器镜像之间的最大差异。 您可以开始看到使用较小镜像的优势,尤其是在经常拉取镜像时。您还可以使用Google Container Builder在云中构建容器镜像,这样可以自动将它们存储在Google Container Registry中。构建(build)+ 推送(Push):
 

Go Onbuild: 25 Seconds

Go Multistage: 20 Seconds

再次证明,使用较小的镜像有一点小优势,没有我想象的那么大。在小型机器上构建镜像如果是在小型机器上构建镜像,那么使用较小的容器镜像是否有优势呢? 如果你有一个功能强大的笔记本电脑与快速互联网连接和/或容器生成器,优势并不明显。 但是,如果您使用功能较弱的机器,故事就会发生变化。 为了模拟这一点,我使用了一个Google Compute Engine f1-micro VM来构建,推送和拉取这些镜像,结果非常惊人!构建(build):
 

Go Onbuild: 52 seconds

Go Multistage: 6 seconds

推送(Push):
 

Go Onbuild: 54 seconds

Go Multistage: 28 seconds

拉取(Pull):
 

Go Onbuild: 48 Seconds

Go Multistage: 16 seconds

在这种情况下,使用较小的容器镜像确实非常有帮助!在Kubernetes上拉取镜像虽然您可能不关心构建和推送容器镜像所需的时间,但您应该非常关心拉取容器镜像所需的时间。 对于Kubernetes,这可能是您的生产集群最重要的指标。例如,假设您有一个三节点集群,其中一个节点崩溃。 如果您使用的是像Kubernetes Engine这样的托管系统,系统会自动生成一个新节点来代替它。但是,这个新节点将是全新的,并且必须先拉取所有容器镜像才能开始工作。 拉取容器镜像所需的时间越长,集群执行的时间就越长!当您增加群集大小(例如,使用Kubernetes Engine Autoscaling)或将节点升级到新版本的Kubernetes时,可能会发生这种情况(接下来的几集中会关注这个问题)。我们可以看到来自多个部署的多个容器的拉取性能可以在这里真正体现出来,使用小容器可能会浪费几分钟的部署时间!安全性和漏洞除了性能之外,使用较小的容器镜像还有很大的安全性提升。 与使用大基础镜像的容器镜像相比,小容器镜像通常具有较小的攻击面。

几个月前,我构建了Go onbuild和multi-age容器镜像,因此它们可能包含一些已被发现的漏洞。 使用Google Container Registry的内置漏洞扫描,可以轻松扫描容器中的已知漏洞。 让我们看看我们能找到些什么。

e1cf04955f24bcad81e4be2cdca0ab2282a3001d

哇,这次两者之间有很大的不同! 较小容器镜像中只有三个“中级”漏洞,而较大容器镜像中有16个严重漏洞和300多个其他漏洞。

让我们深入了解更大容器镜像里面存在的问题。

81082ed93234e8177d04c7147536dd64b50776cb

您可以看到大多数存在的问题与我们的应用程序无关,甚至没有我们的应用程序! 因为多阶段构建使用的是更小的基础镜像,所以可以产生漏洞的东西更少。结论

使用小容器镜像的性能和安全优势不言而喻。使用小的基础镜像和builder pattern可以更容易地构建小镜像,并且还有许多其他技术可用于单个技术栈和编程语言,以最小化容器体积。 无论你做什么,你都可以确信你保持容器镜像最小化的努力是值得的!


原文发布时间为:2018-09-18

本文作者:kelvinji2009

本文来自云栖社区合作伙伴“数据和云”,了解相关信息可以关注“数据和云”。

原文链接:https://yq.aliyun.com/articles/641981
关注公众号

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。

持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。

转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。

文章评论

共有0条评论来说两句吧...

文章二维码

扫描即可查看该文章

点击排行

推荐阅读

最新文章