对容器镜像的思考和讨论
作者 | Liu,Bo
 來源|阿里巴巴云原生公眾號
前言
常言道,startup 有 startup 的好,大廠有大廠的好,那么大廠究竟好在哪呢?拿硅谷老牌大廠們 FLG 來說,如果要問最令人懷念的是什么?Free food 和基礎(chǔ)設(shè)施(Infrastructure)一定是會(huì)上榜的,兩者均極大提升了廣大應(yīng)用開發(fā)者的幸福指數(shù)。那么能不能“讓天下沒有難做的應(yīng)用”呢?請大家把目光投向正在興起的云原生生態(tài)。
在云原生生態(tài)中,容器服務(wù)包括了鏡像和容器引擎兩個(gè)部分。其中容器鏡像作為核心的云原生應(yīng)用制品,打包了完整的操作系統(tǒng)和應(yīng)用運(yùn)行環(huán)境,應(yīng)用的迭代也因?yàn)槭褂昧诉@種不可變架構(gòu)而變得更簡單,更頻繁。
本文將圍繞著容器鏡像這一核心,分享它的相關(guān)知識和業(yè)界的思考與實(shí)踐。
容器鏡像的概念
1)容器鏡像
容器鏡像有一個(gè)官方的類比,“生活中常見的集裝箱”,雖然擁有不同的規(guī)格,但箱子本身是不可變的(Immutable),只是其中裝的內(nèi)容不同。
對于鏡像來說,不變的部分包含了運(yùn)行一個(gè)應(yīng)用軟件(如 mysql)所需要的所有元素。開發(fā)者可以使用一些工具(如 Dockerfile)構(gòu)建出自己的容器鏡像,簽名并上傳到互聯(lián)網(wǎng)上,然后需要運(yùn)行這些軟件的人可以通過指定名稱(如 example.com/my-app)下載、驗(yàn)證和運(yùn)行這些容器。
2)OCI 標(biāo)準(zhǔn)鏡像規(guī)范
在 OCI 標(biāo)準(zhǔn)鏡像規(guī)范出臺之前,其實(shí)有兩套廣泛使用的鏡像規(guī)范,分別是 appc 和 docker v2.2,但“合久必分,分久必合”,有意思的是兩者的內(nèi)容已經(jīng)在各自的發(fā)展中逐步同化了,所以 OCI 組織順?biāo)浦鄣卦?docker v2.2 的基礎(chǔ)上推出了 oci image format spec,規(guī)定了對于符合規(guī)范的鏡像,允許開發(fā)者只要對容器打包和簽名一次,就可以在所有的容器引擎上運(yùn)行該容器。
這份規(guī)范給出了 OCI image 的定義:
This specification defines an OCI Image, consisting of a manifest, an image index (optional), a set of filesystem layers, and a configuration.3)容器的工作流程
一個(gè)典型的容器工作流程是從由 developers 制作容器鏡像開始的(build),然后上傳到鏡像存儲(chǔ)中心(ship),最后部署在集群中(run)。
容器鏡像技術(shù)發(fā)展中遇到的問題
不得不說,容器鏡像的設(shè)計(jì)是很出彩的,首先它蘊(yùn)含了“完整的操作系統(tǒng)就是一個(gè)包”的優(yōu)秀思想,帶著大家跳出了安裝包的思路,又提出了諸如 dockerfile 這樣的提升開發(fā)者體驗(yàn)的 killer features,還能利用分層結(jié)構(gòu)來節(jié)約時(shí)間空間。
不過,“金無足赤,人無完人”,優(yōu)秀的設(shè)計(jì)并不等于優(yōu)秀的實(shí)踐,下面來聊一聊問題具體出在哪。
1. 容器鏡像使用者
1)問題一:啟動(dòng)容器慢
容器啟動(dòng)慢的情況普遍發(fā)生在當(dāng)用戶啟動(dòng)一個(gè)很大 size 的容器鏡像時(shí),由于在容器準(zhǔn)備階段需要三步(以 overlayfs 為例):
-  download 鏡像。 
-  unpack 鏡像。 
-  使用 overlayfs 將容器可寫層和鏡像中的只讀層聚合起來提供容器運(yùn)行環(huán)境。 
其中,download 鏡像時(shí)需要 download 整個(gè)鏡像,不能實(shí)現(xiàn)文件數(shù)據(jù)按需加載。再加上 download 鏡像本身受限于網(wǎng)絡(luò)帶寬的影響,當(dāng)容器鏡像 size 在到幾個(gè) G 時(shí),下載時(shí)間會(huì)較長,破壞了容器原本優(yōu)秀的用戶體驗(yàn)。
2)問題二:較高的本地存儲(chǔ)成本
不同鏡像之間可以共享的最小單位是鏡像中的層,它的缺點(diǎn)之一是在 deduplication 上的效率是較低的,原因是:
-  首先,層內(nèi)部存在重復(fù)的數(shù)據(jù)。 
-  其次,層與層之間可能存在大量重復(fù)的數(shù)據(jù),但即使有微小的差別,也會(huì)被作為不同的層。 
-  再次,根據(jù) OCI image spec 對刪除文件和 hardlink 的設(shè)計(jì),一個(gè)鏡像內(nèi)部可能存在已經(jīng)被上層刪除的文件仍然存在于下層中,并包含在鏡像中。 
所以,當(dāng)不同鏡像的容器被調(diào)度到同一臺機(jī)器上運(yùn)行時(shí),鏡像本身在本地文件系統(tǒng)中所占的存儲(chǔ)空間是一筆不可忽視的成本開銷。
2. 鏡像提供者側(cè)
這里的提供者主要指容器服務(wù)的鏡像中心。
1)問題一:巨大的存儲(chǔ)浪費(fèi)
-  存在大量相似鏡像 造成這種情況有兩個(gè)原因: -  首先,上面提到的層的缺點(diǎn),在容器鏡像中心會(huì)產(chǎn)生許多相似鏡像。 
-  其次,OCI image 使用了 tar+gzip 格式來表達(dá)鏡像中的層,而 tar 格式并不區(qū)分 tar archive entries ordering,這帶來一個(gè)問題,即如果用戶在不同機(jī)器上 build 去同一個(gè)鏡像,最終可能會(huì)因?yàn)槭褂昧瞬煌奈募到y(tǒng)而得到不同的鏡像,然后用戶上傳之后,鏡像中心中會(huì)存在若干不同鏡像的實(shí)質(zhì)內(nèi)容是完全相同的情況。 
 
