概述
Docker使構建容器鏡像變得輕而易舉。只需將標準 Dockerfile 放入您的文件夾,運行 docker build 命令,然後運行,芝麻開門!您的容器鏡像已構建成功!
這種簡單性的缺點是,很容易構建出大體積的容器鏡像,其中包含很多不需要的東西——包括潛在的安全漏洞。
因為使用的是解釋型語言還是編譯型語言,創建容器鏡像的過程會有所不同。下面一起深入瞭解(下面通過dockerfile模擬):
解釋型語言的容器化
解釋型語言,如Ruby,Python,Node.js,PHP和其他語言通過發送源代碼到解釋器來運行代碼。 這樣的好處是可以跳過編譯步驟,但其缺點是要求將解釋器與代碼一起丟進去。
幸運的是,大多數這些語言都提供了預構建的Docker容器,其中包含一個輕量級環境,允許運行更小的容器。
我們來看一個Node.js應用程序並對其進行容器化。
首先,使用node:onbuild鏡像作為基礎。 Docker容器的onbuild版本預先打包了您需要的所有內容,因此無需執行大量配置即可搞定。 這意味著Dockerfile非常簡單(只有兩行!)。 但是要付出的磁盤大小代價——差不多700MB!
通過使用較小的基礎鏡像(如Alpine),您可以顯著減少容器的大小。Alpine Linux是一款體積小,輕量級的Linux發行版,在Docker用戶中非常受歡迎,因為它與許多應用程序兼容,同時仍然保持小體積。
幸運的是,Node.js(以及其他流行語言)有一個官方的Alpine圖像,可以滿足您的一切需求。與默認的node鏡像不同,node:alpine會刪除許多文件和程序,只留下足以運行應用程序的部分。
基於Alpine Linux的Dockerfile創建起來有點複雜,因為必須運行一些針對onbuild的命令。
但是,產生的鏡像只有65MB!
編譯型語言的容器化
諸如Go,C,C ++,Rust,Haskell等編譯型語言可以創建在沒有許多外部依賴性的情況下運行的二進制文件。 這意味著你可以提前構建二進制文件並將其投入生產,而無需把創建二進制文件(如編譯器)放進去。
下面採用Go應用程序並使用此模式對其進行容器化。
首先,使用golang:onbuild鏡像作為基礎。 和以前一樣,Dockerfile只有兩行,但需付出磁盤大小超過700MB的代價!
下一步是使用更小的基礎鏡像,也就是golang:alpine鏡像。
同樣,使用Alpine基礎鏡像創建Dockerfile有點複雜,因為必須運行一些執行onbuild鏡像相關的命令。
但同樣,由此產生的鏡像要小得多,大小隻有256MB!
但是,我們可以使鏡像更小:假設不需要Go附帶的任何編譯器或其他構建和調試工具,可以從最終容器中刪除它們。
下面使用多階段構建來獲取由golang:alpine容器創建的二進制文件並將其自行打包。
下面的這個容器鏡像只有12MB大小!
在Kubernetes上拉取鏡像
雖然你可能不關心構建和推送容器鏡像所需的時間,但應該非常關心拉取容器鏡像所需的時間。 對於Kubernetes,這可能是您的生產集群最重要的指標。
例如,假設有一個三節點集群,其中一個節點崩潰。 如果使用的是像Kubernetes Engine這樣的託管系統,系統會自動生成一個新節點來代替它。
但是,這個新節點將是全新的,並且必須先拉取所有容器鏡像才能開始工作。 拉取容器鏡像所需的時間越長,集群執行的時間就越長!
當增加群集大小(例如,使用Kubernetes Engine Autoscaling)或將節點升級到新版本的Kubernetes時,可能會發生這種情況(接下來的幾集中會關注這個問題)。
我們可以看到來自多個部署的多個容器的拉取性能可以在這裡真正體現出來,使用小容器可能會浪費幾分鐘的部署時間!
安全性和漏洞
除了性能之外,使用較小的容器鏡像還有很大的安全性提升。 與使用大基礎鏡像的容器鏡像相比,小容器鏡像通常具有較小的攻擊面。
幾個月前,我構建了Go onbuild和multi-age容器鏡像,因此它們可能包含一些已被發現的漏洞。 使用Google Container Registry的內置漏洞掃描,可以輕鬆掃描容器中的已知漏洞。
較小容器鏡像中只有三個“中級”漏洞,而較大容器鏡像中有16個嚴重漏洞和300多個其他漏洞。
大多數存在的問題與應用程序無關,甚至沒有應用程序! 因為多階段構建使用的是更小的基礎鏡像,所以可以產生漏洞的東西更少。
總結
使用小容器鏡像的性能和安全優勢不言而喻。使用小的基礎鏡像和builder pattern可以更容易地構建小鏡像,並且還有許多其他技術可用於單個技術棧和編程語言,以最小化容器體積。 無論做什麼,都可以確信保持容器鏡像最小化的努力是值得的!
後期內容會更多分享DBA和DEVOPS內容,感興趣的朋友可以關注下!
閱讀更多 波波說運維 的文章