上次複習完 Docker Container 之後,接著再來複習一下什麼是 Docker Image,一樣從字面認識開始吧!
什麼是 Image?
說到 Image 這個詞,大家對這個英文單字應該都不陌生,因為在一般使用情境下,Image 就是圖片的意思。不過其實 Image 的這個詞,在電腦科學和技術領域中,還有「靜態的、不可變的檔案」的意義在,在這種時候,通常就會把這個詞稱作是「映像」或是「鏡像」。所以 Image 這個詞彙並不單純只有圖片的意思,在 Docker 的世界中,Image 這個詞,也就是剛剛提到的「映像」或是「鏡像」的意思。所謂的「映像」或是「鏡像」通常是一個完整系統或應用程式環境的快照,這樣的檔案包含了系統或軟體執行所需的所有內容,例如:程式碼、設定檔、函式庫、相依套件,甚至作業系統核心等。
Docker Image 呢?
說到 Docker Image,也就是所謂的「Docker 映像」,或是「Docker 鏡像」。前面在看 Image 這個詞的時候,就有提到在技術領域中,Image 這個詞有著「映像」或是「鏡像」的意思,在 Docker 世界裡的 image 指的也就是用來啟動 Docker Container 的靜態模板。往更細節的方向來說明的話,就是用來啟動應用程式及其執行環境的檔案。
其實 Docker Image 就像是一個建築設計圖,建築設計圖是靜態不可變的東西,但我們可以用建築設計圖建立出多個獨立的建築物,Docker Image 一樣也是靜態且不可變的,但我們可以用它啟動多個互相獨立的 Docker Container。
我們還可以用另一個和程式面更有關聯的東西來看 Docker Image,在前端世界裡,其實有一個東西和 Docker Image 有點類似,這個東西就是 Component 元件 (這裡特別強調一下,這裡指的 Component,是建立元件實例的 Component .vue 檔案,或是 React 的 Component Function),因為 Docker Image 執行出來的 Container 和 Component 實際使用時,建立出來的實例,一樣都是獨立不互相干擾的存在。差別在於我們如果想要改動創建實例的 Component 時,只要進去修改 Component 的內容就可以了(Vue 的話就是 Component 檔案,React 的話,則是去改動 Component function),讓 Component 重新渲染,就可以修改到既有的產生出來的 Component,但是如果是 Docker Image 的話,就必須重新建立一個新的 Image,才會套用到新的內容。Component 是一個可以用肉眼看到內容的檔案,Docker Image 則是由多個 Layer 組成,並且無法直接「用肉眼看到」它的內容,而是需要用指令來查看。另外,因為 Docker Image 的分層架構特性,即使將 Docker Container 停掉,實際上 Docker Image 還是會被認定為有被使用,而無法在這個狀態下將 Image 刪除。
Docker Image 的特性
前面用了很多比較的例子來看什麼時 Docker Image,接著來進一步看看前面有稍微提到的一些 Docker Image 的特性。
1. 分層架構:Docker Image 是由多個 Layer 組成的,並且透過聯合檔案系統 (Union File System) 來組合這些層,每個 Layer 代表 Image 的一個變更,例如:基礎作業系統、應用程式的程式碼。裡面的分層會被重複使用,加快 Image 建立速度,並且儉省空間。進一步的詳細內容,我們後面再來看。
2. 唯獨性:Docker Image Layer 是唯獨的,所以不能直接修改 Image 本身,但可以透過新增 Layer 來覆蓋舊內容。
3. 不可變:這部分就是前面有提到過的部分,當 Docker Image 被建立後,無法再進行 Docker Image 內容的修改,如果想要修改裡面的內容,就必須重新建立一個新的 Image。創建新的 Image 後,原本的 Image 還是可以使用,不會受到影響。
4. 可重用性:這部分也是前面有提到過的部分,那就是一個 Image 可以用來啟動多個獨立的 Container,且彼此互不影響。
5. 可擴展性:可以用一個建立好的一個 Image 來建立一個新的 Image,也就是說一個新的 Image 可以繼承於另一個既有的 Image。
Docker Image 的分層架構
接著再來進一步看看所謂的分層架構是什麼?一個 Docker Image 其實是多層的架構,將指令層層堆疊最後變成一個完整的 Image 檔案,當 Docker Container 被建立起來時,Docker 會使用檔案聯合系統(Union Filesystem, UnionFS)把這些層這些層合併成一個可操作的檔案系統。
進一步透過 Docker 指令來看看實際上說的層是什麼東西。
透過 docker history [image name] 這個指令,可以查看Docker Image 的各層歷史,這些層是由 Dockerfile 中的各條指令(如 FROM、RUN、COPY 等)所創建,每次執行指令時,Docker 會創建一個新的層。