-  
-  鏡像去重效率低:雖然鏡像中心有垃圾回收來實(shí)現(xiàn)去重功能,但其仍然以層為單位,所以只能在有完全相同 hash value 的層之間去重。 
2)問題二:云原生軟件供應(yīng)鏈帶來的新需求
隨著時(shí)間推移,和軟件供應(yīng)鏈一起發(fā)展的還有對軟件供應(yīng)鏈環(huán)節(jié)的多樣性攻擊手段。安全防護(hù)是軟件供應(yīng)鏈中非常重要的組成,不光體現(xiàn)在對軟件本身的安全增強(qiáng),也體現(xiàn)在對供應(yīng)鏈本身的安全增強(qiáng)。而因?yàn)閼?yīng)用運(yùn)行環(huán)境被前置到了容器鏡像中,所以對容器鏡像的安全,包括對鏡像的漏洞掃描和簽名成為了容器服務(wù)提供者的必要能力。
對容器鏡像的思考和討論
1. 業(yè)界的嘗試
針對前面所述的問題,業(yè)界大小廠也是集思廣益,各顯神通,下面提幾個(gè)典型的項(xiàng)目:
1)CernVM-FS
使用了 fuse 按需從遠(yuǎn)程加載需要的數(shù)據(jù)。
2)Slacker
通過設(shè)計(jì)一個(gè)鏡像的 benchmark 貢獻(xiàn)了幾個(gè)有意思的理論基礎(chǔ):
-  事實(shí)上,容器啟動(dòng)時(shí)間很長。 
-  啟動(dòng)時(shí)數(shù)據(jù)讀寫放大系數(shù)很大(啟動(dòng)時(shí)中只使用 6% 的數(shù)據(jù))。 
-  分析了 57 個(gè) docker image 的 layer 數(shù)量,發(fā)現(xiàn)一半以上的 image 的 layer 數(shù)量大于 9。 
Slacker 最終使用了按需加載和減少鏡像層數(shù)將啟動(dòng)速度提高了 5-20 倍。
3)SquashFs
Oracle 使用 Linux SquashFS 來替代 targz 存儲(chǔ)容器 image layer 的內(nèi)容,去掉了 unpack tar 的環(huán)節(jié)。
2. OCI 社區(qū)中的討論
自 2019 年開始,對于鏡像本身的吐槽慢慢多了起來,發(fā)酵了一年多,OCI 社區(qū)覺得時(shí)機(jī)成熟了,從 2020 年 6 月開始,花了一個(gè)多月時(shí)間密集討論了當(dāng)前 OCI 鏡像規(guī)范的缺陷,以及 OCIv2 鏡像格式(*)需要滿足哪些要求。
(*)OCIv2 在這里只是一個(gè)宣傳命名,實(shí)際上 OCIv2 是當(dāng)前 OCI 鏡像規(guī)范的改進(jìn),而不會(huì)是一個(gè)全新的鏡像規(guī)范。
1)OCI 鏡像規(guī)范的缺陷
經(jīng)過討論得出目前的缺陷主要有兩點(diǎn):
-  tar 格式標(biāo)準(zhǔn) - tar 格式并不區(qū)分 tar archive entries ordering,這帶來一個(gè)問題,即如果用戶在不同機(jī)器上去 build 同一個(gè)鏡像,最終可能會(huì)因?yàn)槭褂昧瞬煌奈募到y(tǒng)而得到不同的鏡像,比如在文件系統(tǒng) A 上的 order 是 foo 在 bar 之前進(jìn)入 tar,在文件系統(tǒng) B 上的 order 是 bar 在 foo 之前進(jìn)入 tar,那么這兩個(gè)鏡像是不同的。
- 當(dāng) tar 被 gzip 壓縮過之后不支持 seek,導(dǎo)致 run container 之前必須先下載并解壓 targz 的 image layers,而不能實(shí)現(xiàn)文件數(shù)據(jù)按需加載。
 
