Kubernetes最佳实践:如何以及为什么构建轻量级容器镜像

原文链接:https://cloud.google.com/blog/products/gcp/kubernetes-best-practices-how-and-why-to-build-small-container-images

编者按:

今天是谷歌开发人员Sandeep Dinesh关于如何最大限度地利用Kubernetes环境的七集视频和博客系列的第一部分。今天,他将处理如何使容器镜像尽可能小的理论和实践问题。

Docker使构建容器变得轻而易举。只需将标准Dockerfile放入文件夹,运行docker ’ build '命令,您的容器镜像已经构建!

这种简单性的缺点是很容易构建出装有很多不需要的应用的大型容器——包括潜在的安全漏洞。

在“Kubernetes最佳实践”这一节中,我们将探索如何使用 Alpine Linux和Dockerbuilder pattern ,创建可用于生产的容器镜像,然后运行一些基准测试,以确定这些容器在Kubernetes集群中如何执行。

根据使用的是解释性语言还是编译语言,创建容器镜像的过程是不同的。就让我们一探究竟吧!

容器化解释性语言程序

解释性语言,如Ruby、Python、Node.js、PHP等通过运行代码的解释器发送源代码。这样做的好处是可以跳过编译步骤,但缺点是需要随代码一起交付解释器。
幸运的是,大多数这些语言都提供预构建的Docker容器,其中包括一个轻量级环境,允许您运行小得多的容器。

我们取一个Node.js 应用程序和容器。首先,让我们使用“node:onbuild”Dockerj镜像作为基础。Docker容器的“onbuild”版本预先打包了您需要运行的所有东西,因此您不需要执行很多配置就可以让它们正常工作。这意味着Dockerfile非常简单(只有两行!)但是您要为磁盘大小付出代价——几乎700MB!

FROM node:onbuild
EXPOSE 8080

1dtu8.PNG

通过使用更小的基础镜像,如Alpine,可以显著减轻量级容器的大小。Alpine Linux是一个小型的轻量级Linux发行版,非常受Docker用户的欢迎,因为它与很多应用程序兼容,但容器仍然很小。

幸运的是,Node有一个官方的Alpine Node.js(以及其他流行语言)镜像,它拥有您需要的一切。与默认的“node”Docker镜像不同,“node:alpine”删除了很多文件和程序,只留下足够运行应用程序的空间。

创建基于Alpine linux的Dockerfile有点复杂,因为您必须运行onbuild镜像为您执行的一些命令。

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

但是,这很值得,因为生成的镜像要小得多,只有65 MB!
2sdk1.PNG

容器化编译语言程序

诸如Go、C、c++、Rust、Haskell等编译语言创建的二进制文件可以在没有很多外部依赖的情况下运行。这意味着您可以提前构建二进制文件并将其发布到生产环境中,而不必发布创建二进制文件(如编译器)的工具。
有了Docker对多步骤构建的支持,您可以轻松地发布二进制文件和最少的搭建。让我们学习。

让我们以一个Go应用程序为例,使用这个模式对其进行容器化。首先,让我们使用“golang:onbuild”Docker镜像作为基础。和以前一样,Dockerfile只有两行,但是同样要付出磁盘大小的代价——超过700MB!

FROM golang:onbuild
EXPOSE 8080

3027r.PNG
下一步是使用更轻量的基础镜像,在本例中是“golang:alpine”镜像。到目前为止,这与我们对解释语言所遵循的过程相同。

同样,使用Alpine基本镜像创建Dockerfile有点复杂,因为您必须运行onbuild镜像为您执行的一些命令。

FROM golang:alpine
WORKDIR /app
ADD . /app
RUN cd /app && go build -o goapp
EXPOSE 8080
ENTRYPOINT ./goapp

但是,得到的镜像要小得多,只有256MB!

4vvof.PNG
但是,我们可以使镜像更小:您不需要任何GO的编译器或附带的其他构建和调试工具,因此可以将它们从最终容器中删除。

让我们使用一个多步骤构建来获取由golang:alpine容器创建的二进制文件,并将其单独打包。

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大小!

5ak48.PNG
在构建这个容器时,您可能会注意到Dockerfile做了一些奇怪的事情,比如手动将HTTPS证书安装到容器中。这是因为基本的Alpine Linux几乎没有预装任何东西。因此,即使您需要手动安装任何和所有依赖项,最终的结果也是非常小的容器!

注意:如果您想节省更多的空间并进一步去掉未使用的依赖项,我建议您查看谷歌中的无发行版项目。Java用户也可以查看Jib。但是,Alpine仍然是一个很好的基本镜像选择,因为使用标准调试工具和安装依赖项要容易得多(代价是需要更多的空间和更高的攻击面积)。

何处构建和存储容器

为了构建和存储镜像,我强烈推荐结合使用谷歌容器构建器和谷歌容器注册表。容器生成器非常快,自动将镜像推送到容器注册表。大多数开发人员应该很容易在免费层完成所有工作,容器注册表的价格与原始谷歌云存储相同(便宜!)
像谷歌Kubernetes Engine这样的平台可以安全地从谷歌容器注册表中提取镜像,而不需要任何额外的配置,这让您的工作变得更加轻松!

