【Docker】(9)---每天5分鍾玩轉 Docker 容器技術之鏡像


鏡像是 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 容器技術》書籍



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM