深入了解Python的官方Docker镜像

python-with-docker

翻译:A deep dive into the official Docker image for Python

Docker的官方Python镜像非常流行,实际上,我建议将其变体之一作为基础镜像。但是许多人不太了解它的作用,这可能导致混乱和破裂。

因此,在这篇文章中,我将介绍它的构造方式,为什么有用,如何正确使用它以及它的局限性。特别是,我将通读截至2020年8月19日python:3.8-slim-buster变体,在进行过程中对其进行解释。

阅读 Dockerfile

基础镜像

我们从基础镜像开始:

1
FROM debian:buster-slim

也就是说,基础镜像是Debian GNU / Linux 10,这是Debian发行版的当前稳定发行版,也称为Buster。

Debian 的所有发行版均以 Toy Story 中的角色命名。如果您想知道,Buster是Andy的爱犬

因此,首先,这是一个Linux发行版,可确保长期稳定,同时提供错误修复。该slim变体安装的软件包较少,因此没有提供编译器。

环境变量

接下来设置环境变量。首先确保/usr/local/bin$PATH 中:

1
2
# ensure local python is preferred over distribution python
ENV PATH /usr/local/bin:$PATH

基本上,Python 镜像是将 Python 安装到/usr/local中,因此可以确保所安装的 Python 可执行文件是默认使用的。

接下来,设置区域设置:

1
2
3
# http://bugs.python.org/issue19846
# > At the moment, setting "LANG=C" on a Linux system *fundamentally breaks Python 3*, and that's not OK.
ENV LANG C.UTF-8

据我所知,即使不这样做,现代Python 3仍将默认为UTF-8,因此我不确定目前是否有必要。

还有一个环境变量可以告诉您当前的Python版本:

1
ENV PYTHON_VERSION 3.8.5

还有一个带有GPG密钥的环境变量,用于在下载Python时验证其源代码。

运行时依赖

运行 Python 需要一些其他软件包:

1
2
3
4
RUN apt-get update && apt-get install -y --no-install-recommends \
		ca-certificates \
		netbase \
	&& rm -rf /var/lib/apt/lists/*
  • ca-certificates是标准证书颁发机构的证书列表,与您的浏览器用来验证https://URLs 的证书相当。这使Python,wget和其他工具可以验证服务器提供的证书。

  • netbase安装了一些文件到/etc。这些文件在将某些名称映射到相应的端口或协议时所需。例如,在这种情况下,/etc/services将服务名称映射https到相应的端口号443/tcp

安装Python

接下来,安装编译器工具链,下载Python源代码,编译Python,然后卸载不需要的Debian软件包:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
RUN set -ex \
	\
	&& savedAptMark="$(apt-mark showmanual)" \
	&& apt-get update && apt-get install -y --no-install-recommends \
		dpkg-dev \
		gcc \
		libbluetooth-dev \
		libbz2-dev \
		libc6-dev \
		libexpat1-dev \
		libffi-dev \
		libgdbm-dev \
		liblzma-dev \
		libncursesw5-dev \
		libreadline-dev \
		libsqlite3-dev \
		libssl-dev \
		make \
		tk-dev \
		uuid-dev \
		wget \
		xz-utils \
		zlib1g-dev \
# as of Stretch, "gpg" is no longer included by default
		$(command -v gpg > /dev/null || echo 'gnupg dirmngr') \
	\
	&& wget -O python.tar.xz "https://www.python.org/ftp/python/${PYTHON_VERSION%%[a-z]*}/Python-$PYTHON_VERSION.tar.xz" \
	&& wget -O python.tar.xz.asc "https://www.python.org/ftp/python/${PYTHON_VERSION%%[a-z]*}/Python-$PYTHON_VERSION.tar.xz.asc" \
	&& export GNUPGHOME="$(mktemp -d)" \
	&& gpg --batch --keyserver ha.pool.sks-keyservers.net --recv-keys "$GPG_KEY" \
	&& gpg --batch --verify python.tar.xz.asc python.tar.xz \
	&& { command -v gpgconf > /dev/null && gpgconf --kill all || :; } \
	&& rm -rf "$GNUPGHOME" python.tar.xz.asc \
	&& mkdir -p /usr/src/python \
	&& tar -xJC /usr/src/python --strip-components=1 -f python.tar.xz \
	&& rm python.tar.xz \
	\
	&& cd /usr/src/python \
	&& gnuArch="$(dpkg-architecture --query DEB_BUILD_GNU_TYPE)" \
	&& ./configure \
		--build="$gnuArch" \
		--enable-loadable-sqlite-extensions \
		--enable-optimizations \
		--enable-option-checking=fatal \
		--enable-shared \
		--with-system-expat \
		--with-system-ffi \
		--without-ensurepip \
	&& make -j "$(nproc)" \
		LDFLAGS="-Wl,--strip-all" \
	&& make install \
	&& rm -rf /usr/src/python \
	\
	&& find /usr/local -depth \
		\( \
			\( -type d -a \( -name test -o -name tests -o -name idle_test \) \) \
			-o \( -type f -a \( -name '*.pyc' -o -name '*.pyo' -o -name '*.a' \) \) \
			-o \( -type f -a -name 'wininst-*.exe' \) \
		\) -exec rm -rf '{}' + \
	\
	&& ldconfig \
	\
	&& apt-mark auto '.*' > /dev/null \
	&& apt-mark manual $savedAptMark \
	&& find /usr/local -type f -executable -not \( -name '*tkinter*' \) -exec ldd '{}' ';' \
		| awk '/=>/ { print $(NF-1) }' \
		| sort -u \
		| xargs -r dpkg-query --search \
		| cut -d: -f1 \
		| sort -u \
		| xargs -r apt-mark manual \
	&& apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false \
	&& rm -rf /var/lib/apt/lists/* \
	\
	&& python3 --version

里面有很多东西,但是基本结果是:

  1. Python已安装到中/usr/local
  2. 所有.pyc文件被删除。
  3. gcc一旦不再需要编译Python所需的程序包等,便将其删除。

由于所有这些操作均在单个RUN命令中发生,因此镜像最终不会将编译器存储在其任何层中,从而使镜像变得更小。

您可能会注意到的一件事是Python需要libbluetooth-dev进行编译。这令人惊讶,所以我追根究底,很明显,Python可以创建蓝牙套接字,但前提是必须在安装了此软件包的情况下进行编译的。

设置别名

接下来,/usr/local/bin/python3获取一个别名/usr/local/bin/python,两种方式都可调用到:

1
2
3
4
5
6
# make some useful symlinks that are expected to exist
RUN cd /usr/local/bin \
	&& ln -s idle3 idle \
	&& ln -s pydoc3 pydoc \
	&& ln -s python3 python \
	&& ln -s python3-config python-config

安装 pip

pip包下载工具都有其自己的发布时间表,与Python的不同。例如,这Dockerfile将安装2020年7月发布的Python3.8.5。 pip20.2.2已于8月发布,但要Dockerfile确保包含较新的版本pip

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# if this is called "PIP_VERSION", pip explodes with "ValueError: invalid truth value '<VERSION>'"
ENV PYTHON_PIP_VERSION 20.2.2
# https://github.com/pypa/get-pip
ENV PYTHON_GET_PIP_URL https://github.com/pypa/get-pip/raw/5578af97f8b2b466f4cdbebe18a3ba2d48ad1434/get-pip.py
ENV PYTHON_GET_PIP_SHA256 d4d62a0850fe0c2e6325b2cc20d818c580563de5a2038f917e3cb0e25280b4d1

RUN set -ex; \
	\
	savedAptMark="$(apt-mark showmanual)"; \
	apt-get update; \
	apt-get install -y --no-install-recommends wget; \
	\
	wget -O get-pip.py "$PYTHON_GET_PIP_URL"; \
	echo "$PYTHON_GET_PIP_SHA256 *get-pip.py" | sha256sum --check --strict -; \
	\
	apt-mark auto '.*' > /dev/null; \
	[ -z "$savedAptMark" ] || apt-mark manual $savedAptMark; \
	apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \
	rm -rf /var/lib/apt/lists/*; \
	\
	python get-pip.py \
		--disable-pip-version-check \
		--no-cache-dir \
		"pip==$PYTHON_PIP_VERSION" \
	; \
	pip --version; \
	\
	find /usr/local -depth \
		\( \
			\( -type d -a \( -name test -o -name tests -o -name idle_test \) \) \
			-o \
			\( -type f -a \( -name '*.pyc' -o -name '*.pyo' \) \) \
		\) -exec rm -rf '{}' +; \
	rm -f get-pip.py

同样,所有.pyc文件均被删除。

Entrypoint

最后,Dockerfile具体说明 Entrypoint:

1
CMD ["python3"]

使用CMD不加 ENTRYPOINTpython默认情况下会在运行镜像时进入 REPL:

1
2
3
4
5
$ docker run -it python:3.8-slim-buster
Python 3.8.5 (default, Aug  4 2020, 16:24:08)
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> 

但是,您还可以根据需要指定其他可执行文件:

1
2
$ docker run -it python:3.8-slim-buster bash
root@280c9b73e8f9:/# 

我们学到了什么?

slim-buster变体,这里有一些要点。

python官方镜像如何包含的Python

尽管这一点似乎很明显,但值得注意的它是如何包含的:它是自定义安装Python到/usr/local

常见错误是通过使用Debian的Python版本再次安装Python:

1
2
3
4
FROM python:3.8-slim-buster

# THIS IS NOT NECESSARY:
RUN apt-get update && apt-get install python3-dev

这会在/usr中而不是在/usr/local中安装额外的Python,并且通常会是其他版本的Python。您可不希望同一镜像中有两个不同版本的Python。大多数情况下,这只会导致混乱。

如果您确实要使用Debian版本的Python,请debian:buster-slim改为使用基本镜像。

python官方镜像包括了最新的pip

例如,Python 3.5的最新版本是在2019年11月,但是python:3.5-slim-buster包含的Docker镜像是pip从2020年8月开始的。(通常)这是一件好事,这意味着您可以获得最新的错误修复,性能改进以及对较新版本的支持。

python官方镜像删除所有.pyc文件

如果要稍微加快启动速度,则可能希望.pyc使用compileall将标准库源代码编译到自己的镜像中。

python官方镜像不安装Debian安全更新

尽管经常会重新生成debian:buster-slimpython镜像,但是在某些窗口中已发布了新的Debian安全修复程序,但是尚未重新生成镜像。您应该将安全更新安装到基础Linux发行版

updatedupdated2020-09-032020-09-03