此外,容器注册表提供了漏洞扫描工具和IAM支持开箱即用。这些工具可以使您更容易地保护和锁定容器。

评估轻量级容器的性能

人们声称轻量级容器的最大优势是缩短了构建时间和拉取时间。让我们测试一下,使用onbuild创建的容器,以及在多阶段过程中使用Alpine创建的容器!
TL;DR:对于功能强大的计算机或容器构建器,没有显著差异,但是对于较小的计算机和共享系统(如许多CI/CD系统),有显著差异。就绝对性能而言,小镜像总是更好的

在大型机器上构建镜像

对于第一个测试,我将使用一个非常结实的笔记本电脑进行构建。我用的是我们办公室的WiFi,所以下载速度非常快!
6 e5g0.png
对于每个构建,我都会删除缓存中的所有Docker镜像。
Build:

Go Onbuild: 35 Seconds
Go Multistage: 23 Seconds

The build takes about 10 seconds longer for the larger container. While this penalty is only paid on the initial build, your Continuous Integration system could pay this price with every build.

The next test is to push the containers to a remote registry. For this test, I used Container Registry to store the images.
对于较大的容器,构建过程要多花大约10秒。虽然这个代价只在初始构建时付出,但是您的持续集成系统可以在每次构建时付出这个代价。

下一个测试是将容器推送到远程注册表。在这个测试中,我使用容器注册表来存储镜像。

Push:

Go Onbuild: 15 Seconds
Go Multistage: 14 Seconds

这很有趣!为什么推一个12MB的对象和推一个700MB的对象需要相同的时间?原来容器注册表在幕后使用了很多技巧,包括为许多流行的基本镜像提供全局缓存。

最后,我想测试将镜像从注册表拉到本地机器需要多长时间。
Pull:

Go Onbuild: 26 Seconds
Go Multistage: 6 Seconds

在20秒内,这是使用两个不同容器镜像的最大区别。您可以开始看到使用较小的镜像的优势,特别是如果您经常使用镜像的话。

您还可以使用Container Builder在云中构建容器,它的另一个好处是自动将容器存储在容器注册表中。
Build + Push:

Go Onbuild: 25 Seconds
Go Multistage: 20 Seconds

再一次,使用小镜像有一个小优势,但没有我想象的那么戏剧化。

在小机器上构建镜像

那么使用较小的容器是否有优势呢?如果你有一台功能强大的笔记本电脑,有快速的互联网连接和/或容器构建器,那就不需要了。然而,如果您使用的是功能较弱的机器,情况就会发生变化。为了模拟这个场景,我使用了一个普通的谷歌计算引擎f1-micro VM来构建、推拉这些镜像,结果是惊人的!
Pull:

Go Onbuild: 52 seconds
Go Multistage: 6 seconds

Build:

Go Onbuild: 54 seconds
Go Multistage: 28 seconds

Push:

Go Onbuild: 48 Seconds 
Go Multistage: 16 seconds

在这种情况下,使用较小的容器真的很有帮助!

在Kubernetes上拉取

虽然您可能不关心构建和推动容器所需的时间,但是您应该真正关心拉动容器所需的时间。对于Kubernetes,这可能是生产集群最重要的度量标准。

例如,假设有一个3个节点的集群,其中一个节点崩溃。如果您使用的是像Kubernetes引擎这样的托管系统,系统会自动旋转一个新节点来代替它。

但是,这个新节点将是全新的,在它开始工作之前必须拉出所有容器。拖拽容器的时间越长,集群的性能就越差!

当您增加集群大小(例如,使用Kubernetes引擎自动伸缩),或者将节点升级到Kubernetes的新版本时(请继续关注这方面的后续内容),就会发生这种情况。

我们可以看到,从多个部署中提取多个容器的性能可以在这里得到提高,使用轻量级容器可能会节省部署时间!

安全漏洞

除了性能外,使用较小的容器还具有显著的安全性优势。与使用大型基础镜像的容器相比,轻量级容器的攻击面通常较小。

我在几个月前构建了Go“onbuild”和“multistage”容器,因此它们可能包含一些后来发现的漏洞。使用容器注册中心的内置漏洞扫描,很容易扫描您的容器中已知的漏洞。让我们看看我们发现了什么。
7j639.PNG
哇,这两者之间有很大的区别!在较小的容器中只有3个“中等”漏洞,而在较大的容器中有16个关键漏洞和300多个其他漏洞。

让我们深入研究一下,看看更大的容器有哪些问题。

8wy9q.PNG
你可以看到,大多数问题与我们的应用程序无关,而是我们甚至没有使用的程序!由于多级镜像使用的是更小的基镜像,因此可以折衷的东西更少。

结论

使用轻量级容器的性能和安全性优势不言而喻。使用轻量级基础镜像和“构建器模式”可以更容易地构建轻量级镜像,而且对于单个堆栈和编程语言,还有许多其他技术可以最小化容器大小。无论您做什么,您都可以确信您为保持容器小而付出的努力是值得的!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值