標籤:

深入分析 Docker 鏡像原理

第一部分:Docker 鏡像的基本知識

1.1 什麼是 Docker 鏡像

從整體的角度來講,一個完整的 Docker 鏡像可以支撐一個 Docker 容器的運行,在 Docker 容器運行過程中主要提供文件系統視角。例如一個 ubuntu:14.04 的鏡像,提供了一個基本的 ubuntu:14.04 的發行版,當然此鏡像是不包含操作系統 Linux 內核的。

說到此,可能就需要注意一下,linux 內核和 ubuntu:14.04 Docker 鏡像的區別了。傳統虛擬機安裝 ubuntu:14.04 會包含兩部分,第一,某一個 Linux 內核的發行版本,比如 Linux 3.8 版本的內核;第二,第一個特定的 Ubuntu 發行版,這部分內容不包含 Linux 內核,但是包含 Linux 之外的軟體管理方式,軟體驅動,如 apt-get 軟體管理包等。

理解以上內容之後,就可以理解,為什麼在一個 Linux 內核版本為 3.8 的 ubuntu:14.04 基礎上,可以把 Linux 內核版本升級到 3.18,而 ubuntu 的版本依然是 14.04。最主要的就是:Linux 內核版本與 ubuntu 操作系統發行版之間的區別。

Linux 內核+ubuntu 操作系統發行版,組成一台工作的機器讓用戶體驗。那麼靈活替換 ubuntu 操作系統發行版,那是不是也可以實現呢。那麼 Docker 很方便的利用了這一點,技術手段就是 Docker 鏡像。

Docker 的架構中,Docker 鏡像就是類似於 「ubuntu 操作系統發行版」,可以在任何滿足要求的 Linux 內核之上運行。簡單一點有 「Debian 操作系統發行版」 Docker 鏡像、「Ubuntu 操作系統發行版」 Docker鏡像;如果在 Debian 鏡像中安裝 MySQL 5.6,那我們可以將其命名為 Mysql:5.6 鏡像;如果在 Debian 鏡像中安裝有 Golang 1.3,那我們可以將其命名為 golang:1.3 鏡像;以此類推,大家可以根據自己安裝的軟體,得到任何自己想要的鏡像。

那麼鏡像最後的作用是什麼呢?很好理解,回到 Linux 內核上來運行,通過鏡像來運行時我們常常將提供的環境稱為容器。

以上內容是從宏觀的角度看看 Docker 鏡像是什麼,我們再從微觀的角度進一步深入 Docker 鏡像。剛才提到了「Debian 鏡像中安裝 MySQL 5.6,就成了 mysql:5.6 鏡像」,其實在此時 Docker 鏡像的層級概念就體現出來了。底層一個 Debian 操作系統鏡像,上面疊加一個 mysql 層,就完成了一個 mysql 鏡像的構建。層級概念就不難理解,此時我們一般 debian 操作系統鏡像稱為 mysql 鏡像層的父鏡像。

層級管理的方式大大便捷了 Docker 鏡像的分發與存儲。說到分發,大家自然會聯想到 Docker 鏡像的靈活性,傳輸的便捷性,以及高超的移植性。Docker Hub,作為全球的鏡像倉庫,作為 Docker 生態中的數據倉庫,將全世界的 Docker 數據匯聚在一起,是 Docker 生態的命脈。

Docker 有兩方面的技術非常重要,第一是 Linux 容器方面的技術,第二是 Docker 鏡像的技術。從技術本身來講,兩者的可複製性很強,不存在絕對的技術難點,然而 Docker Hub 由於存在大量的數據的原因,導致 Docker Hub 的可複製性幾乎不存在,這需要一個生態的營造。

1.2 Docker 鏡像的內容

大致介紹了 Docker 鏡像是什麼,我們來看看 Docker 鏡像中有哪些內容?

介紹之前,我先分享一下,我個人在接觸 Docker 的兩年時間中,對 Docker 鏡像內容認識的變化。

第一階段:初步接觸 Docker。相信很多愛好者都會和我一樣,有這樣一個認識: Docker 鏡像代表一個容器的文件系統內容。

第二階段:初步接觸聯合文件系統。聯合文件系統的概念,讓我意識到鏡像層級管理的技術,每一層鏡像都是容器文件系統內容的一部分。

