在容器化環境中,有效管理儲存資源至關重要。Podman 提供多種儲存管理機制,讓使用者能靈活地控制容器資料的持久化和分享。除了基本的命名磁碟區和繫結掛載,更可以利用本地磁碟區驅動程式整合 NFS 共用,或在映像建置階段預先定義磁碟區。然而,在 SELinux 啟用的系統上,必須注意檔案系統標籤的設定,才能確保容器正確存取掛載的資源。透過 :z:Z 選項,可以有效管理 SELinux 標籤,避免許可權問題。此外,理解 MCS(多類別安全)機制,能更精細地控制容器間的資源隔離,提升安全性。除了持久化儲存,Podman 也支援 tmpfs 等非持久化儲存,以及直接掛載 OCI 映像作為容器內的檔案系統,滿足不同應用場景的需求。

容器資料儲存實作

前面的範例展示了具有產生 UID 的磁碟區,但也可以選擇附加磁碟區的名稱,如下例所示:

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

在前面的範例中,Podman 建立了一個名為 nginx_vol 的新磁碟區,並將其儲存在預設的磁碟區目錄下。當建立命名磁碟區時,Podman 不需要產生 UID。

預設的磁碟區目錄對於無根和有根容器具有不同的路徑:

  • 對於無根容器,預設的磁碟區儲存路徑是 <USER_HOME>/.local/share/containers/storage/volumes
  • 對於有根容器,預設的磁碟區儲存路徑是 /var/lib/containers/storage/volumes

在這些路徑中建立的磁碟區在容器銷毀後仍然存在,可以被其他容器重複使用。

要手動刪除磁碟區,請使用 podman volume rm 命令:

$ podman volume rm nginx_vol

當處理多個磁碟區時,podman volume prune 命令會刪除所有未使用的磁碟區。以下範例刪除了使用者預設磁碟區儲存中的所有磁碟區(無根容器使用的那一個):

$ podman volume prune

下面的範例展示瞭如何使用 sudo 字首刪除有根容器使用的磁碟區:

$ sudo podman volume prune

重點提示

不要忘記監控主機上積累的磁碟區,因為它們佔用可以回收的磁碟空間,並定期清理未使用的磁碟區,以避免雜亂無章的主機儲存。

將主機儲存附加到容器

使用者還可以在執行容器之前預先建立和填充磁碟區。下面的範例使用 podman create volume 命令建立掛載到 Nginx 檔案根目錄的磁碟區,然後用測試用的 index.html 檔案填充它:

$ podman volume create custom_nginx
$ echo "Hello World!" >> $(podman volume inspect custom_nginx --format "{{ .Mountpoint }}")/index.html

現在我們可以使用預先填充的磁碟區執行新的 Nginx 容器:

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

HTTP 測試顯示更新後的內容:

$ curl localhost:8080
Hello World!

這次,最初不為空的磁碟區用其內容遮蔽了容器目標目錄。

使用 –mount 選項掛載磁碟區

與繫結掛載一樣,我們可以在 -v|--volume--mount 選項之間自由選擇。下面的範例使用 --mount 旗標執行 Nginx 容器:

$ podman run -d -p 8080:80 --name nginx_volume4 --mount type=volume,src=custom_nginx,dst=/usr/share/nginx/html docker.io/library/nginx

雖然 -v|--volume 選項緊湊且廣泛採用,但 --mount 選項的優點是更清晰和更具表達力的語法,以及對掛載型別的明確宣告。

磁碟區驅動程式

前面的磁碟區範例都根據相同的本地磁碟區驅動程式,用於管理主機本地檔案系統中的磁碟區。可以透過在 /usr/share/containers/containers.conf 檔案中的 [engine.volume_plugins] 部分傳遞外掛程式名稱後跟檔案或 socket 路徑來組態額外的磁碟區驅動程式。

本地磁碟區驅動程式範例

本地磁碟區驅動程式還可以用於掛載執行容器的主機中的 NFS 共用。然而,無根容器無法實作此結果。下面的範例展示瞭如何建立由 NFS 共用支援的磁碟區,並將其掛載到 MongoDB 容器的 /data/db 目錄中:

$ sudo podman volume create --driver local --opt type=nfs --opt o=addr=nfs-host.example.com,rw,context="system_u:object_r:container_file_t:s0" --opt device=:/opt/nfs-export nfs-volume
$ sudo podman run -d -v nfs-volume:/data/db docker.io/library/mongo

上述範例的前提是 NFS 伺服器的初步組態,該伺服器應該可以被執行容器的主機存取。

在建置中定義磁碟區

可以在映像建置過程中預先定義磁碟區。這允許映像維護者定義哪些容器目錄將自動附加到磁碟區。要了解這個概念,讓我們檢查這個最小的 Dockerfile:

FROM docker.io/library/nginx:latest
VOLUME /usr/share/nginx/html

docker.io/library/nginx 映像的唯一更改是 VOLUME 指令,它定義了應該在主機中作為匿名磁碟區外部掛載的目錄。這只是元資料,磁碟區只在從該映像啟動容器時才會在執行時建立。

如果我們建置映像並根據範例 Dockerfile 執行容器,我們可以看到自動建立的匿名磁碟區:

$ podman build -t my_nginx .
$ podman run -d --name volumes_from_build my_nginx
$ podman inspect volumes_from_build --format "{{ .Mounts }}"
[{volume 4d6ac7edcb4f01add205523b7733d61ae4a5772786eacca68e4972b20fd1180c /home/packt/.local/share/containers/storage/volumes/4d6ac7edcb4f01add205523b7733d61ae4a5772786eacca68e4972b20fd1180c/_data /usr/share/nginx/html local [nodev exec nosuid rbind] true rprivate}]

程式碼解析:

  1. podman build -t my_nginx .:使用當前目錄中的 Dockerfile 建置一個名為 my_nginx 的映像。
    • 這條命令告訴 Podman 在當前目錄下尋找 Dockerfile 並根據其指示建置映像。
  2. podman run -d --name volumes_from_build my_nginx:從 my_nginx 映像啟動一個名為 volumes_from_build 的容器,並在後台執行。
    • 這條命令根據剛才建置的映像啟動一個新的容器,並賦予它一個名稱。
  3. podman inspect volumes_from_build --format "{{ .Mounts }}":檢查名為 volumes_from_build 的容器的詳細資訊,並格式化輸出以顯示掛載資訊。
    • 這條命令用於檢視容器的組態細節,特別是掛載的磁碟區資訊。

此圖表描述了建置和執行容器的流程:

@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

title Podman容器儲存管理與SELinux整合

package "Kubernetes Cluster" {
    package "Control Plane" {
        component [API Server] as api
        component [Controller Manager] as cm
        component [Scheduler] as sched
        database [etcd] as etcd
    }

    package "Worker Nodes" {
        component [Kubelet] as kubelet
        component [Kube-proxy] as proxy
        package "Pods" {
            component [Container 1] as c1
            component [Container 2] as c2
        }
    }
}

api --> etcd : 儲存狀態
api --> cm : 控制迴圈
api --> sched : 調度決策
api --> kubelet : 指令下達
kubelet --> c1
kubelet --> c2
proxy --> c1 : 網路代理
proxy --> c2

note right of api
  核心 API 入口
  所有操作經由此處
end note

@enduml

此圖示展示了從 Dockerfile 到檢視容器掛載資訊的整個流程。

容器儲存實作:主機儲存掛載與 SELinux 相關考量

容器技術的核心優勢之一是其對儲存的靈活性與管理能力。Podman 作為一個高效的容器管理工具,提供了多種方式來掛載主機儲存至容器中,並有效地管理這些儲存資源。在本章中,我們將探討如何將主機儲存掛載到容器中,以及在 SELinux 啟用的環境下,如何正確地組態和管理掛載的資源。

自動化磁碟區建立與掛載

在未明確指定磁碟區建立選項的情況下,Podman 會自動建立並掛載容器所需的磁碟區。這種行為在需要持久化資料的容器(如資料函式庫)中非常常見。例如,docker.io/library/mongo 映象預設會建立兩個磁碟區,分別掛載在 /data/configdb/data/db。這種自動化組態簡化了容器的佈署和管理流程。

程式碼範例:啟動 MongoDB 容器並掛載預設磁碟區為 tmpfs

$ podman run -d --image-volume tmpfs docker.io/library/mongo

內容解密:

  • podman run:啟動一個新的容器。
  • -d:以分離模式執行容器。
  • --image-volume tmpfs:將映象中定義的磁碟區掛載為 tmpfs,tmpfs 是一種根據記憶體的暫存檔案系統。
  • docker.io/library/mongo:指定的 MongoDB 映象名稱。

跨容器掛載磁碟區

Podman 允許容器之間分享資料,透過 --volumes-from 選項可以實作跨容器掛載磁碟區。這使得多個容器可以存取相同的資料,從而實作資料分享。

程式碼範例:啟動 MongoDB 容器並將其磁碟區掛載到 Fedora 容器

$ podman run -d --name mongodb01 docker.io/library/mongo
$ podman run -it --volumes-from=mongodb01 docker.io/library/fedora

內容解密:

  • 第一個 podman run 命令啟動了一個名為 mongodb01 的 MongoDB 容器。
  • 第二個 podman run 命令啟動了一個 Fedora 容器,並透過 --volumes-from=mongodb01mongodb01 容器的磁碟區掛載到 Fedora 容器中。

SELinux 對掛載資源的影響

在啟用 SELinux 的系統上,檔案和目錄會被標記上特定的上下文型別,以控制對這些資源的存取。當容器嘗試掛載主機上的資源時,如果資源的標籤不正確,容器內的程式可能無法存取這些資源。

程式碼範例:嘗試掛載自定義檔案到 Nginx 容器

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

內容解密:

  • 首先建立了一個自定義的檔案目錄 ~/custom_docroot 並在其下建立了一個 index.html 檔案。
  • 然後嘗試將該目錄掛載到 Nginx 容器的 /usr/share/nginx/html 目錄下。
  • 由於 SELinux 的限制,Nginx 程式無法存取掛載的 index.html 檔案,導致傳回 403 Forbidden 錯誤。

處理 SELinux 相關問題

為瞭解決 SELinux 引發的存取問題,可以透過兩種方式:一是將 SELinux 切換到 permissive 模式,二是重新標記掛載的資源。後者是更為推薦的做法,可以透過 Podman 的 :z:Z 字尾來實作。

程式碼範例:使用 :z 字尾重新標記掛載資源

$ podman run -d \
    --name custom_nginx \
    -p 8080:80 \
    -v ~/custom_docroot:/usr/share/nginx/html:z \
    docker.io/library/nginx

內容解密:

  • 使用 :z 字尾告訴 Podman 對掛載的資源進行重新標記,以允許多個容器讀寫該資源。
  • 這樣,Nginx 程式就能正確存取 index.html 檔案,並傳回預期的結果。

容器儲存實作:深入理解 SELinux 與多類別安全(MCS)

在容器化技術中,儲存的安全性是至關重要的。SELinux(Security-Enhanced Linux)為容器提供了強制存取控制(MAC),確保容器間的隔離與安全性。本章節將探討 SELinux 的運作機制、MCS(Multi-Category Security)的應用,以及如何有效地管理容器的儲存。

SELinux 與 MCS 類別標籤

當容器掛載主機儲存時,SELinux 會為資源分配特定的標籤,例如 system_u:object_r:container_file_t:s0。其中的 s0 代表多層安全(MLS)敏感度級別,這意味著具有相同敏感度級別的程式可以對該資源進行讀寫操作。然而,這種機制可能導致安全問題,因為具有相同敏感度級別的惡意容器可能會竊取或覆寫其他容器的資料。

為瞭解決這一問題,SELinux 引入了多類別安全(MCS)。MCS 為資源分配額外的類別標籤,只有具有相同類別標籤的程式才能存取這些資源。當容器啟動時,其內部的程式會被標記為 MCS 類別,格式為 cXXX,cYYY,其中 XXXYYY 是隨機分配的整數。

使用 :Z 選項實作 MCS 標籤

Podman 允許使用者透過在掛載資源時新增 :Z 字尾來自動應用 MCS 類別標籤。以下範例展示瞭如何使用 :Z 選項啟動 Nginx 容器,並掛載自定義的檔案目錄:

$ podman run -d \
--name custom_nginx \
-p 8080:80 \
-v ~/custom_docroot:/usr/share/nginx/html:Z \
docker.io/library/nginx

執行上述命令後,掛載的資料夾將被重新標記為包含 MCS 類別:

$ ls -alZ ~/custom_docroot
total 20
drwxrwxr-x. 2 packt packt system_u:object_r:container_file_t:s0:c16,c898 4096 Oct 16 15:53 .
drwxrwxr-x. 74 packt packt unconfined_u:object_r:user_home_dir_t:s0 12288 Oct 16 21:12 ..
-rw-rw-r--. 1 packt packt system_u:object_r:container_file_t:s0:c16,c898 13 Oct 16 15:53 index.html

內容解密:

  1. podman run -d: 以分離模式執行容器,使其在背景執行。
  2. --name custom_nginx: 為容器指定名稱 custom_nginx,方便後續管理和操作。
  3. -p 8080:80: 將主機的 8080 連線埠對映到容器的 80 連線埠,使得外部可以透過主機的 8080 連線埠存取 Nginx 服務。
  4. -v ~/custom_docroot:/usr/share/nginx/html:Z: 將主機上的 ~/custom_docroot 目錄掛載到容器的 /usr/share/nginx/html 目錄,並使用 :Z 字尾確保 SELinux 正確標記該目錄及其內容,以允許容器內的程式存取該目錄。
  5. docker.io/library/nginx: 指定要執行的容器映像,這裡使用的是官方的 Nginx 映像。

多次掛載與 MCS 類別變更

如果我們再次使用 :Z 字尾掛載同一個資源到另一個容器,MCS 類別標籤將被重新分配,從而導致先前容器的存取許可權失效。以下範例展示了這一行為:

$ podman run -d \
--name custom_nginx2 \
-p 8081:80 \
-v ~/custom_docroot:/usr/share/nginx/html:Z \
docker.io/library/nginx

此時,若存取第一個容器的服務,將傳回 403 Forbidden 錯誤,因為其原有的 MCS 類別標籤已不再匹配新分配的標籤。

其他儲存型別:tmpfs、image 與 devpts

除了 bind mount 和 volume,Podman 還支援其他儲存型別,如 tmpfs、image 和 devpts。

tmpfs:非永續性儲存

tmpfs 為根據虛擬記憶體的檔案系統,適合用於需要快速讀寫但不需要持久化的場景,如快取。以下範例展示瞭如何使用 --mount--tmpfs 選項掛載 tmpfs:

$ podman run -d -p 8080:80 \
--name tmpfs_example1 \
--mount type=tmpfs,tmpfs-size=512M,destination=/tmp \
docker.io/library/httpd

image:掛載 OCI 映像

OCI 映像可以用於在容器內掛載額外的檔案系統,用於除錯或執行特定工具。以下範例展示瞭如何在 Alpine 容器中掛載 BusyBox 映像:

$ podman run -it \
--mount type=image,src=docker.io/library/busybox,dst=/mnt,rw=true \
alpine

內容解密:

  1. --mount type=image:指定掛載型別為映像。
  2. src=docker.io/library/busybox:指定要掛載的映像來源。
  3. dst=/mnt:將映像掛載到容器的 /mnt 目錄。
  4. rw=true:允許對掛載的映像進行讀寫操作,但實際寫入的是 overlay 上層,而非原始映像。