Docker 映象優化與最佳實踐

阿里云云棲社群發表於2017-10-24

摘要:雲棲TechDay41期,阿里雲高階研發工程師御阪帶來Docker映象優化與最佳實踐。從Docker映象儲存的原理開始,針對映象的儲存、網路傳輸,介紹如何在構建中對這些關鍵點進行優化。並介紹Docker最新的多階段構建的功能,以解決構建依賴的中間產物問題。

以下是精彩內容整理:

映象概念

Docker 映象優化與最佳實踐

映象是什麼?從一個比較具體的角度去看,映象就是一個多層儲存的檔案,相較於普通的ISO系統映象來說,分層儲存會帶來兩個優點,一個是分層儲存的映象比較容易擴充套件,比如我們可以基於一個Ubuntu映象去構建我們的Nginx映象,這樣我們只需要在Ubuntu映象的基礎上面做一些Nginx的安裝配置工作,一個Nginx映象工作就算製作完成了,我們不需要從頭開始去製作各種映象。另一點我們可以優化映象儲存空間,假如我們有兩個映象,Tag1.0映象和 Tag2.0映象,我們如果以傳統方式去傳這兩個映象,每個映象大概130多兆,但如果我們以分層的方式去儲存兩個映象,我們通過下面兩個紫色的才能共享,可以節約大量的空間,兩個映象加起來只需要140多兆的空間就可以存下來。這樣一是節省了儲存空間,二是可以減少網路上的開銷,比如我們已經把下面映象下載了,我們要去下載上面映象的時候,我們只需要去下10M的部分。

如果從抽象的角度去看,Docker映象其實是Docker提供的一種標準化的交付手段,傳統應用在交付的時候其實是交付一個可執行檔案,這個可執行檔案不包括它的執行環境,我們可能會因為32位系統或64位系統,或者開發測試使用1.0軟體,結果交付時候發現使用者的環境是2.0等各種各樣的問題,導致我們要去花時間去排查,如果我們以Docker映象的標準化形式去交付,我們就會避免掉這些問題。

映象基本操作與儲存方式

Docker 映象優化與最佳實踐

我們的一個映象會有一個座標,一個映象座標基本上會由四個部分組成,前面會有一個映象服務域名,每一個服務提供商都會有不同的域名,當我們確定服務提供商給我們的域名之後,我們一般會要到服務提供商那裡去申請自己的名稱空間,倉庫名稱一般是標識映象的用途,比如說Ubuntu映象、CentOS映象,標籤一般是用於去區分映象版本,比如我們對Ubuntu映象可能會打一些16.04的包,在我們確定了一個映象服務域名以及在雲服務商申請名稱空間之後,我們就可以對映象做一些操作了。

首先我們需要去登陸,我們會用第一條命令去登陸,然後,當我們在本地準備好一個映象想要上傳的時候,我們先要對這個映象進行打標,把它的座標變成我們現在需要上傳映象的座標,然後再去做一些推送拉取的動作,最後針對Docker還提供兩個額外命令去做映象交付,如果我們是特殊的環境,沒有辦法網路連通的時候,我們可以將這個映象打包成一個普通檔案進行傳輸。比如我們和公安合作,他們沒有辦法通過我們的Registry下載映象,我們可能要把它打成一個普通檔案,然後以U盤的方式去交付。

映象儲存細節

Docker 映象優化與最佳實踐

Docker映象是存在聯合檔案系統的,每一個映象其實是分層儲存的,比如在第一層我們新增了三個新檔案,然後在這一層基礎上我們又增加了一層,新增了一個檔案,第三層可能會需要做一些修改,我們把File3做了一個修改移到上面來,然後刪掉了File4,這裡就會引到聯合檔案系統裡面的寫時複製機制,當我們要去修改一個檔案的時候,映象依賴底層都是隻讀的,我們不能去直接修改,比如我們想去修改File3,我們不能直接去修改這個檔案,我們需要在修改的時候把檔案複製到當前這一層,比如說L3層,然後再去修改它。

一個映象做好之後,當我們想要知道映象裡面有哪一些內容的時候,我們其實會有一個檢視概念,我們從聯合檔案系統的角度去看映象的時候,其實我們不會看到L1、L2、L3,我們會最後看到結果,File1、File2、File3,File4就看不到了,然後在我們瞭解原理之後,我們就可以去理解容器執行起來是一個什麼樣的情況。容器執行起來和上面形成是類似的,圖中下半部分,同樣也是L1、L2、L3的三層映象,當容器執行起來的時候,Docker daemon會動態生成一層可寫層作為容器的執行層,然後當容器裡面需要去修改一些檔案,比如File2,也是copy on write機制把檔案複製上來,然後做一些修改,新增檔案的時候也是一樣,然後容器在執行的時候也會有一個檢視,當我們把容器停掉的時候,檢視一層就沒有了,它會被銷燬,但是容器層讀寫層還會保留,所以我們把容器停掉再啟動的時候,我們依舊會看到我們之前在容器裡面的一些操作。