第三階段:研究鏡像與容器的關係:容器是一個動態的環境,每一層鏡像中的文件屬於靜態內容,然而 Dockerfile 中的 ENV、VOLUME、CMD 等內容最終都需要落實到容器的運行環境中,而這些內容均不可能直接坐落到每一層鏡像所包含的文件系統內容中,那此時每一個 Docker 鏡像還會包含 json 文件記錄與容器之間的關係。

因此,Docker 鏡像的內容主要包含兩個部分:第一,鏡像層文件內容;第二,鏡像 json 文件。

1.3 Docker 鏡像存儲位置

既然是說鏡像存儲的位置,那麼應該包含:鏡像層文件和鏡像 json 文件。如一個 ubuntu:14.04 鏡像,包含 4 個鏡像層,在 aufs 存儲驅動的情況下,在磁碟上的情況可以如以下圖所示:

1.3.1 查看鏡像層組成:

我們可以通過命令 docker history ubuntu:14.04 查看 ubuntu:14.04,結果如下:

1.3.2 鏡像層文件內容存儲

Docker 鏡像層的內容一般在 Docker 根目錄的 aufs 路徑下,為 /var/lib/docker/aufs/diff/,具體情況如下:

圖中顯示了鏡像 ubuntu:14.04 的 4 個鏡像層內容,以及每個鏡像層內的一級目錄情況。需要額外注意的是:鏡像層 d2a0ecffe6fa 中沒有任何內容,也就是所謂的空鏡像。

1.3.3 鏡像 json 文件存儲

對於每一個鏡像層,Docker 都會保存一份相應的 json 文件,json 文件的存儲路徑為 /var/lib/docker/graph,ubuntu:14.04 所有鏡像層的 json 文件存儲路徑展示如下:

除了 json 文件,大家還看到每一個鏡像層還包含一個 layersize 文件,該文件主要記錄鏡像層內部文件內容的總大小。既然談到了鏡像 json 文件,為了給下文鋪墊,以下貼出 ubuntu:14.04 中空鏡像層 d2a0ecffe6fa 的 json 文件:

Docker 鏡像存儲,就和大家一起先看到這。同時介紹 Docker 鏡像的基本知識也告一段落。以下我們進入此次分享的第二部分。

第二部分 Dockerfile、Docker 鏡像和 Docker 容器的關係

Dockerfile 是軟體的原材料,Docker 鏡像是軟體的交付品,而 Docker 容器則可以認為是軟體的運行態。從應用軟體的角度來看,Dockerfile、Docker 鏡像與 Docker 容器分別代表軟體的三個不同階段,Dockerfile 面向開發,Docker 鏡像成為交付標準,Docker 容器則涉及部署與運維,三者缺一不可,合力充當 Docker 體系的基石。

簡單來講,Dockerfile構建出Docker鏡像,通過Docker鏡像運行Docker容器。

我們可以從Docker容器的角度,來反推三者的關係。首先可以來看下圖:

我們假設這個容器的鏡像通過以下 Dockerfile 構建而得:

FROM ubuntu:14.04 nADD run.sh / nVOLUME /data nCMD ["./run.sh"] n

2.1 Dockerfile 與 Docker 鏡像

首先,我們結合上圖來看看 Dockerfile 與 Docker 鏡像之間的關係。

FROM ubuntu:14.04:設置基礎鏡像,此時會使用基礎鏡像 ubuntu:14.04 的所有鏡像層,為簡單起見,圖中將其作為一個整體展示。

ADD run.sh /:將 Dockerfile 所在目錄的文件 run.sh 加至鏡像的根目錄,此時新一層的鏡像只有一項內容,即根目錄下的 run.sh。

VOLUME /data:設定鏡像的 VOLUME,此 VOLUME 在容器內部的路徑為 /data。需要注意的是,此時並未在新一層的鏡像中添加任何文件,即構建出的磁層鏡像中文件為空,但更新了鏡像的 json 文件,以便通過此鏡像啟動容器時獲取這方面的信息。

CMD ["./run.sh"]:設置鏡像的默認執行入口,此命令同樣不會在新建鏡像中添加任何文件,僅僅在上一層鏡像 json 文件的基礎上更新新建鏡像的 json 文件。

因此,通過以上分析,以上的Dockerfile可以構建出一個新的鏡像,包含4個鏡像層,每一條命令會和一個鏡像層對應,鏡像之間會存在父子關係。

圖中很清楚的表明了這些關係。

2.2 Docker 鏡像與 Docker 容器的關係

Docker 鏡像是 Docker 容器運行的基礎,沒有 Docker 鏡像,就不可能有 Docker 容器,這也是 Docker 的設計原則之一。

可以理解的是:Docker 鏡像畢竟是鏡像,屬於靜態的內容;而 Docker 容器就不一樣了,容器屬於動態的內容。動態的內容,大家很容易聯想到進程,內存,CPU 等之類的東西。的確,Docker 容器作為動態的內容,都會包含這些。

為了便於理解,大家可以把 Docker 容器,理解為一個或多個運行進程,而這些運行進程將佔有相應的內存,相應的 CPU 計算資源,相應的虛擬網路設備以及相應的文件系統資源。而 Docker 容器所佔用的文件系統資源,則通過 Docker 鏡像的鏡像層文件來提供。

那麼作為靜態的鏡像,如何才有能力轉化為一個動態的 Docker 容器呢?此時,我們可以想像:第一,轉化的依據是什麼;第二,由誰來執行這個轉化操作。

其實,轉化的依據是每個鏡像的 json 文件,Docker 可以通過解析 Docker 鏡像的 json 的文件,獲知應該在這個鏡像之上運行什樣的進程,應該為進程配置怎麼樣的環境變數,此時也就實現了靜態向動態的轉變。

誰來執行這個轉化工作?答案是 Docker 守護進程。也許大家早就理解這樣一句話: Docker 容器實質上就是一個或者多個進程,而容器的父進程就是 Docker 守護進程。這樣的,轉化工作的執行就不難理解了:Docker 守護進程手握 Docker 鏡像的 json 文件,為容器配置相應的環境,並真正運行 Docker 鏡像所指定的進程,完成 Docker 容器的真正創建。

Docker 容器運行起來之後,Docker 鏡像 json 文件就失去作用了。此時 Docker 鏡像的絕大部分作用就是:為 Docker 容器提供一個文件系統的視角,供容器內部的進程訪問文件資源。

再次回到上圖,我們再來看看容器和鏡像之間的一些特殊關係。

首先,之前已經提及 Docker 鏡像是分層管理的,管理 Docker 容器的時候,Docker 鏡像仍然是分層管理的。由於此時動態的容器中已經存在進程,進程就會對文件系統視角內的文件進行讀寫操作,因此,就會涉及一個問題:容器是否會篡改 Docker 鏡像的內容?

答案自然是不會的。統一來講,正如上圖,所有的Docker鏡像層對於容器來說,都是只讀的,容器對於文件的寫操作絕對不會作用在鏡像中。

既然如此,實現的原理就很重要,究其根本:Docker 守護進程會在 Docker 鏡像的最上層之上,再添加一個可讀寫層,容器所有的寫操作都會作用到這一層中。而如果 Docker 容器需要寫底層 Docker 鏡像中的文件,那麼此時就會涉及一個叫 Copy-on-Write 的機制,即 aufs 等聯合文件系統保證:首先將此文件從 Docker 鏡像層中拷貝至最上層的可讀寫層,然後容器進程再對讀寫層中的副本進行寫操縱。對於容器進程來講,它只能看到最上層的文件。

那最後我們再來說說:Docker 容器的文件系統視角中,到底是不是存在一些內容,不是存儲於 Docker 鏡像中的?

這次的答案依舊是肯定的。

再次重申一點,Docker 鏡像中存儲的都是一些靜態文件。這些文件原則上應該和容器具體信息以及主機信息完全解藕。那麼 Docker 容器中不存在 Docker 鏡像中的內容主要有以下幾點:

  • /proc 以及 /sys 等虛擬文件系統的內容

  • 容器的 hosts 文件,hostname 文件以及 resolv.conf 文件,這些事具體環境的信息,原則上的確不應該被打入鏡像。

  • 容器的 Volume 路徑,這部分的視角來源於從宿主機上掛載到容器內部的路徑

  • 部分的設備文件

原文鏈接:深入分析 Docker 鏡像原理

推薦閱讀:

如何學習、了解kubernetes?
DaoCloud和雲雀到底誰家的技術比較強一些?VMware和微軟系的比較?
在Ubuntu 16.04環境下安裝Docker-CE(附視頻教程)
密碼1212@KubeCon北美峰會幹貨直播培訓課
基於Docker、NodeJs實現高可用的服務發現

TAG:Docker |