雖然 Dockerfile 簡化了鏡像構(gòu)建的過程氛赐,并且把這個(gè)過程可以進(jìn)行版本控制漠吻,但是很多人構(gòu)建鏡像的時(shí)候擅耽,都有一種沖動——把可能用到的東西都打包到鏡像中傻盟。這種不正當(dāng)?shù)? Dockerfile 使用也會導(dǎo)致很多問題:
1.? docker 鏡像太大速蕊。如果你經(jīng)常使用鏡像或者構(gòu)建鏡像,一定會遇到那種很大的鏡像娘赴,甚至有些能達(dá)到 2G 以上
2.? docker 鏡像的構(gòu)建時(shí)間過長规哲。每個(gè) build 都會耗費(fèi)很長時(shí)間,對于需要經(jīng)常構(gòu)建鏡像(比如單元測試)的地方這可能是個(gè)大問題
3.? 重復(fù)勞動诽表。多次鏡像構(gòu)建之間大部分內(nèi)容都是完全一樣而且重復(fù)的唉锌,但是每次都要做一遍,浪費(fèi)時(shí)間和資源
希望讀者能夠?qū)?docker 鏡像有一定的了解竿奏,閱讀這篇文章至少需要一下前提知識:
1.? 了解 docker 的基礎(chǔ)概念袄简,運(yùn)行過容器
2.? 熟悉 docker 鏡像的基礎(chǔ)知識,知道鏡像的分層結(jié)構(gòu)
3.? 最好是負(fù)責(zé)過某個(gè) docker 鏡像的構(gòu)建(使用 docker build 命令創(chuàng)建過自己的鏡像)
Dockerfile 和鏡像構(gòu)建
Dockerfile 是由一個(gè)個(gè)指令組成的泛啸,每個(gè)指令都對應(yīng)著最終鏡像的一層绿语。每行的第一個(gè)單詞就是命令,后面所有的字符串是這個(gè)命令的參數(shù)候址,關(guān)于? Dockerfile 支持的命令以及它們的用法吕粹,可以參考官方文檔,這里不再贅述岗仑。
?當(dāng)運(yùn)行 docker build 命令的時(shí)候匹耕,整個(gè)的構(gòu)建過程是這樣的:
1.? 讀取 Dockerfile 文件發(fā)送到 docker daemon
2.? 讀取當(dāng)前目錄的所有文件(context),發(fā)送到 docker daemon
3.? 對 Dockerfile 進(jìn)行解析荠雕,處理成命令加上對應(yīng)參數(shù)的結(jié)構(gòu)
4.? 按照順序循環(huán)遍歷所有的命令稳其,對每個(gè)命令調(diào)用對應(yīng)的處理函數(shù)進(jìn)行處理
5.? 每個(gè)命令(除了 FROM)都會在一個(gè)容器執(zhí)行,執(zhí)行的結(jié)果會生成一個(gè)新的鏡像
6.? 為最后生成的鏡像打上標(biāo)簽
編寫 Dockerfile 的一些優(yōu)秀實(shí)踐
1. 使用統(tǒng)一的 base 鏡像
有些文章講優(yōu)化鏡像會提倡使用盡量小的基礎(chǔ)鏡像舞虱,比如 busybox 或者 alpine 等欢际。我更推薦使用統(tǒng)一的大家比較熟悉的基礎(chǔ)鏡像,比如? ubuntu矾兜,centos? 等损趋,因?yàn)榛A(chǔ)鏡像只需要下載一次可以共享,并不會造成太多的存儲空間浪費(fèi)椅寺。它的好處是這些鏡像的生態(tài)比較完整浑槽,方便我們安裝軟件,除了問題進(jìn)行調(diào)試返帕。
2. 動靜分離
經(jīng)常變化的內(nèi)容和基本不會變化的內(nèi)容要分開桐玻,把不怎么變化的內(nèi)容放在下層,創(chuàng)建出來不同基礎(chǔ)鏡像供上層使用荆萤。比如可以創(chuàng)建各種語言的基礎(chǔ)鏡像镊靴,python2.7铣卡、python3.4、go1.7偏竟、java7等等煮落,這些鏡像包含了最基本的語言庫,每個(gè)組可以在上面繼續(xù)構(gòu)建應(yīng)用級別的鏡像踊谋。
3. 最小原則:只安裝必需的東西
很多人構(gòu)建鏡像的時(shí)候蝉仇,都有一種沖動——把可能用到的東西都打包到鏡像中。要遏制這種想法殖蚕,鏡像中應(yīng)該只包含必需的東西轿衔,任何可以有也可以沒有的東西都不要放到里面。因?yàn)殓R像的擴(kuò)展很容易睦疫,而且運(yùn)行容器的時(shí)候也很方便地對其進(jìn)行修改害驹。這樣可以保證鏡像盡可能小,構(gòu)建的時(shí)候盡可能快笼痛,也保證未來的更快傳輸裙秋、更省網(wǎng)絡(luò)資源。
4. 一個(gè)原則:每個(gè)鏡像只有一個(gè)功能
不要在容器里運(yùn)行多個(gè)不同功能的進(jìn)程缨伊,每個(gè)鏡像中只安裝一個(gè)應(yīng)用的軟件包和文件摘刑,需要交互的程序通過 pod(kubernetes 提供的特性)? 或者容器之間的網(wǎng)絡(luò)進(jìn)行交流。這樣可以保證模塊化刻坊,不同的應(yīng)用可以分開維護(hù)和升級枷恕,也能減小單個(gè)鏡像的大小。
5. 使用更少的層
雖然看起來把不同的命令盡量分開來谭胚,寫在多個(gè)命令中容易閱讀和理解徐块。但是這樣會導(dǎo)致出現(xiàn)太多的鏡像層,而不好管理和分析鏡像灾而,而且鏡像的層是有限的胡控。盡量把相關(guān)的內(nèi)容放到同一個(gè)層,使用換行符進(jìn)行分割旁趟,這樣可以進(jìn)一步減小鏡像大小昼激,并且方便查看鏡像歷史。
RUN apt-get update\ && apt-get install -y --no-install-recommends \? bzr \ cvs \ git \ mercurial \ subversion \ && apt get clean
6. 減少每層的內(nèi)容
盡管只安裝必須的內(nèi)容锡搜,在這個(gè)過程中也可能會產(chǎn)生額外的內(nèi)容或者臨時(shí)文件橙困,我們要盡量讓每層安裝的東西保持最小。
比如使用 --no-install-recommends 參數(shù)告訴 apt-get 不要安裝推薦的軟件包
安裝完軟件包耕餐,清楚 /var/lib/apt/list/ 緩存
刪除中間文件:比如下載的壓縮包
刪除臨時(shí)文件:如果命令產(chǎn)生了臨時(shí)文件凡傅,也要及時(shí)刪除
7. 不要在 Dockerfile 中單獨(dú)修改文件的權(quán)限
因?yàn)?docker? 鏡像是分層的,任何修改都會新增一個(gè)層肠缔,修改文件或者目錄權(quán)限也是如此夏跷。如果有一個(gè)命令單獨(dú)修改大文件或者目錄的權(quán)限哼转,會把這些文件復(fù)制一份,這樣很容易導(dǎo)致鏡像很大拓春。
解決方案也很簡單释簿,要么在添加到 Dockerfile? 之前就把文件的權(quán)限和用戶設(shè)置好,要么在容器啟動腳本(entrypoint)做這些修改硼莽,或者拷貝文件和修改權(quán)限放在一起做(這樣最終也只是增加一層)。
8. 利用 cache 來加快構(gòu)建速度
如果 Docker 發(fā)現(xiàn)某個(gè)層已經(jīng)存在了煮纵,它會直接使用已經(jīng)存在的層懂鸵,而不會重新運(yùn)行一次。如果你連續(xù)運(yùn)行 docker build? 多次行疏,會發(fā)現(xiàn)第二次運(yùn)行很快就結(jié)束了匆光。
不過從 1.10 版本開始,Content Addressable Storage 的引入導(dǎo)致緩存功能的實(shí)效酿联,目前引入了 --cache-from? 參數(shù)可以手動指定一個(gè)鏡像來使用它的緩存终息。
9. 版本控制和自動構(gòu)建
最好把 Dockerfile? 和對應(yīng)的應(yīng)用代碼一起放到版本控制中,然后能夠自動構(gòu)建鏡像贞让。這樣的好處是可以追蹤各個(gè)版本鏡像的內(nèi)容周崭,方便了解不同鏡像有什么區(qū)別,對于調(diào)試和回滾都有好處喳张。
另外续镇,如果運(yùn)行鏡像的參數(shù)或者環(huán)境變量很多,也要有對應(yīng)的文檔給予說明销部,并且文檔要隨著 Dockerfile? 變化而更新摸航,這樣任何人都能參考著文檔很容易地使用鏡像,而不是下載了鏡像不知道怎么用舅桩。
每天都會有更新看過的朋友可以點(diǎn)波關(guān)注酱虎,Java學(xué)習(xí)路線和優(yōu)質(zhì)資源評論或點(diǎn)擊“Java”獲取