雲栖号資訊:【 點選檢視更多行業資訊】
在這裡您可以找到不同行業的第一手的上雲資訊,還在等什麼,快來!
衆所周知,Docker 始于2013年的 dotCloud,迄今剛剛七年,如果你剛好在圈中經曆了2013-2015年這段早期歲月的話,自然應該知道,最初的 Docker = LXC + aufs,前者就是所謂的 Linux 容器了,而後者則是我今天要聊的鏡像。
千禧年:驚豔的 Live CD
說到 Linux distro,除了做差異化的界面主題之外,核心差異一般都在于:
- 如何更友善地安裝;
- 如何更友善地更新;
而在 distro 界卻有一股清流,超脫于這兩件事情之外,它們就是 Live CD,它們裝在一張CD光牒裡,或者是一個 U盤上,不需要安裝、也不會改變。之前創業的時候,我司的運維大佬——彤哥曾經說過:
第一次見到 liveCD 時我内心是震驚的。。
這我當然是贊同的,那時我也是震驚的同學之一,要知道 Knoppix 在 2000 千禧年就來到了世界,而它所基于的著名的 Debian,直到2005年6月,Sarge (3.1) 釋出的時候才正式在 stable release 裡帶上了圖形界面的安裝程式 debian-installer (簡稱 d-i),此前版本的安裝還在用文本菜單。就在這個年代,這樣一個開CD光牒即用、啟動起來就是圖形界面的系統,給我們這些玩家帶來的震撼,當然是可想而知的。那時候的 Live CD 就是十三年後的 Docker,絕對配得上“驚豔”兩個字。

