映象是 Docker 容器的基石,容器是映象的執行例項,有了映象才能啟動容器。為什麼我們要討論映象的內部結構?
如果只是使用映象,當然不需要了解,直接通過 docker 命令下載和執行就可以了。
但如果我們想建立自己的映象,或者想理解 Docker 為什麼是輕量級的,就非常有必要學習這部分知識了。
一、最小的映象
1、執行hello-world映象
hello-world 是 Docker 官方提供的一個映象,通常用來驗證 Docker 是否安裝成功。
我們先通過 docker pull
從 Docker Hub 下載它。
用 docker images
命令檢視映象的資訊。
hello-world 映象竟然還不到 14KB! 通過 docker run
執行。
其實我們更關心 hello-world 映象包含哪些內容。
2. hello-world映象內容
Dockerfile 是映象的描述檔案,定義瞭如何構建Docker映象。Dockerfile的語法簡潔且可讀性強,後面我們會專門討論如何編寫Dockerfile。
hello-world 的 Dockerfile 內容如下:
只有短短三條指令。
#1、此映象是從白手起家,從 0 開始構建。
FROM scratch
#2、將檔案“hello”複製到映象的根目錄。
COPY hello /
#3、容器啟動時,執行 /hello
CMD ["/hello"]
映象 hello-world 中就只有一個可執行檔案 “hello”,其功能就是列印出 “Hello from Docker ......” 等資訊。
/hello 就是檔案系統的全部內容,連最基本的 /bin,/usr, /lib, /dev 都沒有。
hello-world 雖然是一個完整的映象,但它並沒有什麼實際用途。通常來說,我們希望映象能提供一個基本的作業系統環境,使用者可以根據
需要安裝和配置軟體。這樣的映象我們稱作 base 映象。我們下一節討論 base 映象。
二、base 映象
1.base映象含義
base 映象有兩層含義:
1、不依賴其他映象,從 scratch 構建。
2、其他映象可以之為基礎進行擴充套件。
所以,能稱作 base 映象的通常都是各種 Linux 發行版的 Docker 映象,比如 Ubuntu, Debian, CentOS 等。
2.base映象內容
我們以 CentOS 為例考察 base 映象包含哪些內容。
下載映象:docker pull centos
檢視映象資訊
映象大小 231MB。等一下!一個 CentOS 才 200MB ?平時我們安裝一個 CentOS 至少都有幾個 GB,怎麼可能才 200MB !
相信這是幾乎所有 Docker 初學者都會有的疑問,包括我自己。下面我們來解釋這個問題。
Linux 作業系統由核心空間
和使用者空間
組成。如下圖所示:
核心空間是kernel
,Linux 剛啟動時會載入 bootfs 檔案系統,之後 bootfs 會被解除安裝掉。使用者空間的檔案系統是 rootfs
,包含我們熟悉的
/dev, /proc, /bin 等目錄。對於 base 映象來說,底層直接用 Host 的 kernel,自己只需要提供 rootfs 就行了。
而對於一個精簡的 OS,rootfs 可以很小,只需要包括最基本的命令、工具和程式庫就可以了。相比其他 Linux 發行版,CentOS 的 rootfs
已經算臃腫的了,alpine 還不到 10MB。我們平時安裝的 CentOS 除了 rootfs 還會選裝很多軟體、服務、圖形桌面等,需要好幾個 GB 就
不足為奇了。下面是 CentOS 映象的 Dockerfile 的內容:
第二行 ADD 指令新增到映象的 tar 包就是 CentOS 7 的 rootfs。在製作映象時,tar包會自動解壓到 / 目錄下,生成 /dev, /porc, /bin 等目錄。
不同 Linux 發行版的區別主要就是 rootfs。
比如 Ubuntu 14.04 使用 upstart 管理服務,apt 管理軟體包;而 CentOS 7 使用 systemd 和 yum。這些都是使用者空間上的區別,
Linux kernel 差別不大。所以 Docker 可以同時支援多種 Linux 映象,模擬出多種作業系統環境。
上圖 Debian 和 BusyBox(一種嵌入式 Linux)上層提供各自的 rootfs,底層共用 Docker Host 的 kernel。
這裡需要說明的是:
容器只能使用 Host 的 kernel,並且不能修改。
所有容器都共用 host 的 kernel,在容器中沒辦法對 kernel 升級。如果容器對 kernel 版本有要求(比如應用只能在某個 kernel 版本下執行),則不建議用容器,這種場景虛擬機器可能更合適。
下一節我們討論映象的分層結構。
三、映象的分層結構
Docker 支援通過擴充套件現有映象,建立新的映象。
1、映象分層示例
實際上,Docker Hub 中 99% 的映象都是通過在 base 映象中安裝和配置需要的軟體構建出來的。比如我們現在構建一個新的映象,Dockerfile 如下:
① 新映象不再是從 scratch 開始,而是直接在 Debian base 映象上構建。
② 安裝 emacs 編輯器。
③ 安裝 apache2。
④ 容器啟動時執行 bash。
構建過程如下圖所示:
可以看到,新映象是從 base 映象一層一層疊加生成的。每安裝一個軟體,就在現有映象的基礎上增加一層。
2、映象分層好處
問什麼 Docker 映象要採用這種分層結構呢?
最大的一個好處就是 - 共享資源
。
比如:有多個映象都從相同的 base 映象構建而來,那麼 Docker Host 只需在磁碟上儲存一份 base 映象;同時記憶體中也只需載入一份 base
映象,就可以為所有容器服務了。而且映象的每一層都可以被共享,我們將在後面更深入地討論這個特性。
這時可能就有人會問了:如果多個容器共享一份基礎映象,當某個容器修改了基礎映象的內容,比如 /etc 下的檔案,這時其他容器的 /etc
是否也會被修改?
答案是不會!
修改會被限制在單個容器內。
這就是我們接下來要學習的容器 Copy-on-Write 特性。
3、Copy-on-Write 特性
可寫的容器層
當容器啟動時,一個新的可寫層被載入到映象的頂部。這一層通常被稱作“容器層”,“容器層”之下的都叫“映象層”。
所有對容器的改動 - 無論新增、刪除、還是修改檔案都只會發生在容器層中。
只有容器層是可寫的,容器層下面的所有映象層都是隻讀的
。
下面我們深入討論容器層的細節。
映象層數量可能會很多,所有映象層會聯合在一起組成一個統一的檔案系統。如果不同層中有一個相同路徑的檔案,比如 /a,上層的 /a 會
覆蓋下層的 /a,也就是說使用者只能訪問到上層中的檔案 /a。在容器層中,使用者看到的是一個疊加之後的檔案系統。
新增檔案
在容器中建立檔案時,新檔案被新增到容器層中。
讀取檔案
在容器中讀取某個檔案時,Docker 會從上往下依次在各映象層中查詢此檔案。一旦找到,開啟並讀入記憶體。
修改檔案
在容器中修改已存在的檔案時,Docker 會從上往下依次在各映象層中查詢此檔案。一旦找到,立即將其複製到容器層,然後修改之。
刪除檔案
在容器中刪除檔案時,Docker 也是從上往下依次在映象層中查詢此檔案。找到後,會在容器層中記錄下此刪除操作。
只有當需要修改時才複製一份資料,這種特性被稱作 Copy-on-Write。可見容器層儲存的是映象變化的部分,不會對映象本身進行任何修改。
這樣就解釋了我們前面提出的問題:*容器層記錄對映象的修改,所有映象層都是隻讀的,不會被容器修改,所以映象可以被多個容器共享。
理解了映象的原理和結構,下一節我們學習如何構建映象。
四、構建映象
1、為何要構建映象
對於 Docker 使用者來說,最好的情況是不需要自己建立映象。幾乎所有常用的資料庫、中介軟體、應用軟體等都有現成的 Docker 官方映象或
其他人和組織建立的映象,我們只需要稍作配置就可以直接使用。
使用現成映象的好處除了省去自己做映象的工作量外,更重要的是可以利用前人的經驗。特別是使用那些官方映象,因為 Docker 的工程師
知道如何更好的在容器中執行軟體。
當然,某些情況下我們也不得不自己構建映象,比如:
1. 找不到現成的映象,比如自己開發的應用程式。
2. 需要在映象中加入特定的功能,比如官方映象幾乎都不提供 ssh。
所以本節我們將介紹構建映象的方法。同時分析構建的過程也能夠加深我們對前面映象分層結構的理解。
2、構建映象方法
Docker 提供了兩種構建映象的方法:
1. docker commit 命令
2. Dockerfile 構建檔案
3、docker commit構建映象
docker commit 命令是建立新映象最直觀的方法,其過程包含三個步驟:
1. 執行容器
2. 修改容器
3. 將容器儲存為新的映象
舉個例子:在 ubuntu base 映象中安裝 vi 並儲存為新映象。
1)、第一步:執行容器
-it
引數的作用是以互動模式進入容器,並開啟終端。6e2d389d4576 是容器的內部 ID。
2)、第二步:安裝 vi
確認 vi 沒有安裝。開始安裝 apt-get install -y vim
3)、第三步:儲存新映象
在新視窗中檢視當前執行的容器。
gifted_stallman 是 Docker 為我們的容器隨機分配的名字。
執行 docker commit
命令將容器儲存為映象。
新映象命名為 ubuntu-with-vi
。
檢視新映象的屬性。
從 size 上看到映象因為安裝了軟體而變大了。從新映象啟動容器,驗證 vi 已經可以使用。
以上演示瞭如何用 docker commit 建立新映象。然而,Docker 並不建議使用者通過這種方式構建映象。原因如下:
1. 這是一種手工建立映象的方式,容易出錯,效率低且可重複性弱。比如要在 debian base 映象中也加入 vi,還得重複前面的所有步驟。
2. 更重要的:使用者並不知道映象是如何建立出來的,裡面是否有惡意程式。也就是說無法對映象進行審計,存在安全隱患。
既然 docker commit 不是推薦的方法,我們幹嘛還要花時間學習呢?
原因是:即便是用 Dockerfile(推薦方法)構建映象,底層也 docker commit 一層一層構建新映象的。學習 docker commit 能夠幫助我們
更加深入地理解構建過程和映象的分層結構。
下一節我們學習如何通過 Dockerfile 構建映象。
五、Dockerfile 構建映象
Dockerfile 是一個文字檔案,記錄了映象構建的所有步驟。
1、Dockerfile 構建映象
1)建立Dockerfile檔案
touch Dockerfile
2)用 Dockerfile 建立上節的 ubuntu-with-vi,其內容則為:
3)構建映象
docker build -t ubuntu-with-vi-dockerfile .
ubuntu-with-vi-dockerfile是構建映象所取的名字
執行 docker build
命令,-t
將新映象命名為 ubuntu-with-vi-dockerfile,命令末尾的 .
指明 build context 為當前目錄。
Docker 預設會從 build context 中查詢 Dockerfile 檔案,我們也可以通過 -f 引數指定 Dockerfile 的位置。
4)映象構建成功
通過 docker images 檢視映象資訊。
可以看到新映象已經構建成功,而且大小跟之前docker commit 構建的大小是一樣大的。
參考
本部落格所有內容均來自 《每天5分鐘玩轉 Docker 容器技術》書籍