利用分層機(jī)制優(yōu)化Docker Image
1.Docker Image 分層存儲(chǔ)
為了最大化重用 Image,加快運(yùn)行速度,減少內(nèi)存和磁盤(pán)的占用,Docker container 運(yùn)行時(shí)所構(gòu)造的運(yùn)行環(huán)境,實(shí)際上是由具有依賴關(guān)系的多個(gè) Layer 組成的。如圖 1 所示,每一串?dāng)?shù)字 ID 就代表了一個(gè) Docker Image Layer。當(dāng)我們?cè)?pull 一個(gè) Docker Image 的時(shí)候我們會(huì)發(fā)現(xiàn)所有依賴的 Layer 文件將會(huì)被 download。
圖 1. Docker Image 分層示意圖
例如我們一個(gè) Docker App Image 的運(yùn)行環(huán)境是在基礎(chǔ)的 Docker Base Image 的基礎(chǔ)上,疊加了包含例如 anaconda等各種工具的 Image,再疊加包含模型文檔及其相關(guān)依賴庫(kù)的 Image,以及包含了最終應(yīng)用的code包的 layer。這些 Image 由 AUFS 文件系統(tǒng)加載合并到統(tǒng)一路徑中,以只讀的方式存在,最后再疊加加載一層可寫(xiě)的空白的 Layer 用作記錄對(duì)當(dāng)前運(yùn)行環(huán)境所作的修改。因此,當(dāng) Docker Image 每次由一個(gè)基礎(chǔ) Image 創(chuàng)建后,新 Image 就自動(dòng)增加了一層。如圖 2 所示:
圖 2. Docker Image Layer 的疊加
2 Docker Image 衍生單一 Base Image
隨著項(xiàng)目基于 Docker 的使用逐漸增加,Docker Image 的數(shù)量也將逐漸增加。隨之而來(lái)的問(wèn)題就是如何維護(hù)這些 Docker Image 的升級(jí)。如果缺乏規(guī)劃和設(shè)計(jì),每個(gè) Docker Image 均來(lái)自一個(gè)最基礎(chǔ)的 OS Image,那么就需要對(duì)于所有的 Docker Image 進(jìn)行重構(gòu)。如圖 3所示:
圖 3. Docker Image 衍生單一 Base Image
當(dāng)環(huán)境進(jìn)行更新升級(jí)的時(shí)候,如果所有的節(jié)點(diǎn)均來(lái)自一個(gè)基礎(chǔ)的 OS Image,重復(fù)的 layer 層將會(huì)被重復(fù)更新。也就意味著,這部分重復(fù)的內(nèi)容會(huì)被反復(fù)的下載。如果一個(gè) Docker Image 達(dá)到了 1G 以上的規(guī)模,而每個(gè) Docker Host 節(jié)點(diǎn)的更新都需要重新下載新的 Image. 這樣環(huán)境更新所花費(fèi)的時(shí)間將會(huì)是成倍的增加。如圖 4 所示,Docker Image 2 和 Docker Image3 均是基于 Docker Image 1。
圖 4. 基于同樣 Base Image 的 Docker Image Layer 的疊加
圖 5. Docker Image Layer 在 Docker Host 上的存儲(chǔ)關(guān)系
從圖 5 可以看出在同一個(gè) Docker host 上 download 來(lái)自同樣 Base Image 的 Docker Image, Docker 在下載 Image layer 的時(shí)候,對(duì)于已經(jīng)存在的 layer 是不會(huì)重復(fù)下載的。但是如果 layer 不同,即使內(nèi)部包含的內(nèi)容一樣,也還是會(huì)重復(fù)下載的。
3.利用分層機(jī)制優(yōu)化 Docker Image
通過(guò)上兩節(jié)的介紹,可以發(fā)現(xiàn)缺乏良好設(shè)計(jì)的 Docker Image 會(huì)給日后的維護(hù)以及我們后續(xù)CICD的效率帶來(lái)較大的問(wèn)題。接下來(lái)就介紹下如何利用分層機(jī)制對(duì)項(xiàng)目的 Docker Image 進(jìn)行合理的規(guī)劃。從而提升 Docker 在CICD過(guò)程中的可持續(xù)性,并提升CICD的效率。
3.1 設(shè)計(jì)基于分層機(jī)制的 Docker Image
假設(shè)系統(tǒng)中我們有兩個(gè)應(yīng)用 App1 和 App2。這兩個(gè)節(jié)點(diǎn)的環(huán)境信息如下:
分類 | APP1 | APP2 |
基礎(chǔ)環(huán)境鏡像 (os) | Python3.7 | Python3.7 |
安全組件(Security tools) | some-security-framework | some-security-framework |
通用工具(General tools) | make/gcc/path/wget/sudo/tar | make/gcc/path/wget/sudo/tar |
依賴庫(kù)(Library) | pip install -y some-dependences | pip install -y some-dependences |
模型組件(Model) | some-path/dust.model | some-path/dust.model |
代碼(Code) | code.1 | code.2 |
配置(Config) | app1.conf | app2.conf |
通過(guò)上表環(huán)境信息的對(duì)比,我們發(fā)現(xiàn)在這兩個(gè)不同引用的節(jié)點(diǎn)上,不同的部分只是 最后的代碼code 的和config 文件。對(duì)于其他相同的部分,我們可以考慮通過(guò) Docker Image Layer 的概念將其復(fù)用。從而最大限度發(fā)揮 Docker 的能力。將上表中的兩部分環(huán)境信息以分類為節(jié)點(diǎn)名,重新以樹(shù)狀結(jié)構(gòu)組織如圖 6 所示:
圖6.環(huán)境配置樹(shù)狀圖 1
建議將一些不會(huì)經(jīng)常發(fā)生變化的命令或者同類型的命令,合并到同一層。如圖 7 所示:
圖7.環(huán)境配置樹(shù)狀圖 2
最后將圖中的兩個(gè)樹(shù)狀結(jié)構(gòu)圖進(jìn)行疊加將重復(fù)的節(jié)點(diǎn)進(jìn)行合并,最后得出如下樹(shù)狀結(jié)構(gòu)圖:
圖 8. 環(huán)境配置樹(shù)狀圖 3
現(xiàn)在我們已經(jīng)基于 Docker Image 的分層存儲(chǔ)機(jī)制完成了一個(gè)初步的Docker Image 的規(guī)劃。接下來(lái)就可以根據(jù)上圖結(jié)構(gòu)分別制作 Image。最終我們將會(huì)有三個(gè) Base Image,和最終加入代碼的業(yè)務(wù)鏡像。同時(shí)基于此,我們的Dockerfile也類比如下:這里本該是 4個(gè) gitlab 倉(cāng)庫(kù)制作的 4個(gè)鏡像。為了方便展示鏡像復(fù)用關(guān)系, 用一個(gè)代碼塊展示:
# f1: 運(yùn)維安全團(tuán)隊(duì)增加優(yōu)化基礎(chǔ)安全組件
FROM python3
RUN apt install -y some-security-framework
# push: abc.hub.com/libary/python3
# f2: 架構(gòu)師安裝基礎(chǔ)架構(gòu)
FROM abc.hub.com/libary/python3
RUN wget -c anaconda12.sh && ./anaconda12.sh && rm -f anaconda12.sh
# push: abc.hub.com/ai-tools/env-anaconda:12
# f3: 制作模型鏡像
FROM abc.hub.com/ai-tools/env-anaconda:12
RUN pip install -y some-dependences
RUN wget -c s3.xx.com/some-path/dust.model -O /some/path
# push: abc.hub.com/ai-tools/env-anaconda-dust:runtime
# f4: 制作業(yè)務(wù)鏡像
FROM abc.hub.com/rk-ai-tools/env-anaconda-dust:runtime
ADD code /workspace/code
ENTRYPOINT [ "/bin/bash", "/entrypoint.sh" ]
# push: abc.hub.com/rk-ai-pollution/srv-some-appname-amd64:1.0.0-1234567
3.2 基于分層機(jī)制的 Docker Image 的實(shí)踐
如圖 10 所示, 按照之前介紹的安裝 Security tools/General tools/Library 的Docker Image 大小在 1.8 G 左右。以此為基礎(chǔ)創(chuàng)建的的 App Image 的大小在 1.9G 左右。
圖 10. Docker Image 分層存儲(chǔ)實(shí)驗(yàn) 1
在一個(gè)已經(jīng) download 了 Liberty Docker Image 的環(huán)境下下載 App Image。如圖 11 所示,可以看到已經(jīng)存在的 layer 已經(jīng)是 complete 狀態(tài)。唯一 download 的部分只有新增加的 EAR 所產(chǎn)生的新的 layer。所需時(shí)間僅僅為 1 分 33 秒。
圖 11. Docker Image 分層存儲(chǔ)實(shí)驗(yàn) 2
如果直接在一個(gè)不存在 Liberty Docker Image 的 server 上去 download App Docker Image, 如圖 12 所示,我們可以看到所需要的時(shí)間將超過(guò) 7 分鐘。
圖 12. Docker Image 分層存儲(chǔ)實(shí)驗(yàn) 3
通過(guò)圖 13 可以發(fā)現(xiàn)其他 layer 的 download 時(shí)間要超過(guò) 4 分鐘,如果反復(fù)對(duì)這些重復(fù)的 Docker Image layer 進(jìn)行下載更新,將會(huì)嚴(yán)重影響環(huán)境更新的效率。隨著不同 Image 之間在 Docker Image Layer 上的差異越大,所花費(fèi)的下載 Docker Image 的代價(jià)也將越大。
圖 13. Docker Image 分層存儲(chǔ)實(shí)驗(yàn) 4
4.小結(jié)
通過(guò)上文的描述和實(shí)際測(cè)試可知,如果我們能把鏡像做一個(gè)合理的分層,不但能縮短拉取鏡像的時(shí)間,提高CICD的效率,更能劃分不同團(tuán)隊(duì)不同人員的角色,每個(gè)人只專注自己職責(zé)相關(guān)的鏡像,然后不同團(tuán)隊(duì)或同團(tuán)隊(duì)其他人員可以在其基礎(chǔ)上,再構(gòu)建自己的鏡像,層層遞進(jìn),最終制作一個(gè)業(yè)務(wù)發(fā)布的鏡像。