Skip to main content

透過 Docker Image 理解 OverlayFS

在日常的開發或是部署當中,我們很常使用 Docker 來免去環境設定還有跨平台的困擾,那你有沒有好奇過:如果我們的電腦裡面有很多 Docker Image,會不會佔用很多電腦的硬碟空間嗎?Docker 是如何儲存這些 Image 的?

🐳 Dockerfile 與 Image Layer 實現原理

日常開發中,經常會看到這樣的 Dockerfile:

FROM ubuntu:20.04
RUN apt-get update && apt-get install -y nginx
COPY ./app /app
CMD ["nginx", "-g", "daemon off;"]

當我們執行 docker build 的時候,每個指令都會產生一個新的 layer:

[+] Building 33.8s (8/8) FINISHED
=> [internal] load build definition from Dockerfile
=> => transferring dockerfile: 172B
=> [internal] load metadata for docker.io/library/ubuntu:20.04
=> [1/3] FROM docker.io/library/ubuntu:20.04
=> [2/3] RUN apt-get update && apt-get install -y nginx
=> [3/3] COPY ./app /var/www/html
=> exporting to image
=> => writing image sha256:5d8f96da221d162a0937f2241bd9236e7dfc90872cda77498c467369fc885b6

可以使用 docker history 來查看這些 layer 的組成:

IMAGE          CREATED          CREATED BY                                    SIZE      COMMENT
5d8f96da221d 47 seconds ago CMD ["nginx" "-g" "daemon off;"] 0B buildkit.dockerfile.v0
<missing> 47 seconds ago COPY ./app /var/www/html # buildkit 27B buildkit.dockerfile.v0
<missing> 47 seconds ago RUN /bin/sh -c apt-get update && apt-get... 114MB buildkit.dockerfile.v0
<missing> 5 weeks ago /bin/sh -c #(nop) CMD ["bash"] 0B
<missing> 5 weeks ago /bin/sh -c #(nop) ADD file:74861... 72.8MB

你有沒有好奇過,為什麼每個指令都會產生一個新的層?這些層是如何組合在一起的?更重要的是,為什麼我們在容器裡修改檔案時,不會影響到原本的映像檔? 每個 Docker Image 都是由多個層堆疊而成的。以上面的例子來說:

  • 最底層是 Ubuntu 20.04 的基礎映像檔(72.8MB),包含了基本的作業系統檔案
  • 接著是執行 apt-get install nginx 產生的層(114MB),包含了 nginx 相關的檔案
  • 然後是 COPY 指令產生的層(27B),包含了我們的應用程式檔案
  • 最後是 CMD 指令產生的層(0B),只包含了啟動指令的設定

這種分層設計帶來了很多好處:

  • 空間利用率:多個容器可以共享相同的基礎層。想像一下,如果有 10 個專案都是基於 Ubuntu 20.04,這些專案的容器就可以共用同一個基礎映像層,而不是每個專案都儲存一份完整的作業系統檔案。
  • 建構效率:Docker 會快取每一層。如果只是修改了應用程式的程式碼,重新建構時只需要從 COPY 指令開始,前面的層都可以直接複用。這大幅減少了開發過程中的等待時間。

當我們執行 docker run 啟動容器時,Docker 會在最上層添加一個可寫入的容器層。這就是為什麼我們可以在容器內新增或修改檔案,但這些變更不會影響到原本的映像檔。讓我們實際試試:

docker run -it ubuntu:20.04 bash
root@container:/# touch newfile
root@container:/# echo "hello" > /etc/motd

這些修改只會寫入到容器層,而且當容器被刪除時,這些變更也會跟著消失。如果你希望保存這些變更,就需要使用 docker commit 將容器的當前狀態儲存為新的映像檔。

但這樣的分層機制是如何實現的呢?這就要談到 Linux 核心中的 OverlayFS 了。它不只是一個簡單的檔案系統,而是專門設計用來處理多層檔案系統疊加的解決方案。

🐳 從 UnionFS 到 OverlayFS 的演進

🐳 OverlayFS 核心機制實作解析

🐳 OverlayFS 在實務上的其他應用

🐳 Resource