容器儲存管理對於資料持久化和應用設定至關重要。runroot 目錄儲存容器執行時的臨時資料,包含網路設定、PID 等資訊。檔案複製可透過 podman cp 命令實作,也能透過 podman mount 直接與 overlayfs 互動,操作容器檔案系統。podman commit 命令則能將容器變更儲存至新映像。更進一步,持久化儲存可使用 volumes 和 bind mounts。Bind mounts 將主機目錄掛載至容器,透過 -v--mount 選項設定,可指定唯讀等許可權。Volumes 由容器引擎管理,提供更佳的資料永續性,podman volume 命令可管理 volumes 生命週期。建立空 volume 時,會自動複製目標掛載點的內容,確保資料一致性。

容器資料儲存實作

最後一點值得簡要討論的是 runroot 目錄。在這個資料夾中,容器儲存程式會儲存容器執行過程中產生的所有臨時可寫內容。

如果我們檢查先前範例中啟動容器的執行主機上的資料夾,會發現有一個以容器 ID 命名的資料夾,其中包含多個已掛載到容器上的檔案,用於取代原始檔案:

# ls -l /run/containers/storage/overlay-containers/
bd0eef7cd50760dd52c24550be51535bc11559e52eea7d782a1fa6976524fa76/userdata
total 20
-rw-r--r--. 1 root root 6 16 oct 00.38 conmon.pid
-rw-r--r--. 1 root root 12 16 oct 00.38 hostname
-rw-r--r--. 1 root root 230 16 oct 00.38 hosts
-rw-r--r--. 1 root root 0 16 oct 00.38 oci-log
-rwx
---
---
. 1 root root 6 16 oct 00.38 pidfile
-rw-r--r--. 1 root root 34 16 oct 00.38 resolv.conf
drwxr-xr-x. 3 root root 60 16 oct 00.38 run

內容解密:

  • conmon.pid:記錄了 conmon 行程的 PID,用於監控容器。
  • hostnamehostsresolv.conf:網路組態檔案,用於設定容器的網路環境。
  • oci-log:記錄了容器的OCI日誌,用於除錯。
  • pidfile:記錄了容器主行程的PID。
  • run:目錄包含容器執行時需要的其他檔案。

從上述輸出可以看出,runroot 路徑下的容器資料夾包含了多個直接掛載到容器上的檔案,用於自定義容器。

總結來說,在前面的範例中,我們分析了容器映像的結構以及從該映像啟動新容器時發生的事情。背後的技術令人驚嘆,我們看到許多功能都與作業系統提供的隔離能力有關。在這裡,儲存提供了其他重要的功能,使容器成為我們現在所知的偉大技術。

在容器內外複製檔案

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

該檔案已被複製到主機的 /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/"

內容解密:

  • podman cp 命令用於在容器和主機之間複製檔案。
  • /etc/os-release 檔案包含了Linux發行版的相關資訊。

相反地,我們可以將檔案或資料夾從主機複製到正在執行的容器中:

$ podman cp /tmp/build_folder alpine_cp_test:/

這個範例將 /tmp/build_folder 資料夾及其所有內容複製到Alpine容器的根檔案系統下。然後,我們可以使用 podman execls 命令檢查複製命令的結果。

與overlayfs互動

還有另一種方法可以將檔案從容器複製到主機,即使用 podman mount 命令直接與合併的overlay互動。

要掛載一個正在執行的無根容器的檔案系統,我們首先需要執行 podman unshare 命令,該命令允許使用者在修改的使用者名稱空間中執行命令:

$ podman unshare

此命令會在組態了UID 0和GID 0的新使用者名稱空間中丟棄一個root shell。現在可以執行 podman mount 命令並取得掛載點的絕對路徑:

# cd $(podman mount alpine_cp_test)

上述命令使用shell擴充套件切換到 MergedDir 的路徑,該路徑合併了 LowerDirUpperDir 的內容,以提供不同層的統一檢視。從現在開始,可以將檔案複製到容器的根檔案系統或從中複製出來。

前面的範例根據無根容器,但相同的邏輯適用於有根容器。讓我們啟動一個有根的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 頁面複製到主機的 /tmp 目錄。請注意,sudo 將使用者許可權提升至root,因此複製的檔案將具有UID 0和GID 0的所有權。

內容解密:

  • 使用 podman unshare 可以在新的使用者名稱空間中執行命令。
  • 使用 podman mount 可以取得容器的掛載點路徑。

使用podman commit持久化變更

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

然而,如果我們需要在不開始新構建的情況下保留變更並產生新映像,podman commit 命令提供了一種方法,可以將對容器的變更持久化到新映像中。

提交(commit)的概念在Docker和OCI映像構建中非常重要。事實上,我們可以將Dockerfile的不同步驟視為在構建過程中應用的一系列提交。

範例:更新Nginx容器的預設index.html頁面

首先,建立一個新的 index.html 檔案:

$ echo "Hello World!" > /tmp/index.html

然後,啟動一個新的Nginx容器並將 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 將變更持久化到新映像中:

$ podman commit -p custom_nginx hello-world-nginx

