容器技術的隔離性仰賴其獨特的儲存機制。OverlayFS 作為一種聯合檔案系統,扮演著容器儲存的根本,讓多個目錄如同層層堆積疊般合併成單一虛擬檔案系統。每個容器映象層都能以此方式疊加,最終構成容器可見的檔案系統。這些層級的實際儲存位置位於容器引擎的特定目錄,例如 overlay-images 存放映象元資料,overlay-layers 存放壓縮的映象層檔案,而 overlay 則存放解壓縮後的映象層。透過檢視 overlay 目錄,我們可以發現每個層級目錄中都包含 difflowermergedworklink 等檔案及目錄,它們分別代表 overlay 的上層變更、下層掛載點、overlay 掛載點、內部操作目錄以及層級的唯一識別字串。其中 overlay/l 目錄則存放指向各層 diff 目錄的符號連結,並參考 lower 檔案中的下層資訊,構建出完整的檔案系統層級結構。

理解容器儲存的運作方式,對於有效管理容器的資料至關重要。容器映象由多個唯讀層組成,這些層疊加形成容器的檔案系統。每個層都有一個 SHA-256 摘要值,用於驗證檔案完整性。透過檢查 overlay-layers/layers.json 檔案,並使用工具如 jqgrep,可以驗證下載的映象層是否完整無誤。容器的 root layer 通常不包含 lower 檔案,而是以一個名為 empty 的目錄作為 OverlayFS 的特性。當容器執行時,任何寫入操作都不會直接修改映象層,而是透過 OverlayFS 建立新的 diff 層來儲存變更。這個 diff 層是暫時性的,只在容器生命週期記憶體在。透過 podman mount 指令可以檢視容器的掛載點,其中 upperdir 引數指向 diff 層的目錄。

實際操作中,podman cp 指令可以方便地在容器內外複製檔案和資料夾。例如,可以將容器內的檔案複製到 host 的 /tmp 資料夾,或將 host 的資料夾複製到容器的根檔案系統下。此外,podman mount 指令允許直接與容器的檔案系統互動,透過進入 MergedDir 目錄,可以直接操作容器內的檔案。podman commit 指令則可以將容器的變更持久化到新的映象中。這對於更新設定檔、測試機密或保留修改非常有用。透過 commit,可以建立一個包含更新後檔案和資料夾的新映象層,實作變更的持久化。

容器儲存技術持續發展,volumes 和 bind mounts 等機制提供了更多彈性。/etc/containers/storage.conf 檔案則允許自訂儲存驅動程式和儲存目錄路徑。深入理解這些技術,將有助於更有效率地管理容器,並確保資料的永續性。

容器資料儲存的奧秘:玄貓解構 OverlayFS

身為一位熱愛容器技術的開發者,玄貓一直在探索如何更有效率地管理容器的資料。今天,讓我們一起深入研究容器儲存的核心——OverlayFS,看看它是如何組織和儲存容器的檔案系統層。

OverlayFS:容器檔案系統的根本

OverlayFS 是一種聯合檔案系統,它允許將多個目錄(層)合併成一個單一的虛擬檔案系統。在容器的世界裡,這意味著每個容器映象的層都可以疊加在一起,形成容器最終看到的檔案系統。

那麼,這些層究竟儲存在哪裡呢?在容器引擎的儲存目錄中,你會發現幾個關鍵的目錄:

  • overlay-images: 這裡存放的是容器映象的元資料。
  • overlay-layers: 這個目錄儲存了所有容器映象層的壓縮檔案。
  • overlay: 這裡是所有容器映象解壓縮後的層。

深入探索容器儲存目錄

讓我們先來看看 overlay-images 目錄的內容:

# ls -l overlay-images/
total 8
drwx------. 1 root root 630 15 oct 18.36 3b964f33a2bf66108d5333a541d376f63e0506aba8ddd4813f9d4e104271d9f0
-rw-------. 1 root root 1613 15 oct 18.36 images.json
-rw-r--r--. 1 root root 64 15 oct 18.36 images.lock

正如我們預期的,這個目錄包含了容器映象的元資料。在那個長長的 ID 目錄中,我們可以找到描述組成我們容器映象各層的 manifest 檔案。

接著,讓我們看看 overlay-layers 目錄:

# ls -l overlay-layers/
total 1168
-rw-------. 1 root root 2109 15 oct 18.35 0099baae6cd3ca0ced38d658d7871548b32bd0e42118b788d818b76131ec8e75.tar-split.gz
-rw-------. 1 root root 795206 15 oct 18.35 53498d66ad83a29fcd7c7bcf4abbcc0def4fc912772aa8a4483b51e232309aee.tar-split.gz
...
-rw-------. 1 root root 3716 15 oct 18.36 layers.json
-rw-r--r--. 1 root root 64 15 oct 19.06 layers.lock

這裡儲存了我們容器映象所有已下載層的壓縮檔案。那麼,它們被解壓縮到哪裡了呢?答案就在第三個資料夾,overlay

# ls -l overlay
total 0
drwx------. 1 root root 46 15 oct 18.35 0099baae6cd3ca0ced38d658d7871548b32bd0e42118b788d818b76131ec8e75
drwx------. 1 root root 46 15 oct 18.35 53498d66ad83a29fcd7c7bcf4abbcc0def4fc912772aa8a4483b51e232309aee
...
drwx------. 1 root root 416 15 oct 18.36 l

l 目錄的秘密

當我第一次看到 overlay 目錄下的 l 目錄時,也感到好奇。這個 l 目錄有什麼作用呢?

為了回答這個問題,我們需要檢查其中一個層目錄的內容。讓我們從列表中的第一個開始:

# ls -la overlay/0099baae6cd3ca0ced38d658d7871548b32bd0e42118b788d818b76131ec8e75/
total 8
drwx------. 1 root root 46 15 oct 18.35 .
drwx------. 1 root root 1026 15 oct 18.36 ..
dr-xr-xr-x. 1 root root 24 15 oct 18.35 diff
-rw-r--r--. 1 root root 26 15 oct 18.35 link
-rw-r--r--. 1 root root 86 15 oct 18.35 lower
drwx------. 1 root root 0 15 oct 18.35 merged
drwx------. 1 root root 0 15 oct 18.35 work

讓我們瞭解這些檔案和目錄的用途:

  • diff: 這個目錄代表 overlay 的上層,用於儲存對該層的任何變更。
  • lower: 這個檔案報告了所有下層掛載點,從最上層到最下層排序。
  • merged: 這個目錄是 overlay 掛載的地方。
  • work: 這個目錄用於內部操作。
  • link: 這個檔案包含該層的唯一字串。

現在,回到我們的問題,l 目錄的用途是什麼?

l 目錄下,有指向每個層的 diff 目錄的符號連結,這些符號連結參照 lower 檔案中的下層。讓我們檢查一下:

# ls -la overlay/l/
total 32
drwx------. 1 root root 416 15 oct 18.36 .
drwx------. 1 root root 1026 15 oct 18.36 ..
lrwxrwxrwx. 1 root root 72 15 oct 18.35 A4ZYMM4AK5NM6JYJA7EK2DLTGA -> ../74fa1495774e94d5cdb579f9bae4a16bd90616024a6f4b1ffd13344c367df1f6/diff
lrwxrwxrwx. 1 root root 72 15 oct 18.35 D2WVDYIWL6I77ZOIXRVQKCXNG2 -> ../ae314017e4c2de17a7fb007294521bbe8ac1eeb004ac9fb57d1f1f03090f78c9/diff
...
lrwxrwxrwx. 1 root root 72 15 oct 18.36 ZMKJYKM2VJEAYQHCI7SUQ2R3QW -> ../e5a13564f9c6e233da30a7fd86489234716cf80c317e52ff8261bf0cb34dc7b4/diff

為了再次確認我們學到的知識,讓我們找到容器映象的第一層,並檢查它是否有 lower 檔案。

讓我們檢查容器映象的 manifest 檔案:

# cat overlay-images/3b964f33a2bf66108d5333a541d376f63e0506aba8ddd4813f9d4e104271d9f0/manifest | head -15
{

玄貓解密:容器資料儲存的底層原理與實務

容器技術的核心在於其隔離性,而資料儲存則是隔離性中不可或缺的一環。今天,玄貓將帶領大家探討容器資料儲存的底層機制,並透過例項來驗證這些概念。

容器映象層的奧秘

容器映象由多個唯讀層組成,這些層疊加在一起,形成容器的檔案系統。讓我們從一個容器映象的 manifest 檔案開始,看看這些層是如何組織的:

{
  "schemaVersion": 2,
  "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
  "config": {
    "mediaType": "application/vnd.docker.container.image.v1+json",
    "size": 16212,
    "digest": "sha256:3b964f33a2bf66108d5333a541d376f63e0506aba8ddd4813f9d4e104271d9f0"
  },
  "layers": [
    {
      "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
      "size": 75867345,
      "digest": "sha256:b2cc5146c9c7855cb298ca8b77ecb153d37e3e5c69916ef423613a46a70c0503"
    }
  ]
}

這個 manifest 檔案描述了一個容器映象,其中 layers 陣列包含了構成映象的所有層。每個層都有一個唯一的 SHA-256 摘要值(digest),用於驗證檔案的完整性。

驗證映象層的完整性

為了確保我們下載的映象層沒有被篡改,需要比對壓縮檔案的 SHA-256 摘要值。以下是如何操作的:

# cat overlay-layers/layers.json | jq | grep -B3 -A10 "sha256:b2cc5"

這段程式碼會搜尋 overlay-layers/layers.json 檔案,找到包含 “sha256:b2cc5” 的行,並顯示前後的內容。透過比對摘要值,可以確認映象層的完整性。

overlay-layers/layers.json 檔案包含了映象層的元資料,例如 ID、建立時間、壓縮大小和摘要值等。這個檔案並未經過格式化,因此我們使用 jq 工具來使其更易於閱讀。

內容解密:

  1. cat overlay-layers/layers.json:這個指令將 overlay-layers/layers.json 檔案的內容輸出到標準輸出。
  2. | jq:這個管道符號將 cat 指令的輸出傳遞給 jq 工具,jq 會格式化 JSON 檔案,使其更易於閱讀。
  3. | grep -B3 -A10 "sha256:b2cc5":這個管道符號將 jq 的輸出傳遞給 grep 工具。grep 會搜尋包含 “sha256:b2cc5” 的行,-B3 表示顯示該行之前的三行,-A10 表示顯示該行之後的十行。

探索容器的 Root Layer

找到 Root Layer 的 ID 後,讓我們看看它的內容:

# ls -l overlay/53498d66ad83a29fcd7c7bcf4abbcc0def4fc912772aa8a4483b51e232309aee/
total 4
dr-xr-xr-x. 1 root root 158 15 oct 18.35 diff
drwx------. 1 root root 0 15 oct 18.35 empty
-rw-r--r--. 1 root root 26 15 oct 18.35 link
drwx------. 1 root root 0 15 oct 18.35 merged
drwx------. 1 root root 0 15 oct 18.35 work

由於這是容器映象的第一層,因此沒有 lower 檔案。取而代之的是一個名為 empty 的目錄,這是 OverlayFS 檔案系統的特性。

容器執行時的資料儲存

當容器執行時,任何寫入操作都不會直接修改映象層。相反,OverlayFS 會建立一個新的 diff 層,用於儲存這些變更。讓我們透過一個實際的例子來驗證這一點。

首先,執行一個容器:

# podman run -d quay.io/centos7/httpd-24-centos7
bd0eef7cd50760dd52c24550be51535bc11559e52eea7d782a1fa6976524fa76

這個指令會在背景執行一個根據 quay.io/centos7/httpd-24-centos7 映象的容器。

接著,進入容器並建立一個新檔案:

# podman exec -ti bd0eef7cd50760dd52c24550be51535bc11559e52eea7d782a1fa6976524fa76 /bin/bash
bash-4.2$ pwd
/opt/app-root/src
bash-4.2$ echo "this is my NOT persistent data" > tempfile.txt
bash-4.2$ ls
tempfile.txt

這個新建立的 tempfile.txt 檔案是暫時性的,只會在容器的生命週期記憶體在。

現在,讓我們找到 OverlayFS 建立的 diff 層:

bash-4.2$ mount | head
overlay on / type overlay (rw,relatime,context="system_u:object_r:container_file_t:s0:c300,c861",lowerdir=/var/lib/containers/storage/overlay/l/ZMKJYKM2VJEAYQHCI7SUQ2R3QW:/var/lib/containers/storage/overlay/l/G4KXMAOCE56TIB252ZMWEFRFHU:/var/lib/containers/storage/overlay/l/KNCK5EDUAQJDAIDWQ6TWDFQF5B:/var/lib/containers/storage/overlay/l/D2WVDYIWL6I77ZOIXRVQKCXNG2:/var/lib/containers/storage/overlay/l/LQUM7XDVWHIJRLIWALCFKSMJTT:/var/lib/containers/storage/overlay/l/A4ZYMM4AK5NM6JYJA7EK2DLTGA:/var/lib/containers/storage/overlay/l/V6OV3TLBBLTATIJDCTU6N72XQ5:/var/lib/containers/storage/overlay/l/JHHF5QA7YSKDSKRSCHNADBVKDS,upperdir=/var/lib/containers/storage/overlay/b71e4bea5380ca233bf6b0c7a1c276179b841e263ee293e987c6cc54af516f23/diff,workdir=/var/lib/containers/storage/overlay/b71e4bea5380ca233bf6b0c7a1c276179b841e263ee293e987c6cc54af516f23/work,metacopy=on)
proc on /proc type proc (rw,nosuid,nodev,noexec,relatime)
tmpfs on /dev type tmpfs (rw,nosuid,nodev,context="system_u:object_r:container_file_t:s0:c300,c861",size=65536k,mode=755,inode64)

透過 mount 指令,可以找到容器使用的掛載點。其中,upperdir 引數指向的就是 diff 層的目錄。

內容解密:

  1. mount:這個指令會顯示系統上所有已掛載的檔案系統。
  2. | head:這個管道符號將 mount 指令的輸出傳遞給 head 工具。head 會顯示輸出的前幾行,預設為前十行。

最後,讓我們檢視 diff 層的內容,確認新建立的檔案是否儲存在這裡:

# ls -la /var/lib/containers/storage/overlay/b71e4bea5380ca233bf6b0c7a1c276179b841e263ee293e987c6cc54af516f23/diff/opt/app-root/src/
total 12
drwxr-xr-x. 1 1001 root 58 16 oct 00.40 .
drwxr-xr-x. 1 1001 root 12 22 set 10.39 ..
-rw-------. 1 1001 root 81 16 oct 00.46 .bash_history
-rw-------. 1 1001 root 1024 16 oct 00.38 .rnd
-rw-r--r--. 1 1001 root 31 16 oct 00.39 tempfile.txt
# cat /var/lib/containers/storage/overlay/b71e4bea5380ca233bf6b0c7a1c276179b841e263ee293e987c6cc54af516f23/diff/opt/app-root/src/tempfile.txt
this is my NOT persistent data

正如我們所驗證的,資料儲存在主機作業系統上的一個暫時層中。一旦容器被移除,這個層也會被移除,資料也將遺失。

/etc/containers/storage.conf:容器儲存的設定檔

/etc/containers/storage.conf 檔案包含了容器儲存的設定。這個檔案允許你自訂儲存驅動程式,以及變更內部儲存目錄的預設路徑。

總結來說,容器的資料儲存是一個複雜但重要的議題。理解其底層機制有助於我們更好地管理容器,並確保資料的永續性。

探索容器儲存的奧秘:從 Runroot 到 OverlayFS

在容器的世界中,儲存扮演著至關重要的角色。除了容器映像本身,還有一個名為 runroot 的目錄,它用於存放容器產生的所有臨時可寫入內容。

當我們啟動一個容器時,容器儲存程式會在 host 上的 /run/containers/storage/overlay-containers/ 目錄下建立一個以容器 ID 命名的資料夾。這個資料夾中包含了各種檔案,它們會被掛載到容器中,以覆寫或修改原始檔案。

例如,您可能會看到 conmon.pidhostnamehostsoci-logpidfileresolv.conf 這些檔案。這些檔案允許我們自定義容器的行為,例如設定主機名稱、DNS 解析等。

容器技術的強大之處在於它利用了作業系統提供的隔離功能。儲存技術進一步增強了這種能力,使得容器成為我們今天所知的強大工具。

容器檔案搬運術:podman cp 的妙用

Podman 提供了一個方便的指令 podman cp,允許使用者在執行中的容器內外移動檔案和資料夾。它的用法非常簡單,讓我們來看幾個例子。

首先,啟動一個 Alpine 容器:

$ podman run -d --name alpine_cp_test alpine sleep 1000

現在,讓我們從容器中複製一個檔案。這裡選擇 /etc/os-release 檔案,它包含了關於發行版及其版本 ID 的資訊:

$ podman cp alpine_cp_test:/etc/os-release /tmp

這個指令會將檔案複製到 host 的 /tmp 資料夾,我們可以檢視其內容:

$ cat /tmp/os-release
NAME="Alpine Linux"
ID=alpine
VERSION_ID=3.14.2
PRETTY_NAME="Alpine Linux v3.14"
HOME_URL=https://alpinelinux.org/
BUG_REPORT_URL="https://bugs.alpinelinux.org/"

反過來,我們也可以將檔案或資料夾從 host 複製到執行中的容器:

$ podman cp /tmp/build_folder alpine_cp_test:/

這個例子會將 /tmp/build_folder 資料夾及其所有內容複製到 Alpine 容器的根檔案系統下。可以使用 podman exec 指令搭配 ls 工具來檢視複製的結果。

OverlayFS 的進階玩法:直接與容器檔案系統互動

除了 podman cp,還有另一種方法可以從容器複製檔案到 host,那就是使用 podman mount 指令,直接與合併的 overlays 互動。

要掛載一個以 rootless 模式執行的容器的檔案系統,首先需要執行 podman unshare 指令。這個指令允許使用者在修改後的 user namespace 中執行指令:

$ podman unshare

這個指令會在一個組態了 UID 0 和 GID 0 的新 user namespace 中開啟一個 root shell。現在可以執行 podman mount 指令並取得掛載點的絕對路徑:

# cd $(podman mount alpine_cp_test)

這個指令利用 shell 擴充,切換到 MergedDir 的路徑。正如其名稱所示,MergedDir 合併了 LowerDirUpperDir 的內容,以提供不同層的統一檢視。從現在開始,就可以在容器的根檔案系統中複製檔案了。

先前的例子都是根據 rootless 容器,但相同的邏輯也適用於 rootful 容器。讓我們啟動一個 rootful 的 Nginx 容器:

$ sudo podman run -d \
--name rootful_nginx docker.io/library/nginx

要複製檔案進出容器,需要加上 sudo 指令:

$ sudo podman cp \
rootful_nginx:/usr/share/nginx/html/index.html /tmp

這個指令會將預設的 index.html 頁面複製到 host 的 /tmp 目錄。請注意,sudo 會將使用者許可權提升到 root,因此複製的檔案將具有 UID 0 和 GID 0 的所有權。

從容器複製檔案的實務對於疑難排解特別有用。將檔案複製到執行中的容器則可用於更新和測試機密或設定檔。在這種情況下,我們可以選擇持久化這些變更,如下一節所述。

podman commit:將變更永久儲存

先前的例子並不是永久自定義執行中容器的方法,因為容器的不可變性意味著永續性的修改應該透過映像重建來完成。

然而,如果我們需要保留變更並產生一個新的映像,而不想啟動新的建置,podman commit 指令提供了一種將容器的變更持久化到新映像中的方法。

commit 概念在 Docker 和 OCI 映像建置中至關重要。實際上,我們可以將 Dockerfile 的不同步驟看作是在建置過程中應用的一系列 commits。

以下範例展示瞭如何持久化複製到執行中容器的檔案,並產生一個新的映像。假設我們想要更新 Nginx 容器的預設 index.html 頁面:

$ echo "Hello World!" > /tmp/index.html
$ podman run --name custom_nginx -d -p \
8080:80 docker.io/library/nginx
$ podman cp /tmp/index.html \
custom_nginx:/usr/share/nginx/html/

讓我們測試一下變更:

$ curl localhost:8080
Hello World!

現在,我們想要從執行中的容器開始,使用 podman commit 將變更後的 index.html 檔案持久化到一個新的映像中:

$ podman commit -p custom_nginx hello-world-nginx

這個指令會透過建立一個包含更新後的檔案和資料夾的新映像層來持久化變更。

現在可以安全地停止並移除先前的容器,然後測試新的自定義映像:

$ podman stop custom_nginx && podman rm custom_nginx

讓我們測試新的自定義映像並檢視變更後的 index.html 檔案:

$ podman run -d -p 8080:80 --name hello_world \
localhost/hello-world-nginx
$ curl localhost:8080
Hello World!

在本文中,玄貓學習瞭如何將檔案複製到執行中的容器以及如何從容器複製檔案,以及如何透過產生新的映像來即時 commit 變更。

容器儲存技術的下一步

容器儲存技術不斷演進,為開發者提供了越來越多的靈活性和控制力。從簡單的檔案複製到複雜的 overlayFS 操作,理解這些技術的底層原理對於有效地使用容器至關重要。