Containerd鏡像lazy-pulling解讀

今天zouyee朋友段全鋒童鞋為大家?guī)怼禖ontainerd鏡像lazy-pulling解讀》,其中《kuberneter調(diào)度由淺入深:框架》正在編寫中,敬請期待。
一、背景
我們知道,容器運(yùn)行起來的時(shí)間是非常快的,但是如果節(jié)點(diǎn)上容器的鏡像不存在,那么在運(yùn)行容器時(shí)要先拉取鏡像,拉取鏡像在容器啟動(dòng)的過程中占用的時(shí)間比較長,這個(gè)過程要將容器所有的鏡像層都拉取到本地磁盤中。據(jù)統(tǒng)計(jì),拉鏡像操作要占用容器啟動(dòng)時(shí)間的76%。這在容器數(shù)量少的情況下問題不大,但容器數(shù)量比較多并且都是冷啟動(dòng)的時(shí)候會(huì)非常的慢。
如何解決容器冷啟動(dòng)過程中拉取鏡像慢這個(gè)問題?有這樣的一種解決思路:在容器啟動(dòng)過程中,容器要用的鏡像通過高速網(wǎng)絡(luò)按需從鏡像倉庫中讀取,而不是將鏡像所有的層都拉下來。Stargz-snapshotter是containerd下面的一個(gè)子項(xiàng)目,以Proxy Plugin的方式擴(kuò)展containerd的功能,是Containerd的一個(gè)remote-snapshotter實(shí)現(xiàn)。
版本變遷
|
Containerd |
說明 |
|
1.4 |
于2020年8月17日正式發(fā)布,帶來一系列功能,具體包括對lazy-pulling、SELinux MCS、cgroup v2以及Windows CRI的支持能力 |
二、使用
Stargz-snapshotter在kuberentes中使用比較簡單:使用Containerd作為kuberentes的CRI運(yùn)行時(shí)。在本地起一個(gè)stargz-snapshotter的服務(wù),作為containerd的一個(gè)remote snapshotter。
鏡像轉(zhuǎn)換
在使用前需要將我們的普通的鏡像轉(zhuǎn)換成stargz-snapshotter可以識(shí)別的鏡像,使用ctr-remote工具進(jìn)行轉(zhuǎn)換,下面示例是將本地一個(gè)centos鏡像進(jìn)行轉(zhuǎn)換,轉(zhuǎn)換完成后推送到鏡像倉庫中:
$ ctr-remote image optimize --plain-http --entrypoint='[ "sleep" ]' --args='[ "3000" ]' centos:7 centos:7-eg
對比
使用crictl工具在本地拉取轉(zhuǎn)換前和轉(zhuǎn)換后的鏡像,做一下對比,通過lazy的方式拉取鏡像的速度更快:
# 正常拉取鏡像$ time crictl pull centos:7Image is up to date for sha256:ef8f4eaacef5da519df36f0462e280c77f8125870dbb77e85c898c87fdbbea27real 0m5.967suser 0m0.009ssys 0m0.012s# 拉取優(yōu)化后的鏡像$ time crictl pull centos:7-egImage is up to date for sha256:36edf0c0bb4daca572ba284057960f1441b14e0d21d5e497cb47646a22f653d6real 0m0.624suser 0m0.012ssys 0m0.010s
查看鏡像層
使用crictl創(chuàng)建一個(gè)pod,進(jìn)入容器中,查看/.stargz-snapshotter/目錄下各個(gè)鏡像層在本地緩存的情況:
1 |
$ cat /.stargz-snapshotter/* {"digest":"sha256:857949cb596c96cc9e91156bf7587a105a2e1bc1e6db1b9507596a24a80f351a","size":80005845,"fetchedSize":3055845,"fetchedPercent":3.819527185794988} {"digest":"sha256:8c57b1a6bef1480562bc27d145f6d371955e1f1901ebdea590f38bfedd6e17d0","size":33614550,"fetchedSize":64550,"fetchedPercent":0.19202993941611593} |
三、原理

上圖是stargz-snapshotter的實(shí)現(xiàn)概覽,通常的我們在拉取鏡像時(shí),要將鏡像的每一層拉取下來,而使用stargz-snapshotter后containerd不再是拉取鏡像的層,而是為存儲(chǔ)在鏡像倉庫中鏡像的每一層在容器運(yùn)行節(jié)點(diǎn)上創(chuàng)建一個(gè)目錄,通過遠(yuǎn)程掛載的方式掛到各個(gè)目錄上。容器啟動(dòng)前再將各個(gè)目錄做overlay掛載,為容器提供一個(gè)rootfs。當(dāng)需要讀取某個(gè)文件時(shí),通過網(wǎng)絡(luò)讀取鏡像倉庫中鏡像層中的文件。
下面再看一下鏡像層是怎么遠(yuǎn)程掛載和如何從鏡像層中按需讀取文件的。
用戶態(tài)文件系統(tǒng)

Stargz-snapshotter使用FUSE實(shí)現(xiàn)了用戶態(tài)的文件系統(tǒng)。FUSE(Filesystem in userspace)框架是一個(gè)內(nèi)核模塊,能夠讓用戶在用戶空間實(shí)現(xiàn)文件系統(tǒng)并且掛載到某個(gè)目錄,就像在內(nèi)核實(shí)現(xiàn)文件系統(tǒng)一樣。
如上圖所示,stargz-snapshotter是一個(gè)實(shí)現(xiàn)了用戶態(tài)文件系統(tǒng)的程序(golang語言,使用go-fuse作為實(shí)現(xiàn)的依賴)。當(dāng)有拉取鏡像的操作發(fā)生時(shí),stargz-snapshotter會(huì)為鏡像的每一層在${stargz-root}/snapshotter/snapshots/下創(chuàng)建一個(gè)目錄,執(zhí)行實(shí)現(xiàn)一個(gè)文件系統(tǒng)的邏輯,并將這個(gè)文件系統(tǒng)掛載到剛創(chuàng)建的目錄上,例如圖中的/dcos/snapshotter/snapshots/1。當(dāng)有用戶讀取目錄下的文件時(shí),請求的流向是這樣的:
① 操作請求經(jīng)VFS到FUSE
② FUSE內(nèi)核模塊根據(jù)請求類型,調(diào)用stargz-snapshotter的邏輯,stargz-snapshotter從鏡像倉庫中讀取該層中的文件
③ Stargz-snapshotter將文件的內(nèi)容通過VFS返回給系統(tǒng)調(diào)用
(e)stargz格式
a. stargz格式

通常存放在鏡像倉庫中的鏡像層都是使用gzip壓縮過的,我們不能從這個(gè)壓縮后的文件中提取單個(gè)文件。那stargz-snapshotter是怎么做到從單個(gè)鏡像層中讀取單個(gè)文件的呢?
Stargz-snapshotter使用了另一種壓縮鏡像層的格式,它也是gzip包,一種可seekable的gzip包,圖3是targz和stargz的對比。壓縮包里的文件可以被檢索和抽取,但仍是zip格式的文件;鏡像層中的每個(gè)文件都會(huì)被打成一個(gè)zip包,最后再組成一個(gè)大的zip包;整個(gè)zip包中有一個(gè)TOC文件,它記錄了包中每個(gè)文件的偏移量;Footer占最后47個(gè)字節(jié),記錄了TOC在整個(gè)zip包中的偏移量。
這樣就可以通過鏡像層最后47個(gè)字節(jié)的Footer,找到TOC的偏移量,然后讀取TOC的內(nèi)容就能得到整個(gè)鏡像層中有哪些文件,每個(gè)文件的偏移量是多少。Stargz-snapshotter就是通過這個(gè)TOC文件去按需檢索整個(gè)鏡像層中文件的。
b. estartgz格式

默認(rèn)情況下,將鏡像的某一層遠(yuǎn)程掛載到目標(biāo)主機(jī)后,stargz-snapshotter默認(rèn)會(huì)創(chuàng)建一個(gè)后臺(tái)任務(wù)去緩存鏡像層。在容器啟動(dòng)過程中,如果容器啟動(dòng)需要的文件沒有在本地緩存那么stargz-snapshotter就需要通過網(wǎng)絡(luò)去鏡像倉庫中讀取,這會(huì)導(dǎo)致容器啟動(dòng)速度比較慢。
estargz是對stargz格式進(jìn)行了優(yōu)化,如上圖所示。它多了一個(gè)landmark文件,這個(gè)文件將鏡像層中的文件分成了兩類:一類是容器運(yùn)行時(shí)最有可能用到的,另一類是可能用不到的。這樣后臺(tái)任務(wù)會(huì)優(yōu)先去緩存那些容器運(yùn)行時(shí)需要的文件,這樣會(huì)增加本地緩存的命中率,加快容器的啟動(dòng)速度。
下圖是三種鏡像層的對比情況,legacy是普通鏡像層,stargz是stargz格式的鏡像層,estargz是優(yōu)化后stargz格式的鏡像層。
c. 分層拉取鏡像
鏡像層使用estargz格式可以做到從壓縮包中檢索文件,那stargz是如何從鏡像倉庫中按照分片獲取文件全部或者部分?jǐn)?shù)據(jù)的?
在OCI規(guī)范中有關(guān)于如何從倉庫中獲取部分?jǐn)?shù)據(jù)的描述,而docker registry也有對應(yīng)接口實(shí)現(xiàn)。
Registry中獲取鏡像層部署數(shù)據(jù)的接口如下:

其中,name就是目標(biāo)repository的名稱,digest就是鏡像層blob的digest的值,Host就是鏡像倉庫的地址,Range描述的就是要獲取的blob分片。
返回的響應(yīng)如下:

其中,start是開始的字節(jié),end是結(jié)束的字節(jié),size是層大小,length是本次請求的層分片。
lazy-pulling流程

Containerd使用stargz-snapshotter拉取鏡像的流程如下:
① 根據(jù)鏡像名稱和tag解析出鏡像manifest的digest的值
② 根據(jù)鏡像manifest的digest的值,從鏡像倉庫中下載manifest,保存在content store中
③ 根據(jù)manifest的內(nèi)容獲取鏡像config的digest的值,從鏡像倉庫中下載config,保存在content store中
④ 解析鏡像的每一層,創(chuàng)建snapshot,如果containerd使用的stargz-snapshotter,它會(huì)返回一個(gè)snapshot已經(jīng)存在的錯(cuò)誤。Stargz-snapshotter PrepareSnapshot的邏輯就是為當(dāng)前層準(zhǔn)備文件系統(tǒng)并掛載到本地的一個(gè)過程。
⑤ 所有鏡像層解析完成后會(huì)保存鏡像的元數(shù)據(jù)
四、小結(jié)
創(chuàng)建容器時(shí),拉取鏡像過程在容器啟動(dòng)時(shí)間的占比高,通常我們會(huì)使用多種方法去制作盡量小一點(diǎn)的鏡像,或者通過P2P網(wǎng)絡(luò)去分發(fā)鏡像。鏡像lazy pull是另一種提高鏡像分發(fā)速度的方式。
使用stargz-snapshotter在鏡像拉取時(shí),僅將鏡像的manifest和config下載下來,并鏡像每一層通過遠(yuǎn)程掛載的方式掛到當(dāng)前主機(jī)上,容器運(yùn)行時(shí)達(dá)到按需讀取文件的效果。而傳統(tǒng)方式是將鏡像的每一層都下載到本地進(jìn)行解壓。相比而言前者能加快鏡像的拉取速度,加快容器冷啟動(dòng)的速度。但需要注意,文件是按需加載的,它依賴于一個(gè)比較好的網(wǎng)絡(luò)環(huán)境。
參考資料
1. https://www.usenix.org/conference/fast16/technical-sessions/presentation/harter
2. https://github.com/google/crfs
3. https://github.com/opencontainers/distribution-spec/blob/master/spec.md#fetch-blob-part
4. https://docs.docker.com/registry/spec/api/#fetch-blob-part


你可能還喜歡
點(diǎn)擊下方圖片即可閱讀

云原生是一種信仰 ??
掃碼關(guān)注公眾號(hào)
后臺(tái)回復(fù)?k8s?獲取史上最方便快捷的 Kubernetes 高可用部署工具,只需一條命令,連 ssh 都不需要!

點(diǎn)擊 "閱讀原文" 獲取更好的閱讀體驗(yàn)!
??給個(gè)「在看」,是對我最大的支持??