上述命令透過有效地建立一個包含更新檔案和資料夾的新映像層來持久化變更。

內容解密:

  • podman commit 用於將容器的變更持久化到新映像。
  • -p 選項用於暫停容器,以確保資料一致性。

測試新映像:

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

在本文中,我們學習瞭如何將檔案複製到正在執行的容器或從中複製出來,以及如何透過生成新映像來提交變更。在下一節中,我們將介紹如何透過引入卷和繫結掛載的概念,將主機儲存附加到容器。

容器資料儲存實作:掛載主機儲存至容器

容器本質上是短暫的,根據不可變的映像檔執行。當容器當機或被刪除時,所有內部資料將會遺失。因此,我們需要一種方法將資料儲存在獨立的位置,並掛載到執行中的容器內,即使容器被刪除也能保留資料。

容器儲存管理的挑戰

除了資料永續性問題外,還有另一個重要的考量:金鑰和設定檔的管理。將金鑰或憑證硬編碼在映像檔中並不是一個好做法,因為這需要在憑證輪替或設定變更時重新建置整個映像檔。

使用掛載方式管理容器儲存

為瞭解決上述問題,OCI 規範支援使用 volumesbind mounts 來管理與容器相關的儲存。本章節將探討這兩種方式的工作原理以及如何將它們掛載到容器中。

管理與掛載 bind mounts 至容器

bind mounts 利用了 Linux 的原生功能,允許將檔案系統階層的一部分重新掛載到另一個位置。這意味著我們可以將主機上的目錄複製到容器內的另一個掛載點。

使用 -v|--volume 選項

-v 選項使用簡潔的單一欄位引數來定義主機目錄和容器內的掛載點,格式為 /HOST_DIR:/CONTAINER_DIR。例如,將主機上的 /host_files 目錄掛載到容器內的 /mnt

$ podman run -v /host_files:/mnt docker.io/library/nginx

也可以加入額外的引數來定義掛載行為,例如將主機目錄掛載為唯讀:

$ podman run -v /host_files:/mnt:ro docker.io/library/nginx

使用 --mount 選項

--mount 選項使用 key=value 語法來定義來源和目標,以及掛載型別和其他引數。這種方式支援多種掛載型別(bind mounts、volumes、tmpfs 等),格式為 type=TYPE,source=HOST_DIR,destination=CONTAINER_DIR。例如:

$ podman run --mount type=bind,src=/host_files,dst=/mnt docker.io/library/nginx

也可以加入額外的引數,例如將主機目錄掛載為唯讀:

$ podman run --mount type=bind,src=/host_files,dst=/mnt,ro=true docker.io/library/nginx

管理與掛載 volumes 至容器

volumes 是由容器引擎直接建立和管理的目錄,並掛載到容器內的特定掛載點。它們提供了一個很好的解決方案來持久化容器生成的資料。

使用 podman volume 命令管理 volumes

可以使用 podman volume 命令來列出、檢查、建立和刪除系統中的 volumes。例如,建立一個 volume 並自動掛載到 Nginx 容器的檔案根目錄:

$ podman run -d -p 8080:80 --name nginx_volume1 -v /usr/share/nginx/html docker.io/library/nginx

檢查容器組態,可以看到 volume 已被建立並掛載到容器內:

$ podman inspect nginx_volume1

輸出結果顯示了一個 Mounts 區段,其中包含了掛載在容器內的物件列表:

"Mounts": [
    {
        "Type": "volume",
        "Name": "2ed93716b7ad73706df5c6f56bda262920accec59e7b6642d36f938e936d36d9",
        "Source": "/home/packt/.local/share/containers/storage/volumes/2ed93716b7ad73706df5c6f56bda262920accec59e7b6642d36f938e936d36d9/_data",
        "Destination": "/usr/share/nginx/html",
        "Driver": "local",
        "Mode": "",
        "Options": [
            "nosuid",
            "nodev",
            "rbind"
        ],
        "RW": true,
        "Propagation": "rprivate"
    }
]

使用 podman volume ls 命令可以列出系統中的 volumes:

$ podman volume ls
DRIVER VOLUME NAME
local 2ed93716b7ad73706df5c6f56bda262920accec59e7b6642d36f938e936d36d9

檢視 volume 的來源路徑,可以看到預設檔案已經被複製到 volume 中:

$ ls -al /home/packt/.local/share/containers/storage/volumes/2ed93716b7ad73706df5c6f56bda262920accec59e7b6642d36f938e936d36d9/_data
total 16
drwxr-xr-x. 2 gbsalinetti gbsalinetti 4096 Sep 9 20:26 .
drwx
---
---
. 3 gbsalinetti gbsalinetti 4096 Oct 16 22:41 ..
-rw-r--r--. 1 gbsalinetti gbsalinetti 497 Sep 7 17:21 50x.html
-rw-r--r--. 1 gbsalinetti gbsalinetti 615 Sep 7 17:21 index.html

這證明瞭當建立一個空的 volume 時,它會被填充目標掛載點的內容。當容器停止時,volume 將被保留,並且可以在容器重新啟動或其他容器中使用。