如何为 Python 应用选择最好的 Docker 镜像?

前言

在使用 Python 的早些年,为了解决 Python 包的隔离与管理 virtualenvwrapper 就成为我的工具箱中重要的一员。后来,随着 Python 3 的普及,virtualenvwrapper 逐渐被 venv 所替换。毕竟 venv 是 Python 3 的标配,优点是显而易见的。而这几年,应用场景的的复杂性越来与高,无论是开发还是部署都需要设置复杂的环境。例如使用 Redis 实现消息队列,用 Psycopg 完成对于 PostgreSQL 数据库的存取等等。随之而来 Docker 就变成了程序员必不可少的常备工具。为了掌握如何将我的 Python 应用与 Docker 结合起来,就要学习他人的经验分享。于是一次又一次地看到了下面这样的 Dockerfile 例子:

如何为 Python 应用选择最好的 Docker 镜像?
相比起来,我不熟悉 Alpine 这个 Linux 发行版本。我很困惑这个版本难道比其它哪些老字号的 Linux 发行版更适合 Docker 的环境吗?至于我的 Python 应用,究竟选择哪一个 Docker 基础镜像更好呢?

对于 Docker 基础镜像的要求

为我的 Python 应用构建一个 Docker 镜像并不是要从零开始,而是从现有的 Linux 基础镜像开始构建。这些基础镜像除了提到过的 Alpine 以外 还有我更熟悉的 Ubuntu、Centos 、Debian 等等。在决定选择哪一个之前,我们需要回答的一个问题就是:

  • 我们究竟对于这个 Docker 基础镜像有哪些要求?
  • 这些要求既要满足通常意义上我们对操作系统的要求,也要满足对于 Python 应用的特殊的需求。

归纳起来,我们的需求应当包括这几点:

  • 稳定性:要求今天的构建与以后的构建有相同的基本库、目录结构和基础结构。否则我们的 Python 应用程序将将有可能存在不确定的隐患。

  • 安全更新:需要基础镜像得到良好维护,以便及时获取基本操作系统的安全更新

  • 最新的依赖关系:除非我们的应用仅仅是一个简单的 Python 程序,否则就不得不依赖操作系统所提供 的库和应用程序(例如:GCC 编译器、OpenSSL 库等)。我们需要这些应用和库要足够新,否则就会有各种安全性的问题或者功能性的不足。

  • 丰富的库资源:对于某些应用,可能需要安装一些不太流行的库(例如 lxml 等)。这就需要我们选择的基本镜像提供丰富的库资源。

  • 最新的 Python 版本:虽然可以通过自行安装 Python 来解决,但是拥有最新的 Python 的版本无疑可以节省我们的时间、精力。

  • 小型的 Docker 镜像:在所有条件都相同的情况下,拥有尺寸较小的 Docker 镜像无疑更胜一筹。无论是镜像的构建还是占用存储空间的开销,更小的尺寸一定更有优势。

考虑到应用部署在产环境的需要,我们所选择的 Docker 镜像还应当具备长期支持(Long-term support, LTS) 的承诺。

长期支持 (英语:Long-term support,缩写:LTS)是一种软件的产品生命周期政策,特别是开源软件,它增加了软件开发过程及软件版本周期的可靠度。长期支持延长了软件维护的周期;它也改变了软件更新(补丁)的类型及频率以降低风险、费用及软件部署的中断时间,同时提升了软件的可靠性。但这并不必然包含技术支持。
– 维基百科

Linux 镜像版本的选择

围绕着上述的需求,我们很容易就会找到一批候选的版本。乍看起来,这些基础镜像应该能够满足我们的需要。接下来我们需要仔细甄别其具体的差异,以找出最适合我们的版本,尤其是适合我们的 Python 应用。

选项一:传统的 Linux 分发版本 – Ubuntu TLS、CentOS 以及 Debian

