Docker是一個(gè)開(kāi)源的引擎,可以輕松的為任何應(yīng)用創(chuàng)建一個(gè)輕量級(jí)的、可移植的、自給自足的容器。開(kāi)發(fā)者在筆記本上編譯測(cè)試通過(guò)的容器可以批量地在生產(chǎn)環(huán)境中部署,包括VMs(虛擬機(jī))、bare metal、OpenStack 集群和其他的基礎(chǔ)應(yīng)用平臺(tái)。
近幾年 Docker 風(fēng)靡技術(shù)圈,不少?gòu)臉I(yè)人員都或多或少使用過(guò),也了解如何通過(guò) Dockerfile 構(gòu)建鏡像,從遠(yuǎn)程鏡像倉(cāng)庫(kù)拉取自己所需鏡像,推送構(gòu)建好的鏡像至遠(yuǎn)程倉(cāng)庫(kù),根據(jù)鏡像運(yùn)行容器等。這個(gè)過(guò)程十分簡(jiǎn)單,只需執(zhí)行 docker build、docker pull、docker push、docker run 等操作即可。但大家是否想過(guò)鏡像在本地到底是如何存儲(chǔ)的?容器又是如何根據(jù)鏡像啟動(dòng)的?推送鏡像至遠(yuǎn)程鏡像倉(cāng)庫(kù)時(shí),服務(wù)器又是如何存儲(chǔ)的呢?
Docker 鏡像本地存儲(chǔ)機(jī)制及容器啟動(dòng)原理
Docker 鏡像不是一個(gè)單一的文件,而是有多層構(gòu)成。我們可通過(guò) docker images 獲取本地的鏡像列表及對(duì)應(yīng)的元信息, 接著可通過(guò)docker history 《imageId》 查看某個(gè)鏡像各層內(nèi)容及對(duì)應(yīng)大小,每層對(duì)應(yīng)著 Dockerfile 中的一條指令。Docker 鏡像默認(rèn)存儲(chǔ)在 /var/lib/docker/《storage-driver》中,可通過(guò) DOCKER_OPTS 或者 docker daemon 運(yùn)行時(shí)指定 --graph= 或 -g 指定。
Docker 使用存儲(chǔ)驅(qū)動(dòng)來(lái)管理鏡像每層內(nèi)容及可讀寫(xiě)的容器層,存儲(chǔ)驅(qū)動(dòng)有 DeviceMapper、AUFS、Overlay、Overlay2、Btrfs、ZFS 等,不同的存儲(chǔ)驅(qū)動(dòng)實(shí)現(xiàn)方式有差異,鏡像組織形式可能也稍有不同,但都采用棧式存儲(chǔ),并采用 Copy-on-Write(CoW) 策略。且存儲(chǔ)驅(qū)動(dòng)采用熱插拔架構(gòu),可動(dòng)態(tài)調(diào)整。那么,存儲(chǔ)驅(qū)動(dòng)那么多,該如何選擇合適的呢?大致可從以下幾方面考慮:
若內(nèi)核支持多種存儲(chǔ)驅(qū)動(dòng),且沒(méi)有顯式配置,Docker 會(huì)根據(jù)它內(nèi)部設(shè)置的優(yōu)先級(jí)來(lái)選擇。優(yōu)先級(jí)為 AUFS 》 Btrfs/ZFS 》 Overlay2 》 Overlay 》 DeviceMapper。若使用 DeviceMapper 的話,在生產(chǎn)環(huán)境,一定要選擇 direct-lvm, loopback-lvm 性能非常差。
選擇會(huì)受限于 Docker 版本、操作系統(tǒng)、系統(tǒng)版本等。例如,AUFS 只能用于 Ubuntu 或 Debian 系統(tǒng),Btrfs 只能用于 SLES (SUSE Linux Enterprise Server, 僅 Docker EE 支持)。
有些存儲(chǔ)驅(qū)動(dòng)依賴(lài)于后端的文件系統(tǒng)。例如,Btrfs 只能運(yùn)行于后端文件系統(tǒng) Btrfs 上。
不同的存儲(chǔ)驅(qū)動(dòng)在不同的應(yīng)用場(chǎng)景下性能不同。例如,AUFS、Overlay、Overlay2 操作在文件級(jí)別,內(nèi)存使用相對(duì)更高效,但大文件讀寫(xiě)時(shí),容器層會(huì)變得很大;DeviceMapper、Btrfs、ZFS 操作在塊級(jí)別,適合工作在寫(xiě)負(fù)載高的場(chǎng)景;容器層數(shù)多,且寫(xiě)小文件頻繁時(shí),Overlay 效率比 Overlay2 更高;Btrfs、ZFS 更耗內(nèi)存。
Docker 容器其實(shí)是在鏡像的最上層加了一層讀寫(xiě)層,通常也稱(chēng)為容器層。在運(yùn)行中的容器里做的所有改動(dòng),如寫(xiě)新文件、修改已有文件、刪除文件等操作其實(shí)都寫(xiě)到了容器層。容器層刪除了,最上層的讀寫(xiě)層跟著也刪除了,改動(dòng)自然也丟失了。若要持久化這些改動(dòng),須通過(guò) docker commit 《containerId》 [repository[:tag]] 將當(dāng)前容器保存成為一個(gè)新鏡像。若想將數(shù)據(jù)持久化,或是多個(gè)容器間共享數(shù)據(jù),需將數(shù)據(jù)存儲(chǔ)在 Docker volume 中,并將 volume 掛載到相應(yīng)容器中。
存儲(chǔ)驅(qū)動(dòng)決定了鏡像及容器在文件系統(tǒng)中的存儲(chǔ)方式及組織形式,下面分別對(duì)常見(jiàn)的 AUFS、Overlay 作一簡(jiǎn)單介紹。
AUFS
AUFS 是 Debian (Stretch 之前的版本,Stretch默認(rèn)采用 Overlay2) 或 Ubuntu 系統(tǒng)上 Docker 的默認(rèn)存儲(chǔ)驅(qū)動(dòng),也是 Docker 所有存儲(chǔ)驅(qū)動(dòng)中最為成熟的。具有啟動(dòng)快,內(nèi)存、存儲(chǔ)使用高效等特點(diǎn)。如果使用的 Linux 內(nèi)核版本為 4.0 或更高,且使用的是 Docker CE,可考慮使用Overlay2 (比 AUFS 性能更佳)。
配置 AUFS 存儲(chǔ)驅(qū)動(dòng)
① 驗(yàn)證內(nèi)核是否支持 AUFS
$ grep aufs /proc/filesystems nodev aufs
?、?若內(nèi)核支持,可在 docker 啟動(dòng)時(shí)通過(guò)指定參數(shù) --storage-driver=aufs 選擇 AUFS
AUFS 存儲(chǔ)驅(qū)動(dòng)工作原理
采用 AUFS 存儲(chǔ)驅(qū)動(dòng)時(shí),有關(guān)鏡像和容器的所有層信息都存儲(chǔ)在 /var/lib/docker/aufs/ 目錄下,下面有三個(gè)子目錄:
/diff:每個(gè)目錄中存儲(chǔ)著每層鏡像包含的真實(shí)內(nèi)容
/layers:存儲(chǔ)有關(guān)鏡像層組織的元信息,文件內(nèi)容存儲(chǔ)著該鏡像的組建鏡像列表
/mnt:掛載點(diǎn)信息存儲(chǔ),當(dāng)創(chuàng)建容器后,mnt 目錄下會(huì)多出容器對(duì)應(yīng)的層及該容器的 init 層。目錄名稱(chēng)與容器 ID 不一致。實(shí)際的讀寫(xiě)層存儲(chǔ)在 /var/lib/docker/aufs/diff,直到容器刪除,此讀寫(xiě)層才會(huì)被清除掉。
采用 AUFS 后容器如何讀寫(xiě)文件?
容器進(jìn)行讀文件操作有以下三種場(chǎng)景:
容器層不存在: 要讀取的文件在容器層中不存在,存儲(chǔ)驅(qū)動(dòng)會(huì)從鏡像層逐層向下找,多個(gè)鏡像層中若存在同名文件,上層的有效。
文件只存在容器層:讀取容器層文件
容器層與鏡像層同時(shí)存在:讀取容器層文件
修改文件或目錄
容器中進(jìn)行文件的修改同樣存在三種場(chǎng)景:
第一次寫(xiě)文件:若待修改的文件在某個(gè)鏡像層中,AUFS 會(huì)先執(zhí)行 copy_up 操作將文件從只讀的鏡像層拷貝到可讀寫(xiě)的容器層,然后進(jìn)行修改。在文件非常大的情況下效率比較低下。
刪除文件:刪除文件時(shí),若文件在鏡像層,其實(shí)是在容器層創(chuàng)建一個(gè)特殊的 writeout 文件,容器層訪問(wèn)不到,并沒(méi)有實(shí)際刪掉。
目錄重命名:目前 AUFS 還不支持目錄重命名。
OverlayFS
OverlayFS 是一種類(lèi)似 AUFS 的現(xiàn)代聯(lián)合文件系統(tǒng),但實(shí)現(xiàn)更簡(jiǎn)單,性能更優(yōu)。OverlayFS 嚴(yán)格說(shuō)來(lái)是 Linux 內(nèi)核的一種文件系統(tǒng),對(duì)應(yīng)的 Docker 存儲(chǔ)驅(qū)動(dòng)為 Overlay 或者 Overlay2,Overlay2 需 Linux 內(nèi)核 4.0 及以上,Overlay 需內(nèi)核 3.18 及以上。且目前僅 Docker 社區(qū)版支持。條件許可的話,盡量使用 Overlay2,與 Overlay 相比,它的 inode 利用率更高。
容器如何使用 Overlay/Overlay2 讀寫(xiě)文件
讀文件存在以下三種場(chǎng)景:
文件不存在容器層:若容器要讀的文件不在容器層,會(huì)繼續(xù)從底層的鏡像層找
文件僅在容器層:若容器要讀的文件在容器層,直接讀取,不用在底層的鏡像層查找
文件同時(shí)在容器層和鏡像層:若容器要讀的文件在容器層和鏡像層中都存在,則從容器層讀取
修改文件或目錄
寫(xiě)文件存在以下三種場(chǎng)景:
首次寫(xiě)文件:若要寫(xiě)的文件位于鏡像層中,則執(zhí)行 copy_up 將文件從鏡像層拷貝至容器層,然后進(jìn)行修改,并在容器層保存一份新的。若文件較大,效率較低。OverlayFS 工作在文件級(jí)別而不是塊級(jí)別,這意味著即使對(duì)文件稍作修改且文件很大,也須將整個(gè)文件拷貝至容器層進(jìn)行修改。但需注意的是,copy_up 操作僅發(fā)生在首次,后續(xù)對(duì)同一文件進(jìn)行修改,操作容器層文件即可
刪除文件或目錄:容器中刪除文件或目錄時(shí),其實(shí)是在容器中創(chuàng)建了一個(gè) writeout 文件,并沒(méi)有真的刪除文件,只是使其對(duì)用戶不可見(jiàn)
目錄重命名:僅當(dāng)源路徑與目標(biāo)路徑都在容器層時(shí),調(diào)用 rename(2) 函數(shù)才成功,否則返回 EXDEV
遠(yuǎn)程鏡像倉(cāng)庫(kù)如何存儲(chǔ)鏡像?
不少人可能經(jīng)常使用 Docker,那么有沒(méi)有思考過(guò)鏡像推送至遠(yuǎn)程鏡像倉(cāng)庫(kù),是如何保存的呢?Docker 客戶端是如何與遠(yuǎn)程鏡像倉(cāng)庫(kù)交互的呢?
我們平時(shí)本地安裝的 Docker 其實(shí)包含兩部分:docker client 與 docker engine,docker client 與 docker engine 間通過(guò) API 進(jìn)行通信。Docker engine 提供的 API 大致有認(rèn)證、容器、鏡像、網(wǎng)絡(luò)、卷、swarm 等。Docker engine 與 registry (即:遠(yuǎn)程鏡像倉(cāng)庫(kù))的通信也有一套完整的 API,大致包含 pull、push 鏡像所涉及的認(rèn)證、授權(quán)、鏡像存儲(chǔ)等相關(guān)流程,。目前常用 Registry 版本為 v2,Registry v2 擁有斷點(diǎn)續(xù)傳、并發(fā)拉取鏡像多層等特點(diǎn)。能并發(fā)拉取多層是因?yàn)殓R像的元信息與鏡像層數(shù)據(jù)分開(kāi)存儲(chǔ),當(dāng) pull 一個(gè)鏡像時(shí),先進(jìn)行認(rèn)證獲取到 token 并授權(quán)通過(guò),然后獲取鏡像的 manifest 文件,進(jìn)行 signature 校驗(yàn)。校驗(yàn)完成后,依據(jù) manifest 里的層信息并發(fā)拉取各層。其中 manifest 包含的信息有:倉(cāng)庫(kù)名稱(chēng)、tag、鏡像層 digest 等。各層拉下來(lái)后,也會(huì)先在本地進(jìn)行校驗(yàn),校驗(yàn)算法采用 sha256。Push 過(guò)程則先將鏡像各層并發(fā)推至 Registry,推送完成后,再將鏡像的 manifest 推至 Registry。Registry 其實(shí)并不負(fù)責(zé)具體的存儲(chǔ)工作,具體存儲(chǔ)介質(zhì)根據(jù)使用方來(lái)定,Registry 只是提供一套標(biāo)準(zhǔn)的存儲(chǔ)驅(qū)動(dòng)接口,具體存儲(chǔ)驅(qū)動(dòng)實(shí)現(xiàn)由使用方實(shí)現(xiàn)。
目前官方 Registry 默認(rèn)提供的存儲(chǔ)驅(qū)動(dòng)包括:微軟 Azure、Google gcs、Amazon s3、OpenStack swift、阿里云 OSS、本地存儲(chǔ)等。若需要使用自己的對(duì)象存儲(chǔ)服務(wù),則需要自行實(shí)現(xiàn) registry 存儲(chǔ)驅(qū)動(dòng)。網(wǎng)易云目前將鏡像存儲(chǔ)在自己的對(duì)象存儲(chǔ)服務(wù) nos 上,故專(zhuān)門(mén)針對(duì) nos 實(shí)現(xiàn)了一套存儲(chǔ)驅(qū)動(dòng),另外認(rèn)證服務(wù)也對(duì)接了網(wǎng)易云認(rèn)證服務(wù),并結(jié)合自身業(yè)務(wù)實(shí)現(xiàn)了一套認(rèn)證、授權(quán)邏輯,并有效地限制了倉(cāng)庫(kù)配額。
Registry 干的事情其實(shí)很簡(jiǎn)單,大致可分為:
① 讀配置 ;
?、?注冊(cè) handler ;
?、?監(jiān)聽(tīng)。本質(zhì)上 Registry 是個(gè) HTTP 服務(wù),啟動(dòng)后,監(jiān)聽(tīng)在配置文件設(shè)定的某端口上。當(dāng) http 請(qǐng)求過(guò)來(lái)后,便會(huì)觸發(fā)之前注冊(cè)過(guò)的 Handler。Handler 包含 manifest、tag、blob、blob-upload、blob-upload-chunk、catalog 等六類(lèi)。配置文件包含監(jiān)聽(tīng)端口、auth 地址、存儲(chǔ)驅(qū)動(dòng)信息、回調(diào)通知等。
評(píng)論
查看更多