Wayfair的Python平台团队维护了一组基本Docker镜像,供所有已部署的Python应用程序使用。维护这些基础图像使我们能够:

  • 标准化我们的环境(我们所有的图像都基于CentOS)
  • 简化应用程序开发人员的部署(发布的映像预先配置为连接到内部PyPi和RPM镜像)
  • 提高安全性(我们采取措施加强将部署到生产中的图像)。

我们最近重构了我们的图像,使它们更有效减少我们的图像大小超过50%。这些改进不仅仅是对开发人员友好的优化:除了减少映像构建时间和存储需求外,许多优化还使映像更加安全,因为映像复杂性的降低降低了总体攻击面积。继续往下读,看看我们是如何取得这个伟大的结果的。

优化工具

我们使用并推荐潜水剖面图像。它提供了快速浏览的图形界面图像层并计算一个“效率分数”,以突出浪费空间的图像。

下面是一个示例潜水报告的图像centos: centos7,这与Wayfair上使用的基本CentOS图像相似。

相比之下,以下是在CentOS 7.5上运行Python 3.8的一个未优化的基础图像的潜水输出:高达931 mb(作为比较的基础,开源软件的总规模python: 3.8.5图像是882 mb)。

继承自CentOS基础图像的层用黄色高亮显示,而在Python图像中引入的最大层用红色高亮显示。Dive显示生成所选层的命令(以白色突出显示)。

为了优化我们的图像,我们对每个最大的层进行步进,以确定哪些可以删除或裁剪。下面是图像优化版本的潜水报告。

经过优化,最终的图像是414MB,减少超过50%!我们通过以下策略实现了这一减少。

优化策略

当安装包时,清理在同一层

在上面未优化的图像中,最大的层为324MB,是由命令生成的安装libcurl。这个层之所以如此之大,是因为yum命令生成一个缓存,从而引入了大量的cruft。

因此,在运行yum install时,最好在一个命令中安装所有包,并始终在同一步骤中删除缓存。

centos: centos7 #避免这种运行yum安装- y foo运行yum安装- y bar #更好,但仍然坏运行yum安装- y \ foo \酒吧#喜欢这个运行yum安装酒吧- y \ foo \ \ & &百胜清洁所有\ & & rm射频/var/cache/yum

通过将命令更新为安装libcurl && yum clean all && rm -rf /var/cache/yum,层大小从324MB减少到23MB,减少了93%

同样的原则也适用于其他语言的包管理器;大多数都针对尺寸敏感的Docker图像的本地开发进行了优化。始终尝试在一个步骤中安装所有包,并禁用包管理器的缓存。例如:

  • Python:禁用缓存皮普——no-cache-dir国旗
  • apt-get:使用——no-install-recommends国旗在安装包和清理apt-get clean && rm -rf /var/lib/apt/lists/*

在构建或编译代码时使用多级构建

我们从源代码编译包含在基本映像中的Python发行版,这需要许多构建依赖项。总的来说,这些依赖项超过200MB,并且只在编译时使用。多级构建防止这些依赖关系在最终的映像中发布,提高映像的大小,并减少在已部署的映像中发布GNU工具链和其他二进制文件所带来的攻击向量。

而不是:

安装一些构建python包所需的系统依赖项,运行yum Install -y \ gcc \ && yum clean all \ && rm -rf /var/cache/yum COPY requirementss.txt。运行pip install -r requirements.txt复制应用程序代码/。入口点["python"] CMD["脚本。py”)

喜欢这个:

从python-base-image: 3.8.5 builder #安装一些系统构建python包所需的依赖项运行yum安装gcc - y \ \ & &百胜打扫所有\ & & rm射频/var/cache/yum #创建一起virtualenv继续依赖运行python - m venv / opt / venv ENV路径= " / opt / venv / bin: $路径”#依赖安装到虚拟环境让副本。复制-从=builder /opt/venv/ opt/venv ENV路径="/opt/venv/bin:$PATH"复制应用程序代码/。入口点["python"] CMD["脚本。py”)

鉴于docker容器在本质上是隔离的,在docker映像中使用上述示例中的virtualenv似乎违反直觉,但它们有助于在构建阶段之间轻松复制所有Python依赖项。使用这种方法时要小心pip安装使用动态链接依赖关系的包(例如,包依赖于一个没有与包本身绑定的C库),因为这些依赖关系也必须安装在最终的映像中,而不仅仅是在中间构建步骤中。

避免运行chmod运行乔恩步骤

当执行更改文件权限或文件所有权的命令时,Docker会创建该文件或目录的副本。这可能会导致严重的膨胀。避免这种情况发生,乔恩在相同的步骤,内容复制到图像:

从centos:centos7 #不要做这个副本。/ /应用程序运行chown -R 1001:100 /应用程序#更喜欢这个副本-chown=1001:100 ./ /应用程序

码头工人了最近实现的一个修改文件权限选择复制,但只有当Buildkit后端启用。在使用传统构建后端时,要么在文件复制到映像之前设置权限模式,要么使用多级构建,其中修改文件权限命令只在中间映像中运行。

通过消除,我们能够从图像中消除185MB修改文件权限命令。

在将应用程序数据复制到图像之前要仔细考虑

我们遇到的许多dockerfile包括复制。/。/将所有应用程序数据复制到映像中的最后步骤之一。虽然这样做很方便,但可能会导致生产映像(测试套件、文档等)中出现不必要的麻烦。

考虑包括.dockerignore文件在项目中,有意地将不必要的文件排除在最终的图像中。

结论

如果你觉得这很有趣,你可能会喜欢读Wayfair 's创新的方法构建一个Python平台团队,或者听这个podcast.__init__关于巨蟒的那集。如果你想了解更多,请联系我们!我们是目前招聘我们一直在寻找资深的Pythonistas加入我们的团队。