另外,透過 docker inspect [image name] 這個指令,也可以看到 Docker Container 不同層級和底層檔案系統的結構。GraphDriver 的 Data 包括有關該 Image Layer 的存儲位置、資料夾結構等詳細資訊。RootFS 的 Layers 則是顯示出 Image 中各個層的 hash 值,這些 hash 值代表的是每個層的唯一標示。這些層堆疊在一起,就形成了 Docker Image 檔案。


當建立 Docker Container 的時候,實際上就是去利用可寫層來運行 Container,並基於 Image 中的多層結構來生成最終的檔案系統。
聯合檔案系統(Union Filesystem, UnionFS)
都已經看了 Image Layer 是什麼了,接著也來看聯合檔案系統。所謂的「聯合檔案系統」也就是一個將多個檔案系統層疊組合在一起使用的技術,這個技術雖然是將多個檔案系統合併成一個虛擬的檔案系統,但它其實還支援不同層的讀寫操作,以利 Docker 實現高效的檔案系統管理。
我知道看了這段說明感覺還是很模糊,接下來讓我們用檔案的新增、更新、刪除、讀取來了解聯合檔案系統的工作原理吧!
首先,先來介紹一些在聯合檔案系統中,幾個必須要認識的角色們,其實也就是前面在看 Image Layer 的時候,就有稍微提到過的 LowerDir、UpperDir、MergeDir、WorkDir 這些 Layer。
- LowerDir:是位在最底部的層,實際上不只一層,每層都代表 Dockerfile 中的一條指令,也就是說這一層對應於 RootFS 的 Layers,且在 LowerDir 這層的檔案只能讀取。在進行所有對檔案的讀取操作時,會先檢查 UpperDir,如果檔案不存在於 UpperDir,則會從 LowerDir 中讀取
- UpperDir:這層是可寫層,如果要進行檔案系統的新增、更新、刪除,都會先寫入這一層,再進行相對應的操作,不會直接改動 LowerDir 的檔案。
- MergeDir:這一層是合併 LowerDir 和 UpperDir 的虛擬層,Container 實際操作的會是這一層,讀取檔案時,系統會先從 UpperDir 中查找,若找不到,再從 LowerDir 中查找。
- WorkDir:這層不存在於 LowerDir 之下,它是用來支援檔案系統的操作,並且用於處理合併過程中的中間結果。它會與 LowerDir 和 UpperDir 協同工作,但它不會直接成為檔案系統層的一部分。
最後再來細看一下,當要進行新增、讀取,更新、刪除動作時,實際上會發生哪些步驟。

- 新增檔案:當建立新的檔案時,會直接在 UpperDir 產生一個檔案,不會影響到 LowerDir,並且會從 MergeDir 操作到 LowerDir。
- 讀取檔案:當在讀取檔案時,因為 MergeDir 會將 UpperDir 和 LowerDir 兩層合併,所以 Container 會先從 UpperDir 開始查找檔案,如果在這一層找不到檔案,會進一步進入 LowerDir 找檔案。
- 更新檔案:在進行檔案的編輯更新時,會複製出一份檔案到 UpperDir 來更新,不會直接改動到 LowerDir 的檔案。
- 刪除檔案:在刪除檔案時,實際上也並不會把 LowerDir 的檔案刪除,而是會建立一份 whiteout file,讓 Container 在 mergeDir 讀取不到這個檔案。
結論
Docker Image 是一個靜態模板,由多個層(Layer)所組成,其內容包含應用程式運行所需的所有內容。它是唯讀的,無法修改的內容,如果需要改動,必須創建新的 Image。Docker 使用聯合檔案系統(UnionFS)將這些層合併,並且在 Container 運行時會基於這些層建立可寫的 UpperDir 來進行操作。當容器要進行檔案操作 (如新增、更新或刪除),變更都會寫入 UpperDir,不會改動到位在 LowerDir 中的原始檔案。
這篇拖了好久的複習文終於完成了,雖然花了點時間,但重新複習後,還是有感覺到對相關的內容,有更進一步的認識,雖然差點就無法完成了 !果然隔行如隔山(?),有些很基礎的觀念,自己原本都還不是那麼懂,總之還是順利打完收工啦!那就寫些打家囉!