这三个 Linux 分发版本历史久远(Debian 早在 1993 年就已出现),名气很大,在 Linux 服务器市场上拥有广泛的用户。

  • Ubuntu 18.04(Docker 镜像的名字 ubuntu:18.04)发布于 2018 年 4 月,由于这是 Canonical 公司的长期支持版本(LTS),意味着该版本的用户在 2023 年之前都将获得安全更新。
  • CentOS 8( Docker 镜像的名字 centos:8 )于 2019 年发布,将在 2024 年前进行全面更新,并在 2029 年前提供维护更新。
  • Debian 10(Docker 镜像的名字 debian:buster)发布于 2019 年 7 月,承诺支持到 2024 年。

需要注意的是这些镜像预安装的 Python 有可能不是最新的版本。例如 Ubuntu 18.04 预安装的是 Python 3.6.7,而 Python 3 的最新稳定版本已经升级为 Python 3.8.1。因此我们必须在构建 Docker 镜像的时候去完成 Python 的安装或者升级。在一些特定的 Linux 分发版本中,我们甚至需要自行通过编译 Python 源码的方式来获得最新版本的 Python。例如在 CentOS 8 中,就需要用这个办法来安装 Python3.8。至于具体的办法,可以参考在“Python 3.8 已经来了,你准备好了吗?”一文中的介绍。

https://amazonaws-china.com/cn/blogs/china/python-3-8-is-here-are-you-ready/

选项二:Docker 官方的 Python 镜像

这个 Docker 镜像由 Docker 官方提供。该版本的最大特点就是预装了 Python,并且提供多个不同 Python 版本的选项,例如 Python 3.7、Python 3.8、Python 3.9。需要注意的是,这个版本提供了多个不同的变体,如果搞不清楚这一点很容易在使用中遇到难以预料的问题。这些差异很大的变体包括了:

  • Alpine Linux, 关于这个版本,后面会有专门的讨论
  • Debian Buster, 这是基于 Debian 的一个分支版本,是基于 Debian 的标准镜像的 Layer 来构建的。特点是基础库很完整,缺点是尺寸较大,磁盘的利用率较低。
  • Debian Buster slim,这个版本是针对 Debian Buster 的“瘦身”后的版本。尺寸小,磁盘利用率高是其优点。但是,它缺少通用的包,可能会导致对部分的应用支持不好。

选项三:云计算上的 Linux 镜像 – Amazon Linux 2

今天当我们谈到 Docker 在生产环境中的部署的时候,不能缺少的一个话题就是云计算。相比较起来,云计算上 Linux 以及 Docker 的使用与部署与我们熟悉的传统方式有一些区别。例如安全性的要求、与云计算平台的集成以及管理工具等。于是云计算的平台 Linux 分发版本以及 Docker 镜像也就应需而生。AWS 就提供了 Amazon Linux 的两个版本:Amazon Linux 2 和 Amazon Linux。其中,Amazon Linux 2 是 Amazon Linux 的新一代的 Linux 服务器操作系统版本,这也是 AWS 官方推荐使用的版本。至于选择 Amazon Linux 2 的的理由,简单来说这是一个由 Amazon 提供长期支持的(LTS)、进行了针对性性能优化的、强调安全的、免费的 Linux 分发版本以及 Docker 的镜像。

选项四:Alpine 镜像

很难想象,Alpine 这个 Linux 发行版已经有了 14 年的历史。但广为大家所熟知还是要拜 Docker 的流行所赐。最初,Alpine Linux 是 LEAF 项目(Linux Embedded Appliance Framework Project)的一个分支。LEAF 项目的设计的目标是开发出一个可以放在单个软盘上的 Linux 发行版,而后继的 Alpine 在此基础之上附加了一些更常用的软件包例如 Squid、Samba 等等。因此,Alpine 的典型特征就是“尺寸小”!当然为达成这个目标就要做出妥协。为减少尺寸就不得不放弃我们熟悉的 Linux 环境中最常见的 GNU C Library(glibc)

https://en.wikipedia.org/wiki/GNU_C_Library

取而代之的是一个迷你版的 C 标准库 musl

http://musl.libc.org/

