衆所周知,Docker 始於2013年的 dotCloud,迄今剛剛七年,若是你恰好在圈中經歷了2013-2015年這段早期歲月的話,天然應該知道,最初的 Docker = LXC + aufs,前者就是所謂的 Linux 容器了,然後者則是我今天要聊的鏡像。node
說到 Linux distro,除了作差別化的界面主題以外,核心差別通常都在於:python
而在 distro 界卻有一股清流,超脫於這兩件事情以外,它們就是 Live CD,它們裝在一張光盤裏,或者是一個 U盤上,不須要安裝、也不會改變。以前創業的時候,我司的運維大佬——彤哥曾經說過:linux
第一次見到 liveCD 時我心裏是震驚的。。git
這我固然是贊同的,那時我也是震驚的同窗之一,要知道 Knoppix 在 2000 千禧年就來到了世界,而它所基於的著名的 Debian,直到2005年6月,Sarge (3.1) 發佈的時候才正式在 stable release 裏帶上了圖形界面的安裝程序 debian-installer (簡稱 d-i),此前版本的安裝還在用文本菜單。就在這個年代,這樣一個開光盤即用、啓動起來就是圖形界面的系統,給咱們這些玩家帶來的震撼,固然是可想而知的。那時候的 Live CD 就是十三年後的 Docker,絕對配得上「驚豔」兩個字。github
要知道,一張 700MB 左右的光盤裏塞下個完整的操做系統並不容易(固然有人開頭以後也不太難,後來我愛的 DSL 能夠作到 50MB)。Knoppix 有個很棒的想法——把裝好的操做系統給壓縮一下,放在光盤裏, 隨用隨解壓,這樣,一張 700MB 光盤裏能夠放下大約 2GB 的根文件系統,這樣就跑 KDE 桌面也就沒啥問題了,當是時,distrowatch.com 上能夠看到,一大片 distro 都是基於 Knoppix 魔改的,足見其影響力。golang
Knoppix 在誕生之初的一個執念是「毫不碰本地存儲一根指頭」,而光盤,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。sql
在2005年5月,也就是十五年前,Knoppix 創造性地引入了 UnionFS,而在一年半之後的 5.1 版本中,Knoppix 引入了當年誕生的更穩定的 aufs,此後,包括你們熟悉的 Ubuntu LiveCD、Gentoo LiveCD 全都使用了 aufs。能夠說,正是 Live CD 們,提早了8年,爲 Docker 和 Docker Image 的誕生,作好了存儲上的準備。docker
這裏簡單說一句給不瞭解的人聽,所謂 union fs,是指多個不一樣文件系統聯合(堆疊)在一塊兒,呈現爲一個文件系統,它和通常的 FHS 規定的那種樹裝組織方式是不一樣的,以下圖,對於左邊的標準的目錄樹結構,任何一個文件系統,掛載在樹上的一個掛載點,根據路徑,就能夠指到一個肯定的文件系統,好比,下圖中,全部的 /usr/local/
下面的路徑都在一個文件系統上,而其餘 /usr
就會在另外一個文件系統上;而 UnionFS 是多層堆疊的,你寫的文件會停留在最上層,好比圖中,你修改過的 /etc/passwd
就會在最上的可寫層,其餘的文件就要去下面的層中去找,也就是說,它容許同一個目錄中的不一樣文件在不一樣層中,這樣,Live CD 操做系統跑起來就像真正的本地操做系統同樣能夠讀寫全部路徑了。api
讓咱們把目光放在只讀層上,這一層是 Live CD 的基礎,在 Live CD 尚未 union FS 來作分層的時候就已經存在這個只讀 rootfs 了。對 Knoppix 來講,這一層是不能直接放完整、無壓縮的操做系統的,由於在21世紀初,你們都還在用 24x 到 40x 速光驅的時代,Knoppix Live CD 面臨的一個大問題是 700MB 的光盤和龐大的桌面操做系統之間的矛盾。
開頭咱們提到了,Knoppix 的想法就是「把裝好的操做系統給壓縮一下,放在光盤裏, 隨用隨解壓」,這樣精心選擇後的 2GB 的 Distro 就能夠壓到一張光盤裏了,不過「隨用隨解壓「不是說有就有的,文件系統訪問塊設備,是要找到塊的偏移量的,壓縮了以後,這個偏移量就並不那麼好找了,全解壓到內存裏再找偏移並不那麼容易。
回到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 的性能彷佛要更好一些,尤爲在元數據操做方面。
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 用戶之多,也在於支持了了各類的壓縮算法,只用於不一樣的場景。
斗轉星移,再也不年輕的 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 跳出了軟件包這個思路,他們是這麼看的——
大概的示意圖是這樣的:
這樣,若是在同一臺機器上跑這三個應用(容器),那麼這些共享的只讀層都不須要重複下載。
更進一步說,Docker 這種分層結構的另外一個優勢在於,它自己是很是開發者友好的,能夠看到,下面這個是一個 Dockerfile 的示意,FROM 表明最底下的基礎層,以後的 RUN, ADD 這樣改變 rootfs 的操做,都會將結果存成一個新的中間層,最終造成一個鏡像。這樣,開發者對於軟件依賴性的組織能夠很是清晰地展示在鏡像的分層關係中,好比下面這個 Dockerfile 是一個 packaging 用的 image,它先裝了軟件的依賴包、語言環境,而後初始化了打包操做的用戶環境,而後拉源代碼,最後把製做軟件包的腳本放到鏡像裏。這個組織方式是從通用到特定任務的組織方式,鏡像製做者但願讓這些層能夠儘可能通用一些,底層的內容能夠在其餘鏡像中也用得上,而上層則是和本鏡像的工做最直接相關的內容,其餘開發者在看到這個 Dockerfile 的時候已經能夠基本知道這個鏡像裏有什麼、要幹什麼,以及本身是否能夠借鑑了。這個鏡像的設計是 Docker 設計裏最巧妙的地方之一,也是爲何你們都願意認同,Solomon 要作的就是開發者體驗(DX, Developer Experiences)。
FROM debian:jessie
MAINTAINER Hyper Developers <dev@hyper.sh>
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到多個數據塊,在數據塊以外,引入了一些附加的元數據來作目錄樹到數據塊的映射關係,從而能夠在沒有下載完整鏡像數據的時候也給應用呈現完整的文件系統結構,並在發生具體讀取的時候,根據索引,去獲取相應的數據來提供給應用使用。這個鏡像系統能夠帶來這些好處:
並且,這套系統在設計之初,咱們就發現,由於咱們能夠獲取到應用文件數據訪問模式的,而基於文件的一個好處是,即便鏡像升級了,它的數據訪問模式也是傾向於不太變化的,因此,咱們能夠利用應用文件數據的訪問模式作一些文件預讀之類的針對性操做。
能夠看到,系統存儲這個領域二十年來發生了一個螺旋式的進化,發生在 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。