-  以層為鏡像的基本單位 - 內(nèi)容冗余:不同層之間相同信息在傳輸和存儲(chǔ)時(shí)都是冗余內(nèi)容,在不讀取內(nèi)容的時(shí)候無法判斷到這些冗余的存在。
- 無法并行:單一層是一個(gè)整體,對同一個(gè)層既無法并行傳輸,也不能并行提取。
- 無法進(jìn)行小塊數(shù)據(jù)的校驗(yàn),只有完整的層下載完成之后,才能對整個(gè)層的數(shù)據(jù)做完整性校驗(yàn)。
- 其他一些問題:比如,跨層數(shù)據(jù)刪除難以完美處理。
 
2)下一代鏡像格式的要求
這次鏡像格式大討論從一個(gè)郵件和一份共享文檔開始,并促成了多次在線的 OCI 社區(qū)討論會(huì)議。最后的結(jié)論也很鼓舞人心,在這份共享文檔中可以找到對 OCIv2 鏡像格式需要滿足的要求的詳細(xì)描述。我們可以將這些要求分類為:
(*): 諸如 file timestamp 等只在一個(gè)特定機(jī)器上有意義的 metadata 是沒有必要存在于鏡像中的。
可以看出,上面這些要求明確了容器鏡像的下一步重點(diǎn)在易用、效率、安全三個(gè)方面,達(dá)到在 “build - ship - run” 這三個(gè)階段協(xié)同優(yōu)化的目的。
3. 阿里云在容器鏡像上的思考
阿里云一直積極地推動(dòng)和發(fā)展云原生生態(tài),提供了基礎(chǔ)設(shè)施“阿里云容器鏡像服務(wù)(ACR)”作為用戶云原生容器化的第一站,負(fù)責(zé)提供容器鏡像、Helm Chart 等 OCI Artifacts 管理和分發(fā)服務(wù)。同時(shí)我們也在結(jié)合容器業(yè)務(wù)現(xiàn)狀深化對容器鏡像格式的理解,不斷地總結(jié)什么是滿足發(fā)展需求的容器鏡像格式,這里可以概括為以下幾點(diǎn),新的鏡像格式需要:
- 滿足容器 “build once, run anywhere” 的理念。
- 實(shí)現(xiàn)在鏡像中心和容器運(yùn)行結(jié)點(diǎn)上存儲(chǔ)資源上的高效使用。
- 在容器鏡像的全鏈路上(build, ship, run)比現(xiàn)有的 OCI 鏡像格式速度更快。
- 能夠擴(kuò)展在安全上的能力。
- 最大程度兼容已有基礎(chǔ)設(shè)施,普惠大多數(shù)用戶。
阿里云沙箱容器的鏡像加速
相比于社區(qū)的討論重點(diǎn)放在了新的鏡像格式的設(shè)計(jì)上,阿里云更關(guān)心如何設(shè)計(jì)出一套優(yōu)化全鏈路的鏡像方案,為客戶帶來能夠應(yīng)用在生產(chǎn)中的價(jià)值。
在明確以上在技術(shù)發(fā)展過程中產(chǎn)生的需求之后,我們?yōu)榘⒗镌粕诚淙萜髟O(shè)計(jì)了新的鏡像格式 Rafs,并為 CNCF 下的 Dragonfly 項(xiàng)目引入了容器鏡像服務(wù),它能夠極大縮短鏡像下載時(shí)間,并提供端到端的鏡像數(shù)據(jù)一致性校驗(yàn),從而讓用戶能夠更安全快捷地管理容器應(yīng)用。
1. Rafs: 鏡像格式
Rafs 把一個(gè)容器鏡像只分成元數(shù)據(jù)和數(shù)據(jù)兩層。其中:
- 元數(shù)據(jù)層:元數(shù)據(jù)層是一顆自校驗(yàn)的哈希樹。每個(gè)文件和目錄都是哈希樹中的一個(gè)附帶哈希值的節(jié)點(diǎn)。一個(gè)文件節(jié)點(diǎn)的哈希值是由文件的數(shù)據(jù)確定,一個(gè)目錄節(jié)點(diǎn)的哈希值則是由該目錄下所有文件和目錄的哈希值確定。
- 數(shù)據(jù)層:每個(gè)文件的數(shù)據(jù)被按照固定大小切片并保存到數(shù)據(jù)層中。數(shù)據(jù)切片可以在不同文件以及不同鏡像中的不同文件共享。
2. Nydus: Dragonfly 的容器鏡像服務(wù)
除了使用上面的鏡像格式 Rafs,Nydus 還包含一個(gè)負(fù)責(zé)解析容器鏡像的 FUSE 用戶態(tài)文件系統(tǒng)進(jìn)程。
nydus 能夠解析 FUSE 或者 virtiofs 協(xié)議來支持傳統(tǒng)的 runC 容器或者阿里云沙箱容器。容器倉庫、OSS 對象存儲(chǔ)、NAS、以及 Dragonfly 的超級節(jié)點(diǎn)和 peer 節(jié)點(diǎn)都可以作為 nydus 的鏡像數(shù)據(jù)源。同時(shí),nydus 還可以配置一個(gè)本地緩存,從而避免每次啟動(dòng)都從遠(yuǎn)端數(shù)據(jù)源拉取數(shù)據(jù)。
基于這個(gè)設(shè)計(jì)架構(gòu),nydus 分別在 build, ship, run 和兼容性方面提供下面這些優(yōu)化:
3. 為什么選擇基于文件的設(shè)計(jì)
在設(shè)計(jì)之初,Nydus 選擇了基于文件的設(shè)計(jì)而不是基于塊的設(shè)計(jì),為什么這樣做呢?
主要的原因是,我們想在鏡像加速的基礎(chǔ)上做基于容器特點(diǎn)的附加能力,這一切都建立在能夠獲取到鏡像中的文件元數(shù)據(jù);而基于塊的設(shè)計(jì)只使用 disk LBA,天然的無法獲取其上層(即文件系統(tǒng))中的信息。
有了文件元數(shù)據(jù)之后,我們輕松地實(shí)現(xiàn)了以下幾個(gè)增值功能:
- 鏡像優(yōu)化建議:在 build container 環(huán)節(jié),提示用戶有哪些文件是根本沒有訪問過的,可以考慮借此來優(yōu)化鏡像。
- 預(yù)讀:在 run container 環(huán)節(jié)預(yù)加載,猜到用戶要讀文件,那就預(yù)先在讀操作發(fā)生之前送過去,從而優(yōu)化訪問速度。
- 安全審計(jì):在 run container 環(huán)節(jié),如果一個(gè)容器訪問鏡像內(nèi)容的模式和其他容器產(chǎn)生了明顯差異,那么,這有可能是一個(gè)安全性風(fēng)險(xiǎn)。
- 變更風(fēng)險(xiǎn)發(fā)現(xiàn):在 run container 環(huán)節(jié),如果一個(gè)鏡像升級之后,發(fā)現(xiàn)它訪問內(nèi)容的模式和之前發(fā)生了明顯差異,那么,要么是程序自己有意變了,要么就可能是引入 bug 了,這時(shí)可以考慮提醒開發(fā)者這個(gè)變化。
總結(jié)
OCI image 分層鏡像機(jī)制雖然極大地方便了開發(fā),但在大規(guī)模集群運(yùn)行時(shí),也有頗多不足,對此,OCI 鏡像社區(qū)也在尋求著如何利用鏡像內(nèi)容可感知性,讓它更加快速、節(jié)省資源,也更加安全。阿里云在這個(gè)基礎(chǔ)上本著為客戶帶來價(jià)值的原則,提出了公有云上對鏡像的穩(wěn)定性、預(yù)讀等需求,并為阿里云沙箱容器研發(fā)出了相應(yīng)的的鏡像加速方案,實(shí)現(xiàn) “build-ship-run” 整個(gè)鏡像鏈路上的統(tǒng)一優(yōu)化,讓用戶不光聽著熱鬧,也能用著開心,切實(shí)得到云原生基礎(chǔ)設(shè)施發(fā)展的紅利。
點(diǎn)擊參與“容器鏡像使用調(diào)查問卷”填寫,將有 10 位隨機(jī)獲贈(zèng)阿里云容器鏡像服務(wù)(企業(yè)版)ACR EE 50 元優(yōu)惠券哦~
總結(jié)
以上是生活随笔為你收集整理的对容器镜像的思考和讨论的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: 怎么提升写代码的能力
- 下一篇: 亲历者说 | 完整记录一年多考拉海购的云