常見的儲存驅動主要有AUFS、OverlayFS,還有Device Mapper,前兩種驅動都是基於檔案,它的原理就是需要修改一個檔案的時候把整個檔案複製上去做修改, Device Mapper更偏底層一點,它是基於塊裝置的,它的好處在於當我想要修改一個檔案的時候,我不會將整個檔案拷上去,我會將檔案修改的一些儲存塊拷上去做一些修改,當我有一些大檔案想要修改的時候,Device Mapper會比AUFS、OverlayFS好很多。所以AUFS和OverlayFS就比較適合傳統的WEB應用,它的檔案操作不會很多,但是它可能對我們的應用啟動速度會有一些要求,比如我可能經常要釋出,我希望能夠啟動比較快,但是對於檔案修改的一些效率我不是很關心,那可以使用基於檔案的驅動,當我們是一些計算密集型的應用時候,我們就可以選擇Device Mapper,雖然啟動比較慢,但是它的執行效率相對錶現要好一些。

映象自動化構建

Docker 映象優化與最佳實踐

我們構建一個映象的時候,Docker其實提供了一個標準化的構建指令集,當我們去用這些構建指令去寫類似於指令碼,這種指令碼我們稱之為DockerFile,Docker可以自動解析DockerFile,並將其構建成一個映象,所以你就可以簡單的認為這是一個標準化的指令碼。DockerFile在做一些什麼?首先第一行FROM指令表示要以哪一個映象作為基礎映象進行構建,我們用了openJDK的官方映象,以JAVA環境作為基礎,我們在映象上面準備跑一個JAVA應用,然後接下來兩條LABLE是對映象進行打標,標下映象版本和構建日期,然後接下來的六個RUN是做了一個maven安裝,maven是JAVA的一個生命週期管理工具,接下來將一些原始碼從外面的環境新增到映象裡面,然後兩條RUN命令做了打包工作,最後寫了一個啟動命令。

Docker 映象優化與最佳實踐

總的來說DockerFile寫的還可以,至少思路是很清晰的,一步一步從基礎映象選擇到編譯環境,再把原始碼加進去,然後再到最後的構建,啟動命令寫好,可讀性、可維護性都可以,但是還是可以進行優化的。

我們可以減少映象的層數, Docker對於Docker映象的層數是有一定要求的,除掉最上面在容器執行時候的讀寫層以外,我們一個映象最多隻能有127層,如果超過可能會出現問題,所以第二行命令LABLE就可以把它合成一層,減少了層數,下面六個RUN命令做了maven的安裝工作,我們也可以把它做成一層,把這些命令串起來,後面的構建我們也可以把它合成一層,這樣我們一下就把映象層數從14層減少到7層,減掉了一半。

我們在做映象優化的時候,我們希望能夠儘量減少映象的層數,但是和它相對應的是我們DockerFile的可讀性,我們需要在這兩者之間做折中,我們在保證可讀性不受很大影響的情況下去儘量減少它,其實六條RUN命令在做一件事,就是做maven環境打結,做編譯環境的準備工作。

Docker 映象優化與最佳實踐

接下來我們繼續對映象進行優化,我們可以做一些什麼工作呢?在安裝maven構建工具的時候我們多加了一行,我們把安裝包和展開目錄刪掉了,我們清理了構建的中間產物,我們要去注意每一個構建指令執行的時候,儘量把垃圾清理掉,我們通過apt-get去裝一些軟體的時候,我們也可以去做這樣的清理工作,就是把這些軟體包裝完之後就可以把它刪掉了,這樣可以儘量減少空間,通過增加一行命令,我們可以把映象的大小從137M削減到119M。

通過apt-get去裝軟體或者命令基本上是所有編寫DockerFile的人都去寫的,所以官方已經在debian、Ubuntu的倉庫映象裡面預設加了Hack,它會去幫助你在install自動去把原始碼刪掉。

Docker 映象優化與最佳實踐

我們可以利用構建的快取,Docker構建預設會開啟快取,快取生效有三個關鍵點,映象父層沒有發生變化,構建指令不變,新增檔案校驗和一致。只要一個構建指令滿足這三個條件,這一層映象構建就不會再執行,它會直接利用之前構建的結果,根據構建快取特性我們可以加一行RUN,這裡是以JAVA應用為例,一般一個JAVA應用的pom檔案都是描述JAVA的一些依賴,而在我們平常的開發過程中這些依賴包發生變化的頻率比較低,那麼我們就可以把POM加進來,把POM檔案依賴全部都準備好,然後再去下原始碼,再去做構建工作,只要我們沒有把快取關掉,我們每次構建的時候就不需要重新下安裝包,這樣可以節省大量時間,也可以節省一些網路流量。

Docker 映象優化與最佳實踐

現在阿里雲的容器映象服務其實已經提供了構建功能,我們在統計使用者失敗案例的時候就會發現,網路原因導致的失敗佔90%,比如如果使用者通過node開發NPM在安裝一些軟體包的時候經常卡在中間。所以我們建議加一個軟體源,我們把阿里雲maven地址加到裡面去,我們把配置項加到阿里雲的軟體地址,加阿里雲的maven源作為軟體包的下載目標,時間直接少了40%,這樣對一個映象構建的成功率也是有幫助的。

多階段構建

Docker 映象優化與最佳實踐

DockerFile最終需要做到的產物其實是JAVA應用,我們對於構建、編譯、打包或者安裝這些事情都不關心,我們要的其實是最後的產物。所以,我們可以採取分步的方式去做映象構建,首先我們將之前遇到的所有問題全部都做成基礎映象,上面FROM映象其實已經改了新的,映象裡面已經把軟體源的地址改成了Maven,快取都已經做好了。我們會去利用快取,然後新增原始碼,我們把前面構建的事情做成了映象,讓映象去完成構建,然後我們才會去完成把JAVA包拷進去,啟動工作,但是兩個DockerFile其實是兩個映象,所以我們需要一段指令碼去輔助它,第一行的shell指令碼是做第一個構建指令,我們指定以Bulid的DockerFile去啟動構建,然後生成一個APP Bulid映象,接下來兩行指令碼是把映象生成出來,把裡面的構建產物拷出來,然後我們再去做構建,最後把我們需要的JAVA應用給構建出來,這樣我們的DockerFile相比之前就更加清晰了,而且分步很簡單。

Docker 映象優化與最佳實踐

Docker在17.05之後官方支援了多階段構建,我們把下面的指令碼去掉了,我們不需要一段輔助指令碼,我們只需要在後面申明基礎映象的地方標記,我們第一階段的構建產物名字叫什麼,我們就可以在第二個構建階段裡面用第一個構建階段的產物。比如我們第一階段把JAVA應用構建好,把Maven包裡面的target下面的JAVA架包拷到新的映象裡面,然後在所有優化做完之後效果如圖,我們在第一次構建的時候,優化前102秒,在Docker構建優化後只花55秒就完成了,主要優化在網路上面。當我們修改了JAVA檔案重新進行構建,第二次構建花了86秒,因為Maven安裝那一塊被快取了,我們利用了構建快取,所以少掉20多秒,優化後只花了8秒,因為所有的原始碼前面的一些軟體包下載全部被快取了,我們直接拉新的映象,然後依賴沒有變,直接進行構建,所以8秒基本上是完整構建時間。

我們再來看一下儲存空間上面的優化,第一次構建我們在優化前把映象打出來有137M,但是在我們整個優化之後,只有81M了,這裡的基礎映象由JDK改成JRE,為什麼?因為之前我們把所有流程都放在一個映象裡面時,我們是需要去做構建的,構建時需要去RUN Maven,這種情況下沒有JDK環境是RUN不起來的,但是如果我們分階段,把構建交給Maven映象來做,把真正執行交給新的映象來做,就沒必要用JDK了,我們直接用JRE,優化之後映象少了將近50%。當我們修改原始碼重新進行構建的時候,由於映象成共享的原因,第二次構建在優化前其實多加了兩層到三層,一共有9M,但是優化後的第二次構建只增加1.93KB,這樣我們針對DockerFile的優化就已經做完了。

映象優化有哪些重要的點呢?具體如下:

1. 減少映象的層數,儘量把一些功能上面統一的命令合到一起來做;

2. 注意清理映象構建的中間產物,比如一些安裝包在裝完之後就把它刪掉;

3. 注意優化網路請求,我們去用一些映象源,去用一些網路比較好的開源站點,這樣可以節約時間、減少失敗率;

4. 儘量去用構建快取,我們儘量把一些不變的東西或者變的比較少的東西放在前面,因為不變的東西都是可以被快取的;

5. 多階段進行映象構建,將我們映象製作的目的做一個明確,把我們的構建和真正的一些產物做分離,構建就用構建的映象去做,最終產物就打最終產物的映象。

容器映象服務

最後介紹一下阿里雲容器映象服務。這個服務已經公測一年了,現在我們的服務公測是全部免費的,現在在全球的12個Region都已經部署了我們的服務,每個Region其實都有內網服務和VPC網路服務,如果ECS也在同樣的Region,那麼它的服務是非常快的。然後團隊管理和組織帳號功能也已經上線了,映象購建和映象訊息通知其實都是一些DevOps能力,針對一些映象優化我們提供了一些映象層資訊瀏覽功能,我們後續也會提供分析,推出映象安全掃描、映象同步。


相關文章