此外,我们常用的 Linux 工具(例如 grep、ls、cp 等等)则采用了 BusyBox 以求进一步压缩空间。这种努力的结果是非常有成效的,alpine:latest 镜像的尺寸只有区区的5.59MB,而与之相对的 ubuntu:18.04 的镜像的尺寸却高达 64.2MB。简单计算下来,Alpine 的磁盘空间的需求只是 Ubuntu 18.04 的 8.7% !

对比 – Docker 基础镜像的尺寸

想象一下,在真实的生产环境中我们部署的 Docker 实例的数量可能成百、上千。考虑到数量的因素,Docker 镜像的尺寸就应当是我们考量的一个重要依据。此外启动一个 Docker 实例我们往往需要在尽可能短的时间内完成,Docker 镜像的尺寸无疑也是一个关键因素。那么,我们就将上面列举的 Docker 镜像在尺寸方面做一个对比 :

测试日期:2020 年 2 月 28 日
Linux 分发版本 镜像名称 拉取命令 尺寸
Ubuntu ubuntu:18.04 docker pull ubuntu:18.04 64.2MB
Alphine alpine:latest docker pull alpine:latest 5.59MB
Debian debian:buster docker pull amd64/debian:buster 114MB
CentOS centos:8 docker pull centos:8 237MB
Amazon Linux 2 amazonlinux:latest docker pull amazonlinux:latest 163MB
Debian python:3.7 docker pull python:3.7 919MB
Alphine python:3.7-slim docker pull python:3.7-slim 179MB

好了,在这一项的测试中名次如下 :

python:3.7 > centos:8  >  python:3.7-slim > amazonlinux:latest > debian:buster  > ubuntu:18.04 > alpine:latest

如果从这个排名来看 centos 8 无疑表现的差强人意,故被淘汰。从数字来看似乎 alpine 是最好的选择。且慢,我们再来进行下一项测试- 构建时间。

对比 – Docker 镜像的构建时间

在大多数的时间里,我们所使用的 Docker 镜像都需要从基础镜像开始构建。例如下面的这个 Dockerfile 就用来构建一个 Flask 的应用

# Dockerfile-flask

# Simply inherit the Python 3 image.
FROM python:3

# Set an environment variable
ENV APP /app

# Create the directory
RUN mkdir $APP
WORKDIR $APP

# Expose the port uWSGI will listen on
EXPOSE 5000

# Copy the requirements file in order to install
# Python dependencies
COPY requirements.txt .

# Install Python dependencies
RUN pip install -r requirements.txt

# We copy the rest of the codebase into the image
COPY . .

# Finally, we run uWSGI with the ini file

这种模式带来的问题就是我们不得不考虑构建带来的额外的开销。尤其在一个复杂的项目中,我们需要构建的则不仅仅上面这样简单的场景,复杂的应用往往需要一个较长的构建时间。如果构建时间的开销比较大或者比较复杂,则必然增加了额外的管理、部署以及监控的成本。我的这个测试场景比较简单,只是安装 Python3,以及比较常见的 python 包 numpy、matplotlib 和 pandas。看看每一种 Docker 基础镜像的构建所需的时间是多少。

1、ubuntu:18 , 构建时间 1 分 31.044 秒

FROM ubuntu:18.04

RUN apt-get update -y && 
    apt-get install -y python3.7 python3-pip python3.7-dev
RUN pip3 install --upgrade pip
RUN pip3 install --no-cache-dir numpy matplotlib pandas

2、amazonlinux:2  , 构建时间 30.898 秒

FROM amazonlinux:latest

RUN yum update -y && 
yum install -y python3 python3-devel
RUN pip3 install --no-cache-dir numpy matplotlib pandas

3、debian:latest  , 构建时间 52.237 秒

FROM debian:buster

RUN apt-get update -y && 
    apt-get install -y python3 python3-pip python3-dev
RUN pip3 install --no-cache-dir numpy matplotlib pandas

4、python:latest构建时间 35.752 秒

FROM python:3.7

RUN apt-get update -y && 
    apt-get install -y python3-pip

