李迟按:原文地址在这里 。笔者根据自己的理解进行概译,即不按字面逐句翻译。无甚目的,纯粹试一下个人英文还行不行。
当我们选择 Docker 镜像时,一般会建议使用 Alpine 版本。使用该版本可以使镜像体积更小,构建速度也会更快。如果使用 Go 镜像,这是没问题的。
但是,当使用 Python 镜像时,如果选择 Alpine 版本,会遇到下面的问题:
1、构建速度变慢。
2、镜像体积变大。
3、浪费时间。
4、有时会带来一些莫名其妙的运行时bug ( runtime bug)。
下面我们来看看为什么 Alpine 版本虽然被推荐但却不适合于 Python 应用。
为何推荐 Alpine
假设我们在构建镜像时需要安装 gcc,下面来看看 Alpine 版本镜像和 Ubuntu 18.04 版本在构建耗时和镜像体积两方面的比对。
首先,拉取两者的镜像并查看体积:
1 | $ docker pull --quiet ubuntu:18.04 |
可以看到,两个基础镜像中,Alpine 的体积要小得很多。
接着,我们在两个镜像中安装 gcc 。先在 Ubuntu 中安装:
1 | FROM ubuntu:18.04 |
注:Dockerfile 已经超出本文讨论范围,本文的 Dockerfile 并非最佳实践,如果涉及的话,会添加本文的复杂度。如果要在生产环境中使用 Python,可参考如下两种方法:
- DIY制作:详细讲解,附带例子和参考资料 。
- 使用 ASAP:最佳实践的模板 。
下面构建并查看耗时和镜像体积:
1 | $ time docker build -t ubuntu-gcc -f Dockerfile.ubuntu --quiet . |
现在看看相同操作的 Apline 的 Dockerfile:
1 | FROM alpine |
同样构建并查看耗时和镜像体积:
1 | $ time docker build -t alpine-gcc -f Dockerfile.alpine --quiet . |
对比两者的结构,可以看到,Alpine 版本构建速度更快,体积更小。相对于 Ubuntu的 30 秒,Alpine 只用 15 秒,体积方面,Ubuntu 为150MB,而 Alpine 只有 105MB。
构建 Python 镜像
我们需要在 Python 应用程序中使用pandas
和matplotlib
两个库。可以选择基于 Debian 的官方 Python 镜像(已提前拉取镜像),下面是 Dockerfile:
1 | FROM python:3.8-slim |
构建镜像:
1 | $ docker build -f Dockerfile.slim -t python-matpan. |
镜像体积为 363MB。
Alpine 版本是否更好?我们试一下:
1 | FROM python:3.8-alpine |
构建镜像:
1 | $ docker build -t python-matpan-alpine -f Dockerfile.alpine . |
出错了,到底发生了什么?
标准PyPI wheel 无法在 Alpine 上工作
如果观察上面用基于 Debian 镜像构建的信息,可以发现下载的文件为matplotlib-3.1.2-cp38-cp38-manylinux1_x86_64.whl
。这是预编译的二进制文件。但是,在 Alpine 上,即是下载源码文件(matplotlib-3.1.2.tar.gz
),因为标准的 Linux wheel 不支持 Alpine Linux。
为何?大多数 Linux 发行版本使用 GNU 版本 C 库(即 glibc),可适用于大部分 C 程序,包括 Python。但 Alpine Linux 使用的是 musl(译注:musl 是一个轻量级的C标准库),那些 wheel 使用的是 glibc,所以 Alpine Linux 不支持。
当前大部分 Python 包在 PyPI 上都有二进制 wheel,可节省安装时间。但是如果使用 Alpine Linux,则需要编译安装包的 C 代码。
这也意味着需要自行确认每个包的依赖包。在上述例子中,经搜索已经确认了依赖包,下面是修改后的 Dockerfile:
1 | FROM python:3.8-alpine |
接着构建,竟花了 25 分 57 秒!镜像体积为 851 MB。
下面是两个镜像的对比表:
1 | Base image Time to build Image size Research required |
可以看到,alipine 版本构建更慢,而且体积更大。于是笔者做了大量的研究。
能否解决这些问题?
构建时长
为了加快构建的时间,下一个稳定版本 Alpine Edge 集成了 matplotlib 和 pandas。安装系统包非常快。但是,到2020年1月为止,当前稳定的发布版本还不包括这些流行的软件包。
即使它们是能用,PyPI 上的系统包也经常滞后(更新不快),因此,Alpine 不太可能打包 PyPI 上所有的东西。实际上,笔者认识的大多数 Python 团队都不使用系统包来实现 Python 依赖性,而且使用 PyPI 或 Conda Forge。
镜像大小
有的人可能会说,可以删除原始安装好的软件包,或者不缓存软件包,或者使用多阶段构建 。有人尝试了,但产生了470MB的镜像 。
因此,我们可以得到一个与 slim 版本体积大致相同的镜像,但是 Alpine Linux本来的动机就是更小的镜像以及更快的构建。如果做足了工作,可能会得到一个更小的镜像,但还是要忍受 1500 秒的构建时间,而使用 python:3.8-slim 只需要30秒的构建时间。
而且,还会有其它问题。
Alpine Linux 可能引发未知的运行时bug
虽然理论上Alpine使用的muslc库与其他Linux发行版使用的glibc基本兼容,但在实践中,这种差异可能会导致问题。当问题真的发生的时候,它们将是奇怪的和出乎意料的。
一些例子:
Alpine的线程默认堆栈大小较小,这可能导致Python崩溃。
一位Alpine用户发现,由于musl与glibc分配内存的方式不同,他们的Python应用程序要慢得多。
我曾经在使用WeWork协同工作空间的WiFi时,无法在minikube(虚拟机中的Kubernetes)上运行的Alpine图像中进行DNS查找。原因是WeWork的DNS设置不好,Kubernetes和minikube做DNS的方式,musl对这个边缘案例的处理与glibc的处理相结合。musl没有错(它与RFC匹配),但我不得不浪费时间找出问题所在,然后切换到基于glibc的图像。
另一个用户发现了时间格式化和解析的问题。
这些问题中的大部分或可能全部已经解决,但毫无疑问还有更多的问题需要发现。这类随机破损只是另一个需要担心的问题。
不要使用 Alpine 版本的 Python 镜像
除非您希望构建速度大大减慢、图像更大、工作量更大,并且有可能出现模糊的bug,否则您应该避免将Alpine Linux作为基础图像。有关您应该使用什么的一些建议,请参阅我关于选择一个好的基本映像的文章。