前言
在使用 Python 的早些年,為了解決 Python 包的隔離與管理 virtualenvwrapper 就成為我的工具箱中重要的一員。后來,隨著 Python 3 的普及,virtualenvwrapper 逐漸被 venv 所替換。畢竟 venv 是 Python 3 的標配,優點是顯而易見的。而這幾年,應用場景的的復雜性越來與高,無論是開發還是部署都需要設置復雜的環境。例如使用 redis 實現消息隊列,用 Psycopg 完成對于 PostgreSQL 數據庫的存取等等。隨之而來 Docker 就變成了程序員必不可少的常備工具。為了掌握如何將我的 Python 應用與 Docker 結合起來,就要學習他人的經驗分享。于是一次又一次地看到了下面這樣的 Dockerfile 例子:
相比起來,我不熟悉 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++這些與編譯工具和幾個庫。事實上,在我第一次構建的時候遇到了這樣的錯誤信息 :
這真是未曾預料的問題啊!深究之下終于發現在 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 版本目前還不是穩定版本。如果用于生產環境,我們還是不要自尋煩惱為好。
結論
這個測試也許不能覆蓋我們需要的各個角度,但至少給我們提供了一個選擇的參考。而我在這個測試之后也得到了自己需要的結果,
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 版本會在大規模應用的時候帶來不可預估的風險,不可掉以輕心。
審核編輯:湯梓紅
評論
查看更多