概述
???Reference:
這篇文章是關(guān)于制作 Python Docker 容器鏡像的最佳實(shí)踐绞绒。(2022 年 12 月更新)
最佳實(shí)踐的目的一方面是為了減小鏡像體積疤祭,提升 DevOps 效率疯特,另一方面是為了提高安全性键袱。希望對(duì)各位有所幫助格了。
通用 Docker 容器鏡像最佳實(shí)踐
這里也再次羅列一下對(duì) Python Docker 鏡像也適用的一些通用最佳實(shí)踐。
- 使用
LABEL maintainer
- 標(biāo)記重要端口
- 設(shè)置環(huán)境變量
- 使用非 root 用戶運(yùn)行容器進(jìn)程
- 使用
.dockerignore
排除無關(guān)文件
Python 鏡像推薦設(shè)置的環(huán)境變量
Python 中推薦的常見環(huán)境變量如下:
# 設(shè)置環(huán)境變量
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
-
ENV PYTHONDONTWRITEBYTECODE 1
: 建議構(gòu)建 Docker 鏡像時(shí)一直為1
, 防止 python 將 pyc 文件寫入硬盤 -
ENV PYTHONUNBUFFERED 1
: 建議構(gòu)建 Docker 鏡像時(shí)一直為1
, 防止 python 緩沖 (buffering) stdout 和 stderr, 以便更容易地進(jìn)行容器日志記錄 - ?不再建議使用
ENV DEBUG 0
環(huán)境變量碗淌,沒必要馆截。
使用非 root 用戶運(yùn)行容器進(jìn)程
出于安全考慮,推薦運(yùn)行 Python 程序前硼砰,創(chuàng)建 非 root 用戶并切換到該用戶且蓬。
# 創(chuàng)建一個(gè)具有明確 UID 的非 root 用戶,并增加訪問 /app 文件夾的權(quán)限题翰。
RUN adduser -u 5678 --disabled-password --gecos "" appuser && chown -R appuser /app
USER appuser
使用 .dockerignore
排除無關(guān)文件
需要排除的無關(guān)文件一般如下:
**/__pycache__
**/*venv
**/.classpath
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/bin
**/charts
**/docker-compose*
**/compose*
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
*.db
.python-version
LICENSE
README.md
這里選擇幾個(gè)說明下:
-
**/__pycache__
: python 緩存目錄 -
**/*venv
: Python 虛擬環(huán)境目錄恶阴。很多 Python 開發(fā)習(xí)慣將虛擬環(huán)境目錄創(chuàng)建在項(xiàng)目下诈胜,一般命名為:.venv
或venv
-
**/.env
: Python 環(huán)境變量文件 -
**/.git
**/.gitignore
: git 相關(guān)目錄和文件 -
**/.vscode
: 編輯器、IDE 相關(guān)目錄 -
**/charts
: Helm Chart 相關(guān)文件 -
**/docker-compose*
: docker compose 相關(guān)文件 -
*.db
: 如果使用 sqllite 的相關(guān)數(shù)據(jù)庫文件 -
.python-version
: pyenv 的 .python-version 文件
不建議使用 Alpine 作為 Python 的基礎(chǔ)鏡像
為什么呢存淫?大多數(shù) Linux 發(fā)行版使用 GNU 版本(glibc)的標(biāo)準(zhǔn) C 庫耘斩,幾乎每個(gè) C 程序都需要這個(gè)庫沼填,包括 Python桅咆。但是 Alpine Linux 使用 musl, Alpine 禁用了 Linux wheel 支持。
理由如下:
- 缺少大量依賴
- CPython 語言運(yùn)行時(shí)的相關(guān)依賴
- openssl 相關(guān)依賴
- libffi 相關(guān)依賴
- gcc 相關(guān)依賴
- 數(shù)據(jù)庫驅(qū)動(dòng)相關(guān)依賴
- pip 相關(guān)依賴
- 構(gòu)建可能更耗時(shí)
- Alpine Linux 使用 musl坞笙,一些二進(jìn)制 wheel 是針對(duì) glibc 編譯的岩饼,但是 Alpine 禁用了 Linux wheel 支持。現(xiàn)在大多數(shù) Python 包都包括 PyPI 上的二進(jìn)制 wheel薛夜,大大加快了安裝時(shí)間籍茧。但是如果你使用 Alpine Linux,你可能需要編譯你使用的每個(gè) Python 包中的所有 C 代碼梯澜。
- 基于 Alpine 構(gòu)建的 Python 鏡像反而可能更大
- 乍一聽似乎違反常識(shí)寞冯,但是仔細(xì)一想,因?yàn)樯厦媪_列的原因晚伙,確實(shí)會(huì)導(dǎo)致鏡像更大的情況吮龄。
???Reference:
Using Alpine can make Python Docker builds 50× slower (pythonspeed.com)
這里以這個(gè) Demo FastAPI Python 程序 為例,其基于 Alpine 的 Dockerfile 地址是這個(gè):https://github.com/east4ming/fastapi-url-shortener/blob/main/Dockerfile.alpine
因?yàn)槿鄙俸芏嘁蕾嚺亓疲栽谟?pip 安裝之前漓帚,就需要盡可能全地安裝相關(guān)依賴:
RUN set -eux \
&& apk add --no-cache --virtual .build-deps build-base \
openssl-dev libffi-dev gcc musl-dev python3-dev \
&& pip install --upgrade pip setuptools wheel \
&& pip install --upgrade -r /app/requirements.txt \
&& rm -rf /root/.cache/pip
這里也展示一下基于 Alpine 構(gòu)建完成后的 鏡像未壓縮大小:
△ 基于 Alpine 的 Python Demo 鏡像大谐⒍丁:472 MB; 相比之下,基于 slim 的只有 189 MB
在上面代碼的這一步迅皇,就占用了太多空間:
??思考:
可能上面一段可以精簡(jiǎn)昧辽,但是要判斷對(duì)于哪個(gè) Python 項(xiàng)目,可以精簡(jiǎn)哪些包登颓,實(shí)在是太難了搅荞。
+ apk add --no-cache --virtual .build-deps build-base openssl-dev libffi-dev gcc musl-dev python3-dev
fetch https://dl-cdn.alpinelinux.org/alpine/v3.17/main/x86_64/APKINDEX.tar.gz
fetch https://dl-cdn.alpinelinux.org/alpine/v3.17/community/x86_64/APKINDEX.tar.gz
(1/28) Installing libgcc (12.2.1_git20220924-r4)
(2/28) Installing libstdc++ (12.2.1_git20220924-r4)
(3/28) Installing binutils (2.39-r2)
(4/28) Installing libmagic (5.43-r0)
(5/28) Installing file (5.43-r0)
(6/28) Installing libgomp (12.2.1_git20220924-r4)
(7/28) Installing libatomic (12.2.1_git20220924-r4)
(8/28) Installing gmp (6.2.1-r2)
(9/28) Installing isl25 (0.25-r0)
(10/28) Installing mpfr4 (4.1.0-r0)
(11/28) Installing mpc1 (1.2.1-r1)
(12/28) Installing gcc (12.2.1_git20220924-r4)
(13/28) Installing libstdc++-dev (12.2.1_git20220924-r4)
(14/28) Installing musl-dev (1.2.3-r4)
(15/28) Installing libc-dev (0.7.2-r3)
(16/28) Installing g++ (12.2.1_git20220924-r4)
(17/28) Installing make (4.3-r1)
(18/28) Installing fortify-headers (1.1-r1)
(19/28) Installing patch (2.7.6-r8)
(20/28) Installing build-base (0.5-r3)
(21/28) Installing pkgconf (1.9.3-r0)
(22/28) Installing openssl-dev (3.0.7-r0)
(23/28) Installing linux-headers (5.19.5-r0)
(24/28) Installing libffi-dev (3.4.4-r0)
(25/28) Installing mpdecimal (2.5.1-r1)
(26/28) Installing python3 (3.10.9-r1)
(27/28) Installing python3-dev (3.10.9-r1)
(28/28) Installing .build-deps (20221214.074929)
Executing busybox-1.35.0-r29.trigger
OK: 358 MiB in 65 packages
...
建議使用官方的 python slim 鏡像作為基礎(chǔ)鏡像
繼續(xù)上面,所以我是建議:使用官方的 python slim 鏡像作為基礎(chǔ)鏡像
鏡像庫是這個(gè):https://hub.docker.com/_/python
并且使用 python:<version>-slim
作為基礎(chǔ)鏡像挺据,能用 python:<version>-slim-bullseye
作為基礎(chǔ)鏡像更好(因?yàn)楦氯【撸鄬?duì)就更安全一些).
這個(gè)鏡像不包含默認(rèn)標(biāo)簽中的常用包,只包含運(yùn)行 python 所需的最小包扁耐。這個(gè)鏡像是基于 Debian 的暇检。
使用官方 python slim 的理由還包括:
- 穩(wěn)定性
- 安全升級(jí)更及時(shí)
- 依賴更新更及時(shí)
- 依賴更全
- Python 版本升級(jí)更及時(shí)
- 鏡像更小
???Reference:
The best Docker base image for your Python application (Sep 2022) (pythonspeed.com)
一般情況下,Python 鏡像構(gòu)建不需要使用"多階段構(gòu)建"
一般情況下婉称,Python 鏡像構(gòu)建不需要使用"多階段構(gòu)建".
理由如下:
- Python 沒有像 Golang 一樣块仆,可以把所有依賴打成一個(gè)單一的二進(jìn)制包
- Python 也沒有像 Java 一樣构蹬,可以在 JDK 上構(gòu)建,在 JRE 上運(yùn)行
- Python 復(fù)雜而散落的依賴關(guān)系悔据,在"多階段構(gòu)建"時(shí)會(huì)增加復(fù)雜度
- ...
如果有一些特殊情況庄敛,可以嘗試使用"多階段構(gòu)建"壓縮鏡像體積:
- 構(gòu)建階段需要安裝編譯器
- Python 項(xiàng)目復(fù)雜,用到了其他語言代碼(如 C/C++/Rust)
pip 小技巧
使用 pip 安裝依賴時(shí)科汗,可以添加 --no-cache-dir
減少鏡像體積:
# 安裝 pip 依賴
COPY requirements.txt .
RUN python -m pip install --no-cache-dir --upgrade -r requirements.txt
Python Dockerfile 最佳實(shí)踐樣例
最后, 就是基于以上最佳實(shí)踐的完整樣例, 也可以在這里找到: https://github.com/east4ming/fastapi-url-shortener/blob/main/Dockerfile.slim
FROM python:3.10-slim
LABEL maintainer="cuikaidong@foxmail.com"
EXPOSE 8000
# Keeps Python from generating .pyc files in the container
ENV PYTHONDONTWRITEBYTECODE=1
# Turns off buffering for easier container logging
ENV PYTHONUNBUFFERED=1
# Install pip requirements
COPY requirements.txt .
RUN python -m pip install --no-cache-dir --upgrade -r requirements.txt
WORKDIR /app
COPY . /app
# Creates a non-root user with an explicit UID and adds permission to access the /app folder
RUN adduser -u 5678 --disabled-password --gecos "" appuser && chown -R appuser /app
USER appuser
CMD ["uvicorn", "shortener_app.main:app", "--host", "0.0.0.0"]
總結(jié)
制作 Python Docker 容器鏡像的最佳實(shí)踐藻烤。最佳實(shí)踐的目的一方面是為了減小鏡像體積,提升 DevOps 效率头滔,另一方面是為了提高安全性.
最佳實(shí)踐如下:
- 推薦 2 個(gè) Python 的環(huán)境變量
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
- 使用非 root 用戶運(yùn)行容器進(jìn)程
- 使用
.dockerignore
排除無關(guān)文件 - 不建議使用 Alpine 作為 Python 的基礎(chǔ)鏡像
- 建議使用官方的 python slim 鏡像作為基礎(chǔ)鏡像
- 一般情況下, Python 鏡像構(gòu)建不需要使用"多階段構(gòu)建"
- pip 小技巧:
--no-cache-dir
希望對(duì)大家有所幫助.
最后也感嘆一下, 在云原生時(shí)代, python 在分發(fā)這塊, 特別是鏡像構(gòu)建這塊, 確實(shí)體驗(yàn)怖亭、效率、鏡像大小等方面差 golang 太多了坤检。??????
???參考文檔
- Using Alpine can make Python Docker builds 50× slower (pythonspeed.com)
- The best Docker base image for your Python application (Sep 2022) (pythonspeed.com)
- Multi-stage builds #2: Python specifics (pythonspeed.com)
- 制作容器鏡像的最佳實(shí)踐 - 東風(fēng)微鳴技術(shù)博客 (ewhisper.cn)
三人行, 必有我?guī)? 知識(shí)共享, 天下為公. 本文由東風(fēng)微鳴技術(shù)博客 EWhisper.cn 編寫.