RUN pip3 install pip --upgrade
RUN pip3 install --no-cache-dir numpy matplotlib pandas

5、python:3.7-slim构建时间 53.475 秒

FROM python:3.7

RUN apt-get update -y && 
    apt-get install -y python3-pip

RUN pip3 install pip --upgrade
RUN pip3 install --no-cache-dir numpy matplotlib pandas

6、alpine:latest  ,构建时间 24 分 43.122 秒

FROM alpine:latest

RUN apk update  && 
    apk add  --no-cache --update 
        gcc make automake gcc g++ python3 python3-dev cython freetype-dev
RUN pip3 install --upgrade pip
RUN pip3 install --no-cache-dir numpy matplotlib pandas

测试的结果出来了:

alpine:latest > ubuntu:18.04 > > python:3.7-slim  > debian:buster    > python:last > amazonlinux:latest

确实没有看错,被我们寄予厚望的 Alpine 镜像的构建时间居然是24 分钟以上。与构建速度最快的 Amazon Linux 2 比较起来足足慢了有24 倍的时间!!

如果细心一些,你会发现这个 Dockerfile 与上面的几个不同,多出了 gcc、make、automake、g++这些与编译工具和几个库。事实上,在我第一次构建的时候遇到了这样的错误信息 :

如何为 Python 应用选择最好的 Docker 镜像?

这真是未曾预料的问题啊!深究之下终于发现在 Appine 使用 pip 安装 matplotlib 以及 pandas 的时候,并不是从 PyPi 的仓库中下载 whl 包,而是需要下载源代码然后编译再进行安装的。标准的预编译的 Python 包居然无法直接安装,这究竟是为什么?

答案原来出在 Alpine 使用的 musl 库上。原来几乎全部 Linux 发行版都使用 GNU 版本的 C 标准库(glibc)。但是 Alpine Linux 为了减小存储空间而使用了 musl 库。而我们通过 pip 安装的这些二进制 Python 包是基于 glibc 编译而成的。因此 Alpine 无法安装这些 python 库,只能通过源码编译的方式来进行安装。这也就是为什么 Alpine 的 Docker file 会与其它的不同,以及花费如此之多的时间进行构建的秘密。

我相信熟悉 Alpine 的用户会与我争论,Alpine 的 Edge 版本会解决这个问题。不幸的是,Edge 版本目前还不是稳定版本。如果用于生产环境,我们还是不要自寻烦恼为好。

如何为 Python 应用选择最好的 Docker 镜像?

结论

这个测试也许不能覆盖我们需要的各个角度,但至少给我们提供了一个选择的参考。而我在这个测试之后也得到了自己需要的结果,

  • Alpine 显然不适合作为 Python 应用的基础镜像。尽管它提供了惊人存储的空间上效率,但它对于 Python 包支持的不足的缺陷是难以弥补的。也许 Alpine 更适合于一些对于镜像尺寸敏感的场合,还可以考虑将它用于你的 Go 应用。
  • 在 AWS 云计算的环境中,Amazon Linux 2 的性能表现是有目共睹的。如果你希望得到一个稳定、安全以及高性能的 Python 基础镜像,那就不要忘记 Amazon Linux 2 这个选择。
  • Ubuntu 18.04 以及 Debian 10 表现的中规中矩,完全在我的意料之中。考虑到 Debian 10(Buster)较 Ubuntu 更新一些。这应该是一个好选择。不过随着 Ubuntu 20.04 LTS 即将发布,在我的候选清单上也许要多出一个。
  • 至于 Docker 官方的 Python 镜像,并没有看出明显的优点。考虑到安全性与维护性的问题,我不认为这是个好的选择。
  • 关于 Docker 基础镜像的选择,还需要考虑的一点就是 Linux 一致性的问题。杂乱的、多样化的 Linux 版本会在大规模应用的时候带来不可预估的风险,不可掉以轻心。


(版权归原作者所有,侵删)


原文始发于微信公众号(Raymond运维):如何为 Python 应用选择最好的 Docker 镜像?

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/61302.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!