本文使用「署名 4.0 国际 (CC BY 4.0)」许可协议,欢迎转载、或重新修改使用,但需要注明来源。 [署名 4.0 国际 (CC BY 4.0)](https://creativecommons.org/licenses/by/4.0/deed.zh) 本文作者: 苏洋 创建时间: 2021年10月14日 统计字数: 7428字 阅读时间: 15分钟阅读 本文链接: https://soulteary.com/2021/10/14/use-language-centric-container-base-image-distroless.html ----- # 使用以语言为中心的容器基础镜像 distroless 关于容器技术,我之前分享[不少文章和技巧](https://soulteary.com/subject/tech/#docker-%E5%92%8C-k8s-%E7%9B%B8%E5%85%B3%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98),包括如何优化镜像,如何更优雅的进行构建封装,以及大量的容器应用实践、使用案例以及维护方式。 本篇文章将介绍一个在许多场景下更有效的方案,来让容器镜像更加小巧。比如我们常用的 Node 应用,使用这个方式将减少至少 800M 磁盘空间。 ## 写在前面 以往构建镜像,我们往往会使用来自 DockerHub 上的基础系统镜像(来自 mirror 的镜像同理)或者一些编程语言维护组织推出的官方镜像,来做为基础镜像,来进行我们自己的容器的二次封装和构建。 虽然这样做可以相对快速和轻松的构建应用镜像,但是它往往会带来包含 90% 无用文件的大体积镜像,即使我们进行多阶段构建,依旧不能很好的解决这个问题。严重的时候,甚至会将包含 CVE 漏洞的组件引入镜像中。 虽然 Alpine 镜像已经很小了,但是它依旧包含了许多不必要的组件。那么有没有可能让我们的镜像里**不包含包管理工具、SHELL、冗余的二进制文件,只包含最小的可运行系统,以及我们的语言 Runtime,或者核心的 glibc 依赖呢**? ## 项目背景 有人用上面提到的思路做了一个项目:**distroless**,这个项目来自谷歌云:[https://github.com/GoogleContainerTools/distroless](https://github.com/GoogleContainerTools/distroless) 在继续聊如何使用它,以及使用注意事项之前,先来看看都有“谁”在使用它吧。 - Kubernetes,[Rebase Kubernetes Main Master and Node Images to Distroless/static](https://github.com/kubernetes/enhancements/blob/master/keps/sig-release/1729-rebase-images-to-distroless/README.md#distroless-and-previous-work) - K8S 从2020 年开始使用 distroless ,解决了之前因为使用 debian base,每月数次打安全补丁,导致重新构建、重新分发的问题。同时也让软件镜像整体更轻薄。 - knative,[Knative Serving release v0.6.0](https://github.com/knative/serving/releases/tag/v0.6.0) - knative 从两年前立项初期便开始使用 distroless,并使用 non-root 方式来提供封装好的镜像,以提高安全的下限。 - tekton,[https://github.com/tektoncd/pipeline/releases/tag/0.2.0](https://github.com/tektoncd/pipeline/releases/tag/0.2.0) - tekon 也是在两年前立项初期便开始使用 distroless,同样是使用 non-root 方式提供封装好的镜像,以提高安全的下限。 可能你会好奇,这些镜像除了“安全之外”,镜像尺寸到底能有多小,我们使用官方的介绍数据: > Distroless 镜像非常小,其中最小的镜像 gcr.io/distroless/static约为 650 kB。这大约是alpine (\~ 2.5 MB) 大小的 25% ,以及不到 debian (50 MB)大小的 1.5% 。 虽然官方目前已经提供了多数场景下所需要的镜像,比如: - 适合静态编译语言运行的镜像:C,C++,Go,Rust。 - 适合动态语言使用的镜像:Java,Python,Node 然而,在实际过程中,你可能会遇到需要自定义构建的需求,如何进行镜像构建呢?可以参考 [https://github.com/bazelbuild/rules\_docker](https://github.com/bazelbuild/rules_docker) 项目定制你的镜像。此外,除了 Python 镜像尚在试验阶段外,其实所有的镜像都适合和已经投入生产环境经过了大量验证。 下面我们来看看如何使用 distroless 。 ## 如何使用镜像 在我的网站“知识地图”中,可以找到循序渐进的关于[《如何优化 Docker 镜像尺寸》](https://soulteary.com/subject/tech/#docker-%E5%92%8C-k8s-%E7%9B%B8%E5%85%B3%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98)的几篇文章,我们使用 distroless 镜像的场景,依旧是依赖“多阶段构建”的方式来减少最终产物的尺寸。 比如,我们要构建一个 golang 应用的镜像,只需要在原本镜像文件下面添加三行语句即可: ```bash FROM golang as build WORKDIR /go/src/app ADD . /go/src/app RUN go get -d -v ./... RUN go build -o /go/bin/app # 考虑到可能需要使用 glibc libssl openssl,我们使用 base 镜像 # 如果不需要使用上述依赖,可以切换为 static 镜像,让产物尺寸更小巧 FROM gcr.io/distroless/base COPY --from=build /go/bin/app / CMD ["/app"] ``` ## 使用过程中的问题 下面来聊聊实际使用过程中的常见的两个问题:网络问题和调试问题。 ### 问题一:网络问题 在构建应用镜像过程中,我们一般需要切换镜像进行调试,从而选择出最适合的基础镜像,所以潜在的需求是将各种语言适用的镜像都“下载”下来。 因为众所周知的网络问题,所以一般使用的情况下,我们可能会遇到网络不通而无法下载镜像的问题,类似下面这样。 ```bash docker pull gcr.io/distroless/cc Using default tag: latest Error response from daemon: Get "https://gcr.io/v2/": context deadline exceeded ``` 解决问题的方法也很简单,和[《简单的 Kubernetes 集群搭建》](https://soulteary.com/2018/10/03/how-to-get-your-k8s-cluster.html#%E8%8E%B7%E5%8F%96%E9%95%9C%E5%83%8F%E5%B9%B6%E5%AF%BC%E5%87%BA%E4%B8%BA%E7%A6%BB%E7%BA%BF%E9%95%9C%E5%83%8F%E5%8C%85)一文中的方式类似,我们使用云服务器批量获取和镜像这些容器镜像即可。 不过因为镜像列表只在 [https://console.cloud.google.com/gcr/images/distroless](https://console.cloud.google.com/gcr/images/distroless) 有存放,相比较根据 GCE API 去获取内容再进行解析,最简单的方式便是写两条简单的 “JS 脚本”,从网页中将这些镜像枚举出来: ```js Array.from(document.querySelectorAll('.cfc-table-element.cfc-md1 tbody tr>td:nth-child(1)')).map(n=>`docker pull gcr.io/distroless/${(n.innerText).trim()}`).join('\n'); Array.from(document.querySelectorAll('.cfc-table-element.cfc-md1 tbody tr>td:nth-child(1)')).map(n=>`docker save gcr.io/distroless/${(n.innerText).trim()} -o ${(n.innerText).trim()}.tar`).join('\n') ``` 在网页控制台中运行后,我们会得到下面的命令: ```bash docker pull gcr.io/distroless/base docker pull gcr.io/distroless/base-debian10 docker pull gcr.io/distroless/base-debian11 docker pull gcr.io/distroless/base-debian9 docker pull gcr.io/distroless/cc docker pull gcr.io/distroless/cc-debian10 docker pull gcr.io/distroless/cc-debian11 docker pull gcr.io/distroless/cc-debian9 docker pull gcr.io/distroless/dotnet docker pull gcr.io/distroless/dotnet-debian10 docker pull gcr.io/distroless/dotnet-debian9 docker pull gcr.io/distroless/java docker pull gcr.io/distroless/java-debian10 docker pull gcr.io/distroless/java-debian11 docker pull gcr.io/distroless/java-debian9 docker pull gcr.io/distroless/nodejs docker pull gcr.io/distroless/nodejs-debian10 docker pull gcr.io/distroless/nodejs-debian11 docker pull gcr.io/distroless/nodejs-debian9 docker pull gcr.io/distroless/python2.7 docker pull gcr.io/distroless/python2.7-debian10 docker pull gcr.io/distroless/python2.7-debian9 docker pull gcr.io/distroless/python3 docker pull gcr.io/distroless/python3-debian10 docker pull gcr.io/distroless/python3-debian11 docker pull gcr.io/distroless/python3-debian9 docker pull gcr.io/distroless/static docker pull gcr.io/distroless/static-debian10 docker pull gcr.io/distroless/static-debian11 docker pull gcr.io/distroless/static-debian9 docker save gcr.io/distroless/base -o base.tar docker save gcr.io/distroless/base-debian10 -o base-debian10.tar docker save gcr.io/distroless/base-debian11 -o base-debian11.tar docker save gcr.io/distroless/base-debian9 -o base-debian9.tar docker save gcr.io/distroless/cc -o cc.tar docker save gcr.io/distroless/cc-debian10 -o cc-debian10.tar docker save gcr.io/distroless/cc-debian11 -o cc-debian11.tar docker save gcr.io/distroless/cc-debian9 -o cc-debian9.tar docker save gcr.io/distroless/dotnet -o dotnet.tar docker save gcr.io/distroless/dotnet-debian10 -o dotnet-debian10.tar docker save gcr.io/distroless/dotnet-debian9 -o dotnet-debian9.tar docker save gcr.io/distroless/java -o java.tar docker save gcr.io/distroless/java-debian10 -o java-debian10.tar docker save gcr.io/distroless/java-debian11 -o java-debian11.tar docker save gcr.io/distroless/java-debian9 -o java-debian9.tar docker save gcr.io/distroless/nodejs -o nodejs.tar docker save gcr.io/distroless/nodejs-debian10 -o nodejs-debian10.tar docker save gcr.io/distroless/nodejs-debian11 -o nodejs-debian11.tar docker save gcr.io/distroless/nodejs-debian9 -o nodejs-debian9.tar docker save gcr.io/distroless/python2.7 -o python2.7.tar docker save gcr.io/distroless/python2.7-debian10 -o python2.7-debian10.tar docker save gcr.io/distroless/python2.7-debian9 -o python2.7-debian9.tar docker save gcr.io/distroless/python3 -o python3.tar docker save gcr.io/distroless/python3-debian10 -o python3-debian10.tar docker save gcr.io/distroless/python3-debian11 -o python3-debian11.tar docker save gcr.io/distroless/python3-debian9 -o python3-debian9.tar docker save gcr.io/distroless/static -o static.tar docker save gcr.io/distroless/static-debian10 -o static-debian10.tar docker save gcr.io/distroless/static-debian11 -o static-debian11.tar docker save gcr.io/distroless/static-debian9 -o static-debian9.tar ``` 将上面的内容保存为脚本,扔到服务器上执行,不一会我们所需要的镜像就都会以 `tarball` 的形式规规矩矩的躺在文件夹里了。之后将这些镜像按需下载和载入就能正常使用啦。 ### 问题二:调试模式 前文提到过,由于**生产版本的 distroless 镜像中不包含 SHELL**,所以我们常规的镜像调试方法,`docker exec -it` 便无法使用了。 官方迫于这个实际的开发需求,便提供了配套的调试镜像:包含 busybox shell 的 `debug` 镜像。调试镜像使用方式也非常简单,在之前使用的镜像名称后,添加 `debug` 作为版本号即可,以前文中的 `base` 镜像为例: ```bash FROM golang as build WORKDIR /go/src/app ADD . /go/src/app RUN go get -d -v ./... RUN go build -o /go/bin/app # 在镜像后添加 debug 标签 FROM gcr.io/distroless/base:debug COPY --from=build /go/bin/app / CMD ["/app"] ``` 重新构建镜像后,`docker exec -it` 便又能正常使用啦。 这些调试镜像对应的获取脚本可以使用下面的脚本: ```js Array.from(document.querySelectorAll('.cfc-table-element.cfc-md1 tbody tr>td:nth-child(1)')).map(n=>`docker pull gcr.io/distroless/${(n.innerText).trim()}:debug`).join('\n'); Array.from(document.querySelectorAll('.cfc-table-element.cfc-md1 tbody tr>td:nth-child(1)')).map(n=>`docker save gcr.io/distroless/${(n.innerText).trim()} -o ${(n.innerText).trim()}-debug.tar`).join('\n') ``` ## 最后 关于如何更好的管理这些镜像,我推荐你浏览之前的内容,私有化容器仓库:[《Harbor & Distribution》](https://soulteary.com/subject/tech/#harbor--distribution)。 --EOF