要知道,一張 700MB 左右的CD光牒裡塞下個完整的作業系統并不容易(當然有人開頭之後也不太難,後來我愛的 DSL 可以做到 50MB)。Knoppix 有個很棒的想法——把裝好的作業系統給壓縮一下,放在CD光牒裡, 随用随解壓,這樣,一張 700MB CD光牒裡可以放下大約 2GB 的根檔案系統,這樣就跑 KDE 桌面也就沒啥問題了,當是時,distrowatch.com 上可以看到,一大片 distro 都是基于 Knoppix 魔改的,足見其影響力。
進化:可讀寫層與 UnionFS
Knoppix 在誕生之初的一個執念是“絕不碰本地存儲一根指頭”,而CD光牒,CD-ROM,所使用的 ISO9600 檔案系統也是隻讀的,這無疑暗合了當今流行的“不可變基礎設施”的潮流,但是,即使在今天,沒有可寫檔案系統對于很多 Linux 軟體仍然是非常困難的,畢竟随随便便一個程式也要寫一點配置檔案、狀态資訊、鎖、日志之類的嘛。而誕生之初的 Knoppix 是不可寫的,那麼,要有什麼東西要羅盤,就得手工挖掘出本地硬碟來挂上,或者是挂個 NAS 到 /home 或其他挂載點上來,當你不想隻是做個緊急啟動盤的時候,這就有點麻煩了。
如果我們從今天穿越回去,毫不費力就可以指出,用 overlayfs 加上一個 tmpfs 做可寫層嘛。但是,overlayfs 要到2010年才首次送出 patchset,2014年才被合并到 3.18核心(這中間,當時的淘寶核心組也有不少貢獻和踩坑呢)。當然,比 overlay 早的類似的 unionfs 還是有的,Docker 最早采用的 Aufs 就是其中之一,它是2006年出現的,這裡 AUFS 的 A,可以了解成 Advanced,但它最早的意思實際是 Another——是的,“另一個 UFS”,而它的前身就是 UnionFS。
在2005年5月,也就是十五年前,Knoppix 創造性地引入了 UnionFS,而在一年半以後的 5.1 版本中,Knoppix 引入了當年誕生的更穩定的 aufs,此後,包括大家熟悉的 Ubuntu LiveCD、Gentoo LiveCD 全都使用了 aufs。可以說,正是 Live CD 們,提前了8年,為 Docker 和 Docker Image 的誕生,做好了存儲上的準備。
這裡簡單說一句給不了解的人聽,所謂 union fs,是指多個不同檔案系統聯合(堆疊)在一起,呈現為一個檔案系統,它和一般的 FHS 規定的那種樹裝組織方式是不同的,如下圖,對于左邊的标準的目錄樹結構,任何一個檔案系統,挂載在樹上的一個挂載點,根據路徑,就可以指到一個确定的檔案系統,比如,下圖中,所有的 /usr/local/ 下面的路徑都在一個檔案系統上,而其他 /usr 就會在另一個檔案系統上;而 UnionFS 是多層堆疊的,你寫的檔案會停留在最上層,比如圖中,你修改過的 /etc/passwd 就會在最上的可寫層,其他的檔案就要去下面的層中去找,也就是說,它允許同一個目錄中的不同檔案在不同層中,這樣,Live CD 作業系統跑起來就像真正的本地作業系統一樣可以讀寫所有路徑了。
塊或檔案:Cloop 與 SquashFS
讓我們把目光放在隻讀層上,這一層是 Live CD 的基礎,在 Live CD 還沒有 union FS 來做分層的時候就已經存在這個隻讀 rootfs 了。對 Knoppix 來說,這一層是不能直接放完整、無壓縮的作業系統的,因為在21世紀初,大家都還在用 24x 到 40x 速光驅的時代,Knoppix Live CD 面臨的一個大問題是 700MB 的CD光牒和龐大的桌面作業系統之間的沖突。
開頭我們提到了,Knoppix 的想法就是“把裝好的作業系統給壓縮一下,放在CD光牒裡, 随用随解壓”,這樣精心選擇後的 2GB 的 Distro 就可以壓到一張CD光牒裡了,不過“随用随解壓“不是說有就有的,檔案系統通路塊裝置,是要找到塊的偏移量的,壓縮了之後,這個偏移量就并不那麼好找了,全解壓到記憶體裡再找偏移并不那麼容易。
回到2000年,那時候還是2.2核心,Knoppix 的作者 Klaus Knopper 在創立 Knoppix 之初就引入了一種壓縮的(compressed) loop 裝置,稱為 cloop,這種裝置是一種特殊的格式,它包含了一個索引,進而讓解壓縮的過程對使用者來說是透明的,Knoppix 的 cloop 裝置看起來就是一個大約 2GB 大的塊裝置,當應用讀寫一個偏移的資料的時候,它隻需要根據索引解壓縮對應的資料塊,并傳回資料給使用者就可以了,無需全盤解壓縮。
盡管 Knoppix 把衆多 distro 帶上了 Live CD 的船,但是,衆多後繼者,諸如 arch、Debian、Fedora、Gentoo、Ubuntu 等等 distro 的 LiveCD,以及大家熟悉的路由器上玩的 OpenWrt,都并沒有選擇 cloop 檔案,它們選擇了和應用語義更接近的檔案系統級的解決方案——Squashfs。Squashfs 壓縮了檔案、inode 和目錄,并支援從 4K 到 1M 的壓縮單元尺寸。同樣,它也是根據索引按需解壓的,和 cloop 的不同之處是,當使用者通路一個檔案的時候,它來根據索引解壓相應的檔案所在的塊,而非再經過一層本地檔案系統到壓縮塊的尋址,更加簡單直接。事實上,Knoppix 裡也一直有呼聲想要切換到 squashfs,比如,2004年就有開發者在轉換 knoppix 到squashfs 上,而且,一些測試資料似乎也表明 Squashfs 的性能似乎要更好一些,尤其在中繼資料操作方面。
在 wiki 上是這麼評價 cloop 的缺點的:
The design of the cloop driver requires that compressed blocks be read whole from disk. This makes cloop access inherently slower when there are many scattered reads, which can happen if the system is low on memory or when a large program with many shared libraries is starting. A big issue is the seek time for CD-ROM drives (~80 ms), which exceeds that of hard disks (~10 ms) by a large factor. On the other hand, because files are packed together, reading a compressed block may thus bring in more than one file into the cache. The effects of tail packing are known to improve seek times (cf. reiserfs, btrfs), especially for small files. Some performance tests related to cloop have been conducted.
我來畫蛇添足地翻譯一下:
cloop 的設計要求從磁盤上以壓縮塊為機關讀取資料。這樣,當有很多随機的讀操作時,cloop 就會顯著地變慢,當系統記憶體不足或者一個有很多共享庫的大型程式啟動的時候都很可能發生。cloop 面臨的一個很大的問題是 CD-ROM 的尋道時間(約80毫秒),這在很大程度上超過了硬碟的查找時間(約10毫秒)。另一方面,由于檔案可以被打包在一起,是以讀取壓縮塊實際可能可以将多個檔案帶入緩存。這樣,那些支援 tail packing 的檔案系統(比如 reiserfs,btrfs)可能可以顯著改善 seek 操作的時間,尤其是對于小檔案更是如此。已經有一些與 cloop 相關的性能測試也證明了這些觀點。
當然,盡管有這些争論,cloop 也仍然在 Knoppix 上存在,不過,這個争論最終随着2009年 squashfs 被并入 2.6.29 主線核心,應該算是分出勝負了,進入 kernel 帶來的開箱即用換來的是壓倒性的占有率和更好的支援,Squashfs 的優勢不僅在上述的 distro 使用者之多,也在于支援了了各種的壓縮算法,隻用于不同的場景。
Docker: Make Unionfs Great Again
鬥轉星移,不再年輕的 Live CD 也因為如此普及,而讓人覺得并不新奇了。但是,技術圈也有輪回一般,當年被 Live CD 帶紅的 Union FS 們再一次被 Docker 捧紅,煥發了第二春。一般地說,雖然 aufs 們支援多個隻讀層,但普通的 Live CD 隻要一個隻讀鏡像和一個可寫層留給使用者就可以了。然而, 以 Solomon 為首的 dotCloud 的朋友們充分發揮了他們卓越的想象力,把整個檔案系統變成了“軟體包”的基本機關,進而做到了 #MUGA (Make Unionfs Great Again)。
回想一下,從1993年的 Slackware 到今天的 RHEL,(服務端的)Distro 的所作所為,不外乎我開頭提到的兩件事情——安裝和更新。從 rpm 到 APT/deb 再到 Snappy,初始化好系統之後的工作的精髓就在于怎麼更平滑地安裝和更新、確定沒有依賴問題,又不額外占據太多的空間。解決這個問題的基礎就是 rpm/deb 這樣的包以及包之間的依賴關系,然而,類似“A 依賴 B 和 C,但 B 卻和 C 沖突” 這樣的事情仍然層出不窮,讓人們不停地嘗試解決了二十年。
但 Docker 跳出了軟體包這個思路,他們是這麼看的——
- 完整的作業系統就是一個包,它必然是自包含的,而且如果在開發、測試、部署時都保持同樣完整、不變的作業系統環境,那麼也就沒有那麼多依賴性導緻的問題了;
- 這個完整的作業系統都是不可變的,就像 Live CD 一樣,我們叫它鏡像,可以用 aufs 這樣的 union FS 在上面放一個可寫層,應用可以在運作時寫東西到可寫層,一些動态生成的配置也可以放在可寫層;
- 如果一些應用軟體鏡像,它們共用同一部分基礎系統,那麼,把這些公共部分,放在 Unionfs 的下層作為隻讀層,這樣他們可以給不同的應用使用;當然,如果兩個應用依賴的東西不一樣,那它們就用不同的基礎層,也不需要互相遷就了,自然沒有上面的依賴性沖突存在了;
- 一個鏡像可以包含多個層,這樣,更友善應用可以共享一些資料,節省存儲和傳輸開銷;
大概的示意圖是這樣的:
這樣,如果在同一台機器上跑這三個應用(容器),那麼這些共享的隻讀層都不需要重複下載下傳。
更進一步說,Docker 這種分層結構的另一個優點在于,它本身是非常開發者友好的,可以看到,下面這個是一個 Dockerfile 的示意,FROM 代表最底下的基礎層,之後的 RUN, ADD 這樣改變 rootfs 的操作,都會将結果存成一個新的中間層,最終形成一個鏡像。這樣,開發者對于軟體依賴性的組織可以非常清晰地展現在鏡像的分層關系中,比如下面這個 Dockerfile 是一個 packaging 用的 image,它先裝了軟體的依賴包、語言環境,然後初始化了打包操作的使用者環境,然後拉源代碼,最後把制作軟體包的腳本放到鏡像裡。這個組織方式是從通用到特定任務的組織方式,鏡像制作者希望讓這些層可以盡量通用一些,底層的内容可以在其他鏡像中也用得上,而上層則是和本鏡像的工作最直接相關的内容,其他開發者在看到這個 Dockerfile 的時候已經可以基本知道這個鏡像裡有什麼、要幹什麼,以及自己是否可以借鑒了。這個鏡像的設計是 Docker 設計裡最巧妙的地方之一,也是為什麼大家都願意認同,Solomon 要做的就是開發者體驗(DX, Developer Experiences)。
FROM debian:jessie
MAINTAINER Hyper Developers <[email protected]>
RUN apt-get update &&\
apt-get install -y autoconf automake pkg-config dh-make cpio git \
libdevmapper-dev libsqlite3-dev libvirt-dev python-pip && \
pip install awscli && \
apt-get clean && rm -fr /var/lib/apt/lists/* /tmp/* /var/tmp/*
RUN curl -sL https://storage.googleapis.com/golang/go1.8.linux-amd64.tar.gz | tar -C /usr/local -zxf -
RUN useradd makedeb && mkdir -p ~makedeb/.aws && chown -R makedeb.makedeb ~makedeb && chmod og-rw ~makedeb/.aws
RUN mkdir -p /hypersrc/hyperd/../hyperstart &&\
cd /hypersrc/hyperd && git init && git remote add origin https://github.com/hyperhq/hyperd.git && \
cd /hypersrc/hyperstart && git init && git remote add origin https://github.com/hyperhq/hyperstart.git && \
chown makedeb.makedeb -R /hypersrc
ENV PATH /usr/local/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
ENV USER makedeb
ADD entrypoint.sh /
USER makedeb
WORKDIR /hypersrc
ENTRYPOINT ["/entrypoint.sh"]
一個規範的 Docker Image 或者脫胎于其中的 OCI Image,實際上就是一組中繼資料和一些層資料,這些層資料每個都是一個檔案系統内容的打包,從某種意義上說,典型的 Live CD 的 OS,基本上可以了解成一個隻讀層加上一個可寫層的 Docker Container 的 rootfs。在 Docker 這裡,Union FS 可以說是 Great Again 了。
未來:現代化的鏡像系統
然而,盡管 Docker Image (或者說 OCI Image)的設計蘊含了“完整的作業系統就是一個包”的優秀思想,又利用 Union FS 實作了“分層”這種既實作漂亮的開發者體驗,又能節約時間空間的精巧設計,但随着時間的推移,還是暴露出來了一些問題。從去年(2019年)年初,OCI 社群中開始有人讨論下一代鏡像格式的問題,這個熱烈的讨論中,集中讨論了 OCIv1(實際也是 Docker 的)鏡像格式的一些問題,Aleksa Sarai 也專門寫了一篇部落格來讨論這個話題,具體說,除了 tar 格式本身的标準化問題外,大家對目前的鏡像的主要不滿集中在:
- 内容備援:不同層之間相同資訊在傳輸和存儲時都是備援内容,在不讀取内容的時候無法判斷到這些備援的存在;
- 無法并行:單一層是一個整體,對同一個層既無法并行傳輸,也不能并行提取;
- 無法進行小塊資料的校驗,隻有完整的層下載下傳完成之後,才能對整個層的資料做完整性校驗;
- 其他一些問題:比如,跨層資料删除難以完美處理;
上述這些問題用一句話來總結就是“層是鏡像的基本機關”,然而,鏡像的資料的實際使用率是很低的,比如 Cern 的這篇論文中就提到,一般鏡像隻有6%的内容會被實際用到,這就産生了實質性的更新鏡像資料結構,不再以層為基本機關的動力。
可見,下一代鏡像的一個趨勢就是打破層的結構來對這些隻讀進行更進一步的優化,是的,反應快的同學可能已經回想起了前面提到的 Live CD 裡常用的 Squashfs,它可以根據讀取的需要來解壓相應的檔案塊來放入記憶體、供應用使用,這裡是不是可以擴充一下,變成在需要的時候再去遠端拉回鏡像的内容來供應用使用呢——從檔案的 Lazy decompress 到 Lazy Load,一步之遙,水到渠城。
是的,螞蟻的鏡像加速實踐裡就采取了這樣的架構。在過去,龐大的鏡像不僅讓拉取過程變慢,而且如果這一過程同樣風險重重,貢獻了大半的 Pod 啟動失敗率,而今天,當我們把延遲加載的 rootfs 引入進來的時候,這些失敗幾乎被完全消滅掉了。在去年年末的第10屆中國開源黑客松裡,我們也示範了通過 virtio-fs 把這套系統對接到 Kata Containers 安全容器裡的實作。
如圖,類似 Squashfs,這種新的 Image 格式中,壓縮資料塊是基本機關,一個檔案可以對應0到多個資料塊,在資料塊之外,引入了一些附加的中繼資料來做目錄樹到資料塊的映射關系,進而可以在沒有下載下傳完整鏡像資料的時候也給應用呈現完整的檔案系統結構,并在發生具體讀取的時候,根據索引,去擷取相應的資料來提供給應用使用。這個鏡像系統可以帶來這些好處:
- 按需加載,啟動時無需完全下載下傳鏡像,同時對加載的不完全鏡像内容可以進行完整性校驗,作為全鍊路可信的一個部分;
- 對于 runC 容器,通過 fuse 可以提供使用者态解決方案,不依賴主控端核心變動;
- 對于 Kata 這樣的虛拟化容器,鏡像資料直接送給 Pod 沙箱内部使用,不加載在主控端上;
- 使用體驗上和之前的 Docker Image 并沒有顯著不同,開發者體驗不會變差;
而且,這套系統在設計之初,我們就發現,因為我們可以擷取到應用檔案資料通路模式的,而基于檔案的一個好處是,即使鏡像更新了,它的資料通路模式也是傾向于不太變化的,是以,我們可以利用應用檔案資料的通路模式做一些檔案預讀之類的針對性操作。
可以看到,系統存儲這個領域二十年來發生了一個螺旋式的進化,發生在 Live CD 上的進化,在容器這裡也又來了一次,恍如隔世。目前,我們正在積極地參與 OCI Image v2 的标準推動,也正在把我們的參考實作和 DragonFly P2P 分發結合在一起,并成為 CNCF 的開源項目 Dragonfly 的一部分,希望在未來可以和 OCI 社群進一步互動,讓我們的需求和優勢成為社群規範的一部分,也讓我們可以和社群保持一緻、可平滑過渡,未來可以統一在 OCI-Image-v2 鏡像之下。
作者介紹
王旭,螞蟻金服資深技術專家,也是開源項目 Kata Containers 的架構委員會創始成員,在過去幾年中活躍在國内的開源開發社群與标準化工作中。在加入螞蟻金服之前,他是音速神童的聯合創始人和 CTO,他們在 2015 年開源了基于虛拟化技術的容器引擎 runV,在 2017 年 12 月,他們和 Intel 一起宣布 runV 與 Clear Containers 項目合并,成為 Kata Containers 項目,該項目于 2019 年 4 月被董事會通過成為了 OpenStack 基金會 2012 年以來的首個新開放基礎設施頂級項目。在創立音速神童之前,王旭曾工作于盛大雲計算和中國移動研究院的雲計算團隊。2011 年王旭曾經主持過杭州 QCon 的雲計算主題,同時,也曾經是一位活躍的技術作者、譯者和老 blogger。
【雲栖号線上課堂】每天都有産品技術專家分享!
課程位址:
https://yqh.aliyun.com/live立即加入社群,與專家面對面,及時了解課程最新動态!
【雲栖号線上課堂 社群】
https://c.tb.cn/F3.Z8gvnK
原文釋出時間:2020-06-24
本文作者:螞蟻金服分布式架構
本文來自:“
掘金”,了解相關資訊可以關注“掘金”