前言
在使用 Python 的早些年,為了解決 Python 包的隔離與管理 virtualenvwrapper 就成為我的工具箱中重要的一員。后來,隨著 Python 3 的普及,virtualenvwrapper 逐漸被 venv 所替換。畢竟 venv 是 Python 3 的標(biāo)配,優(yōu)點(diǎn)是顯而易見的。而這幾年,應(yīng)用場景的的復(fù)雜性越來與高,無論是開發(fā)還是部署都需要設(shè)置復(fù)雜的環(huán)境。例如使用 redis 實(shí)現(xiàn)消息隊(duì)列,用 Psycopg 完成對于 PostgreSQL 數(shù)據(jù)庫的存取等等。隨之而來 Docker 就變成了程序員必不可少的常備工具。為了掌握如何將我的 Python 應(yīng)用與 Docker 結(jié)合起來,就要學(xué)習(xí)他人的經(jīng)驗(yàn)分享。于是一次又一次地看到了下面這樣的 Dockerfile 例子:
相比起來,我不熟悉 Alpine 這個 Linux 發(fā)行版本。我很困惑這個版本難道比其它哪些老字號的 Linux 發(fā)行版更適合 Docker 的環(huán)境嗎?至于我的 Python 應(yīng)用,究竟選擇哪一個 Docker 基礎(chǔ)鏡像更好呢?
對于 Docker 基礎(chǔ)鏡像的要求
為我的 Python 應(yīng)用構(gòu)建一個 Docker 鏡像并不是要從零開始,而是從現(xiàn)有的 Linux 基礎(chǔ)鏡像開始構(gòu)建。這些基礎(chǔ)鏡像除了提到過的 Alpine 以外 還有我更熟悉的 Ubuntu、Centos 、Debian 等等。在決定選擇哪一個之前,我們需要回答的一個問題就是:
我們究竟對于這個 Docker 基礎(chǔ)鏡像有哪些要求?
這些要求既要滿足通常意義上我們對操作系統(tǒng)的要求,也要滿足對于 Python 應(yīng)用的特殊的需求。
歸納起來,我們的需求應(yīng)當(dāng)包括這幾點(diǎn):
穩(wěn)定性:要求今天的構(gòu)建與以后的構(gòu)建有相同的基本庫、目錄結(jié)構(gòu)和基礎(chǔ)結(jié)構(gòu)。否則我們的 Python 應(yīng)用程序?qū)⒂锌赡艽嬖诓淮_定的隱患。
安全更新:需要基礎(chǔ)鏡像得到良好維護(hù),以便及時獲取基本操作系統(tǒng)的安全更新
最新的依賴關(guān)系:除非我們的應(yīng)用僅僅是一個簡單的 Python 程序,否則就不得不依賴操作系統(tǒng)所提供 的庫和應(yīng)用程序(例如:GCC 編譯器、OpenSSL 庫等)。我們需要這些應(yīng)用和庫要足夠新,否則就會有各種安全性的問題或者功能性的不足。
豐富的庫資源:對于某些應(yīng)用,可能需要安裝一些不太流行的庫(例如 lxml 等)。這就需要我們選擇的基本鏡像提供豐富的庫資源。
最新的 Python 版本:雖然可以通過自行安裝 Python 來解決,但是擁有最新的 Python 的版本無疑可以節(jié)省我們的時間、精力。
小型的 Docker 鏡像:在所有條件都相同的情況下,擁有尺寸較小的 Docker 鏡像無疑更勝一籌。無論是鏡像的構(gòu)建還是占用存儲空間的開銷,更小的尺寸一定更有優(yōu)勢。
考慮到應(yīng)用部署在產(chǎn)環(huán)境的需要,我們所選擇的 Docker 鏡像還應(yīng)當(dāng)具備長期支持(Long-term support, LTS) 的承諾。
長期支持 (英語:Long-term support,縮寫:LTS)是一種軟件的產(chǎn)品生命周期政策,特別是開源軟件,它增加了軟件開發(fā)過程及軟件版本周期的可靠度。長期支持延長了軟件維護(hù)的周期;它也改變了軟件更新(補(bǔ)?。┑念愋图邦l率以降低風(fēng)險、費(fèi)用及軟件部署的中斷時間,同時提升了軟件的可靠性。但這并不必然包含技術(shù)支持。
– 維基百科
Linux 鏡像版本的選擇
圍繞著上述的需求,我們很容易就會找到一批候選的版本。乍看起來,這些基礎(chǔ)鏡像應(yīng)該能夠滿足我們的需要。接下來我們需要仔細(xì)甄別其具體的差異,以找出最適合我們的版本,尤其是適合我們的 Python 應(yīng)用。
選項(xiàng)一:傳統(tǒng)的 Linux 分發(fā)版本 – Ubuntu TLS、CentOS 以及 Debian
這三個 Linux 分發(fā)版本歷史久遠(yuǎn)(Debian 早在 1993 年就已出現(xiàn)),名氣很大,在 Linux 服務(wù)器市場上擁有廣泛的用戶。
Ubuntu 18.04(Docker 鏡像的名字 ubuntu:18.04)發(fā)布于 2018 年 4 月,由于這是 Canonical 公司的長期支持版本(LTS),意味著該版本的用戶在 2023 年之前都將獲得安全更新。
CentOS 8( Docker 鏡像的名字 centos:8 )于 2019 年發(fā)布,將在 2024 年前進(jìn)行全面更新,并在 2029 年前提供維護(hù)更新。
Debian 10(Docker 鏡像的名字 debian:buster)發(fā)布于 2019 年 7 月,承諾支持到 2024 年。
需要注意的是這些鏡像預(yù)安裝的 Python 有可能不是最新的版本。例如 Ubuntu 18.04 預(yù)安裝的是 Python 3.6.7,而 Python 3 的最新穩(wěn)定版本已經(jīng)升級為 Python 3.8.1。因此我們必須在構(gòu)建 Docker 鏡像的時候去完成 Python 的安裝或者升級。在一些特定的 Linux 分發(fā)版本中,我們甚至需要自行通過編譯 Python 源碼的方式來獲得最新版本的 Python。例如在 CentOS 8 中,就需要用這個辦法來安裝 Python3.8。至于具體的辦法,可以參考在“Python 3.8 已經(jīng)來了,你準(zhǔn)備好了嗎?”一文中的介紹。
https://amazonaws-china.com/cn/blogs/china/python-3-8-is-here-are-you-ready/
選項(xiàng)二:Docker 官方的 Python 鏡像
這個 Docker 鏡像由 Docker 官方提供。該版本的最大特點(diǎn)就是預(yù)裝了 Python,并且提供多個不同 Python 版本的選項(xiàng),例如 Python 3.7、Python 3.8、Python 3.9。需要注意的是,這個版本提供了多個不同的變體,如果搞不清楚這一點(diǎn)很容易在使用中遇到難以預(yù)料的問題。這些差異很大的變體包括了:
Alpine Linux, 關(guān)于這個版本,后面會有專門的討論
Debian Buster, 這是基于 Debian 的一個分支版本,是基于 Debian 的標(biāo)準(zhǔn)鏡像的 Layer 來構(gòu)建的。特點(diǎn)是基礎(chǔ)庫很完整,缺點(diǎn)是尺寸較大,磁盤的利用率較低。
Debian Buster slim,這個版本是針對 Debian Buster 的“瘦身”后的版本。尺寸小,磁盤利用率高是其優(yōu)點(diǎn)。但是,它缺少通用的包,可能會導(dǎo)致對部分的應(yīng)用支持不好。
選項(xiàng)三:云計(jì)算上的 Linux 鏡像 – Amazon Linux 2
今天當(dāng)我們談到 Docker 在生產(chǎn)環(huán)境中的部署的時候,不能缺少的一個話題就是云計(jì)算。相比較起來,云計(jì)算上 Linux 以及 Docker 的使用與部署與我們熟悉的傳統(tǒng)方式有一些區(qū)別。例如安全性的要求、與云計(jì)算平臺的集成以及管理工具等。于是云計(jì)算的平臺 Linux 分發(fā)版本以及 Docker 鏡像也就應(yīng)需而生。AWS 就提供了 Amazon Linux 的兩個版本:Amazon Linux 2 和 Amazon Linux。其中,Amazon Linux 2 是 Amazon Linux 的新一代的 Linux 服務(wù)器操作系統(tǒng)版本,這也是 AWS 官方推薦使用的版本。至于選擇 Amazon Linux 2 的的理由,簡單來說這是一個由 Amazon 提供長期支持的(LTS)、進(jìn)行了針對性性能優(yōu)化的、強(qiáng)調(diào)安全的、免費(fèi)的 Linux 分發(fā)版本以及 Docker 的鏡像。
選項(xiàng)四:Alpine 鏡像
很難想象,Alpine 這個 Linux 發(fā)行版已經(jīng)有了 14 年的歷史。但廣為大家所熟知還是要拜 Docker 的流行所賜。最初,Alpine Linux 是 LEAF 項(xiàng)目(Linux Embedded Appliance Framework Project)的一個分支。LEAF 項(xiàng)目的設(shè)計(jì)的目標(biāo)是開發(fā)出一個可以放在單個軟盤上的 Linux 發(fā)行版,而后繼的 Alpine 在此基礎(chǔ)之上附加了一些更常用的軟件包例如 Squid、Samba 等等。因此,Alpine 的典型特征就是“尺寸小”!當(dāng)然為達(dá)成這個目標(biāo)就要做出妥協(xié)。為減少尺寸就不得不放棄我們熟悉的 Linux 環(huán)境中最常見的 GNU C Library(glibc)
https://en.wikipedia.org/wiki/GNU_C_Library
取而代之的是一個迷你版的 C 標(biāo)準(zhǔn)庫 musl
http://musl.libc.org/
此外,我們常用的 Linux 工具(例如 grep、ls、cp 等等)則采用了 BusyBox 以求進(jìn)一步壓縮空間。這種努力的結(jié)果是非常有成效的,alpine:latest 鏡像的尺寸只有區(qū)區(qū)的5.59MB,而與之相對的 ubuntu:18.04 的鏡像的尺寸卻高達(dá) 64.2MB。簡單計(jì)算下來,Alpine 的磁盤空間的需求只是 Ubuntu 18.04 的 8.7% !
對比 – Docker 基礎(chǔ)鏡像的尺寸
想象一下,在真實(shí)的生產(chǎn)環(huán)境中我們部署的 Docker 實(shí)例的數(shù)量可能成百、上千。考慮到數(shù)量的因素,Docker 鏡像的尺寸就應(yīng)當(dāng)是我們考量的一個重要依據(jù)。此外啟動一個 Docker 實(shí)例我們往往需要在盡可能短的時間內(nèi)完成,Docker 鏡像的尺寸無疑也是一個關(guān)鍵因素。那么,我們就將上面列舉的 Docker 鏡像在尺寸方面做一個對比 :
測試日期:2020 年 2 月 28 日
Linux 分發(fā)版本 | 鏡像名稱 | 拉取命令 | 尺寸 |
---|---|---|---|
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 |
好了,在這一項(xiàng)的測試中名次如下 :
python:3.7 > centos:8? >? python:3.7-slim > amazonlinux:latest > debian:buster? > ubuntu:18.04 > alpine:latest
如果從這個排名來看 centos 8 無疑表現(xiàn)的差強(qiáng)人意,故被淘汰。從數(shù)字來看似乎 alpine 是最好的選擇。且慢,我們再來進(jìn)行下一項(xiàng)測試- 構(gòu)建時間。
對比 – Docker 鏡像的構(gòu)建時間
在大多數(shù)的時間里,我們所使用的 Docker 鏡像都需要從基礎(chǔ)鏡像開始構(gòu)建。例如下面的這個 Dockerfile 就用來構(gòu)建一個 Flask 的應(yīng)用
?
#?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
?
這種模式帶來的問題就是我們不得不考慮構(gòu)建帶來的額外的開銷。尤其在一個復(fù)雜的項(xiàng)目中,我們需要構(gòu)建的則不僅僅上面這樣簡單的場景,復(fù)雜的應(yīng)用往往需要一個較長的構(gòu)建時間。如果構(gòu)建時間的開銷比較大或者比較復(fù)雜,則必然增加了額外的管理、部署以及監(jiān)控的成本。我的這個測試場景比較簡單,只是安裝 Python3,以及比較常見的 python 包 numpy、matplotlib 和 pandas??纯疵恳环N Docker 基礎(chǔ)鏡像的構(gòu)建所需的時間是多少。
1、ubuntu:18 , 構(gòu)建時間 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? , 構(gòu)建時間 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? , 構(gòu)建時間 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,構(gòu)建時間 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, 構(gòu)建時間 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? ,構(gòu)建時間 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
?
測試的結(jié)果出來了:
alpine:latest > ubuntu:18.04 > > python:3.7-slim ?> debian:buster? ??> python:last > amazonlinux:latest
確實(shí)沒有看錯,被我們寄予厚望的 Alpine 鏡像的構(gòu)建時間居然是24 分鐘以上。與構(gòu)建速度最快的 Amazon Linux 2 比較起來足足慢了有24 倍的時間!!
如果細(xì)心一些,你會發(fā)現(xiàn)這個 Dockerfile 與上面的幾個不同,多出了 gcc、make、automake、g++這些與編譯工具和幾個庫。事實(shí)上,在我第一次構(gòu)建的時候遇到了這樣的錯誤信息 :
這真是未曾預(yù)料的問題??!深究之下終于發(fā)現(xiàn)在 Appine 使用 pip 安裝 matplotlib 以及 pandas 的時候,并不是從 PyPi 的倉庫中下載 whl 包,而是需要下載源代碼然后編譯再進(jìn)行安裝的。標(biāo)準(zhǔn)的預(yù)編譯的 Python 包居然無法直接安裝,這究竟是為什么?
答案原來出在 Alpine 使用的 musl 庫上。原來幾乎全部 Linux 發(fā)行版都使用 GNU 版本的 C 標(biāo)準(zhǔn)庫(glibc)。但是 Alpine Linux 為了減小存儲空間而使用了 musl 庫。而我們通過 pip 安裝的這些二進(jìn)制 Python 包是基于 glibc 編譯而成的。因此 Alpine 無法安裝這些 python 庫,只能通過源碼編譯的方式來進(jìn)行安裝。這也就是為什么 Alpine 的 Docker file 會與其它的不同,以及花費(fèi)如此之多的時間進(jìn)行構(gòu)建的秘密。
我相信熟悉 Alpine 的用戶會與我爭論,Alpine 的 Edge 版本會解決這個問題。不幸的是,Edge 版本目前還不是穩(wěn)定版本。如果用于生產(chǎn)環(huán)境,我們還是不要自尋煩惱為好。
結(jié)論
這個測試也許不能覆蓋我們需要的各個角度,但至少給我們提供了一個選擇的參考。而我在這個測試之后也得到了自己需要的結(jié)果,
Alpine 顯然不適合作為 Python 應(yīng)用的基礎(chǔ)鏡像。盡管它提供了驚人存儲的空間上效率,但它對于 Python 包支持的不足的缺陷是難以彌補(bǔ)的。也許 Alpine 更適合于一些對于鏡像尺寸敏感的場合,還可以考慮將它用于你的 Go 應(yīng)用。
在 AWS 云計(jì)算的環(huán)境中,Amazon Linux 2 的性能表現(xiàn)是有目共睹的。如果你希望得到一個穩(wěn)定、安全以及高性能的 Python 基礎(chǔ)鏡像,那就不要忘記 Amazon Linux 2 這個選擇。
Ubuntu 18.04 以及 Debian 10 表現(xiàn)的中規(guī)中矩,完全在我的意料之中??紤]到 Debian 10(Buster)較 Ubuntu 更新一些。這應(yīng)該是一個好選擇。不過隨著 Ubuntu 20.04 LTS 即將發(fā)布,在我的候選清單上也許要多出一個。
至于 Docker 官方的 Python 鏡像,并沒有看出明顯的優(yōu)點(diǎn)??紤]到安全性與維護(hù)性的問題,我不認(rèn)為這是個好的選擇。
關(guān)于 Docker 基礎(chǔ)鏡像的選擇,還需要考慮的一點(diǎn)就是 Linux 一致性的問題。雜亂的、多樣化的 Linux 版本會在大規(guī)模應(yīng)用的時候帶來不可預(yù)估的風(fēng)險,不可掉以輕心。
審核編輯:湯梓紅
評論
查看更多