Docker 基礎知識 - 使用繫結掛載(bind mounts)管理應用程式資料

技術譯民發表於2020-07-21

繫結掛載(bind mounts)在 Docker 的早期就已經出現了。與卷相比,繫結掛載的功能有限。當您使用繫結掛載時,主機上的檔案或目錄將掛載到容器中。檔案或目錄由其在主機上的完整或相對路徑引用。相反地,當您使用卷時,在主機上 Docker 的儲存目錄中建立一個新目錄,Docker 管理該目錄的內容。

該檔案或目錄不需要已經存在於 Docker 主機上。如果還不存在,則按需建立。繫結掛載的效能非常好,但它們依賴於主機的檔案系統,該檔案系統具有特定的可用目錄結構。如果您正在開發新的 Docker 應用程式,請考慮改用命名卷。不能使用 Docker CLI 命令直接管理繫結掛載。

docker-types-of-mounts-bind

選擇 -v 或者 --mount 標記

最初,-v--volume 標記用於獨立容器,--mount 標記用於叢集服務。但是,從 Docker 17.06 開始,您也可以將 --mount 用於獨立容器。通常,--mount 標記表達更加明確和冗長。最大的區別是 -v 語法將所有選項組合在一個欄位中,而 --mount 語法將選項分離。下面是每個標記的語法比較。

提示:新使用者推薦使用 --mount 語法,有經驗的使用者可能更熟悉 -v or --volume 語法,但是更鼓勵使用 --mount 語法,因為研究表明它更易於使用。

  • -v--volume: 由三個欄位組成,以冒號(:)分隔。欄位必須按照正確的順序排列,且每個欄位的含義不夠直觀明顯。
    • 對於繫結掛載(bind mounts), 第一個欄位是主機上檔案或目錄的路徑。
    • 第二個欄位是容器中檔案或目錄掛載的路徑。
    • 第三個欄位是可選的,是一個逗號分隔的選項列表,比如 roconsistentdelegatedcachedzZ。這些選項會在本文下面討論。
  • --mount:由多個鍵-值對組成,以逗號分隔,每個鍵-值對由一個 <key>=<value> 元組組成。--mount 語法比 -v--volume 更冗長,但是鍵的順序並不重要,標記的值也更容易理解。
    • 掛載的型別(type),可以是 bindvolume 或者 tmpfs。本主題討論繫結掛載(bind mounts),因此型別(type)始終為繫結掛載(bind)。
    • 掛載的源(source),對於繫結掛載,這是 Docker 守護程式主機上的檔案或目錄的路徑。可以用 source 或者 src 來指定。
    • 目標(destination),將容器中檔案或目錄掛載的路徑作為其值。可以用 destinationdst 或者 target 來指定。
    • readonly 選項(如果存在),則會將繫結掛載以只讀形式掛載到容器中。
    • bind-propagation 選項(如果存在),則更改繫結傳播。 可能的值是 rprivateprivatersharedsharedrslaveslave 之一。
    • consistency 選項(如果存在), 可能的值是 consistentdelegatedcached 之一。 這個設定只適用於 Docker Desktop for Mac,在其他平臺上被忽略。
    • --mount 標記不支援用於修改 selinux 標籤的 zZ選項。

下面的示例儘可能同時展示 --mount-v 兩種語法,並且先展示 --mount

-v--mount 行為之間的差異

由於 -v-volume 標記長期以來一直是 Docker 的一部分,它們的行為無法改變。這意味著 -v-mount 之間有一個不同的行為。

如果您使用 -v-volume 來繫結掛載 Docker 主機上還不存在的檔案或目錄,則 -v 將為您建立它。它總是作為目錄建立的。

如果使用 --mount 繫結掛載 Docker 主機上還不存在的檔案或目錄,Docker 不會自動為您建立它,而是產生一個錯誤。

啟動帶有繫結掛載的容器

考慮這樣一個情況:您有一個目錄 source,當您構建原始碼時,工件被儲存到另一個目錄 source/target/ 中。您希望工件在容器的 /app/ 目錄可用,並希望每次在開發主機上構建原始碼時,容器能訪問新的構建。使用以下命令將 target/ 目錄繫結掛載到容器的 /app/。在 source 目錄中執行命令。在 Linux 或 macOS 主機上,$(pwd) 子命令擴充套件到當前工作目錄。

下面的 --mount-v 示例會產生相同的結果。除非在執行第一個示例之後刪除了 devtest 容器,否則不能同時執行它們。

--mount

$ docker run -d \
  -it \
  --name devtest \
  --mount type=bind,source="$(pwd)"/target,target=/app \
  nginx:latest

-v

$ docker run -d \
  -it \
  --name devtest \
  -v "$(pwd)"/target:/app \
  nginx:latest

使用 docker inspect devtest 驗證繫結掛載是否被正確建立。檢視 Mounts 部分:

"Mounts": [
    {
        "Type": "bind",
        "Source": "/tmp/source/target",
        "Destination": "/app",
        "Mode": "",
        "RW": true,
        "Propagation": "rprivate"
    }
],

這表明掛載是一個 bind 掛載,它顯示了正確的源和目標,也顯示了掛載是可讀寫的,並且傳播設定為 rprivate

停止容器:

$ docker container stop devtest

$ docker container rm devtest

掛載到容器上的非空目錄

如果您將其繫結掛載到容器上的一個非空目錄中,則該目錄的現有內容會被繫結掛載覆蓋。這可能是有益的,例如當您想測試應用程式的新版本而不構建新映象時。然而,它也可能是令人驚訝的,這種行為不同於 docker volumes

這個例子被設計成極端的,僅僅使用主機上的 /tmp/ 目錄替換容器的 /usr/ 目錄的內容。在大多數情況下,這將導致容器無法正常工作。

--mount-v 示例有相同的結果。

--mount

$ docker run -d \
  -it \
  --name broken-container \
  --mount type=bind,source=/tmp,target=/usr \
  nginx:latest

docker: Error response from daemon: oci runtime error: container_linux.go:262:
starting container process caused "exec: \"nginx\": executable file not found in $PATH".

-v

$ docker run -d \
  -it \
  --name broken-container \
  -v /tmp:/usr \
  nginx:latest

docker: Error response from daemon: oci runtime error: container_linux.go:262:
starting container process caused "exec: \"nginx\": executable file not found in $PATH".

容器被建立,但沒有啟動。刪除它:

$ docker container rm broken-container

使用只讀繫結掛載

對於一些開發應用程式,容器需要寫入繫結掛載,因此更改將傳播回 Docker 主機。在其他時候,容器只需要讀訪問。

這個示例修改了上面的示例,但是通過在容器內的掛載點之後的選項列表(預設為空)中新增 ro,將目錄掛載為只讀繫結掛載。當有多個選項時,使用逗號分隔它們。

--mount-v 示例有相同的結果。

--mount

$ docker run -d \
  -it \
  --name devtest \
  --mount type=bind,source="$(pwd)"/target,target=/app,readonly \
  nginx:latest

-v

$ docker run -d \
  -it \
  --name devtest \
  -v "$(pwd)"/target:/app:ro \
  nginx:latest

使用 docker inspect devtest 驗證繫結掛載是否被正確建立。檢視 Mounts 部分:

"Mounts": [
    {
        "Type": "bind",
        "Source": "/tmp/source/target",
        "Destination": "/app",
        "Mode": "ro",
        "RW": false,
        "Propagation": "rprivate"
    }
],

停止容器:

$ docker container stop devtest

$ docker container rm devtest

配置繫結傳播

對於繫結掛載和卷,繫結傳播預設都是 rprivate 。只能為繫結掛載配置,而且只能在 Linux 主機上配置。繫結傳播是一個高階主題,許多使用者從不需要配置它。

繫結傳播是指在給定繫結掛載或命名卷中建立的掛載是否可以傳播到該掛載的副本。考慮一個掛載點 /mnt,它也掛載在 /tmp 上。傳播設定控制 /tmp/a 上的掛載是否也可以在 /mnt/a 上使用。每個傳播設定都有一個遞迴對應點。在遞迴的情況下,考慮一下 /tmp/a 也被掛載為 /foo。傳播設定控制 /mnt/a 和/或 /tmp/a 是否存在。

傳播設定 描述
shared 原始掛載的子掛載公開給副本掛載,副本掛載的子掛載也傳播給原始掛載。
slave 類似於共享(shared)掛載,但僅在一個方向上。如果原始掛載公開子掛載,副本掛載可以看到它。但是,如果副本掛載公開子掛載,則原始掛載無法看到它。
private 該掛載是私有的。原始掛載的子掛載不公開給副本掛載,副本掛載的子掛載也不公開給原始掛載。
rshared 與 shared 相同,但傳播也擴充套件到巢狀在任何原始或副本掛載點中的掛載點。
rslave 與 slave 相同,但傳播也擴充套件到巢狀在任何原始或副本掛載點中的掛載點。
rprivate 預設值。與 private 相同,這意味著原始或副本掛載點中的任何位置的掛載點都不會在任何方向傳播。

當你在掛載點上設定繫結傳播之前,主機檔案系統需要已經支援繫結傳播。

有關繫結傳播的更多資訊,請參見 Linux 核心共享子樹文件

下面的示例兩次將 target/ 目錄掛載到容器中,第二次掛載設定了 ro 選項和 rslave 繫結傳播選項。

--mount-v 示例有相同的結果。

--mount

$ docker run -d \
  -it \
  --name devtest \
  --mount type=bind,source="$(pwd)"/target,target=/app \
  --mount type=bind,source="$(pwd)"/target,target=/app2,readonly,bind-propagation=rslave \
  nginx:latest

-v

$ docker run -d \
  -it \
  --name devtest \
  -v "$(pwd)"/target:/app \
  -v "$(pwd)"/target:/app2:ro,rslave \
  nginx:latest

現在,如果您建立 /app/foo//app2/foo/ 也存在。

配置 selinux 標籤

如果使用 selinux ,則可以新增 zZ 選項,以修改掛載到容器中的主機檔案或目錄的 selinux 標籤。這會影響主機上的檔案或目錄,並且會產生超出 Docker 範圍之外的後果。

  • z 選項表示繫結掛載內容在多個容器之間共享。
  • Z 選項表示繫結掛載內容是私有的、非共享的。

使用這些選項時要格外小心。使用 Z 選項繫結掛載系統目錄(如 /home/usr )會導致您的主機無法操作,您可能需要重新手動標記主機檔案。

重要提示:當對服務使用繫結掛載時,selinux 標籤(:Z:Z) 以及 :ro 將被忽略。詳情請參閱 moby/moby #32579

這個示例設定了 z 選項來指定多個容器可以共享繫結掛載的內容:

無法使用 --mount 標記修改 selinux 標籤。

$ docker run -d \
  -it \
  --name devtest \
  -v "$(pwd)"/target:/app:z \
  nginx:latest

為 macOS 配置掛載一致性

Docker Desktop for Mac 使用 osxfs 將從 macOS 共享的目錄和檔案傳播到 Linux VM。這種傳播使執行在 Docker Desktop for Mac 上的 Docker 容器可以使用這些目錄和檔案。

預設情況下,這些共享是完全一致的,這意味著每次在 macOS 主機上或通過容器中的掛載發生寫操作時,更改都會重新整理到磁碟上,以便共享中的所有參與者都擁有完全一致的檢視。在某些情況下,完全一致性會嚴重影響效能。Docker 17.05及更高版本引入了一些選項,在每個掛載、每個容器的基礎上調整一致性設定。以下選項可供選擇:

  • consistentdefault: 完全一致性的預設設定,如上所述。
  • delegated: 容器執行時的掛載檢視是權威的。在容器中所做的更新,在主機上可見之前,可能會有延遲。
  • cached: macOS 主機的掛載檢視是權威的。在主機上所做的更新,在容器中可見之前,可能會有延遲。

這些選項在除 macOS 之外的所有主機作業系統上都被完全忽略。

--mount-v 示例有相同的結果。

--mount

$ docker run -d \
  -it \
  --name devtest \
  --mount type=bind,source="$(pwd)"/target,destination=/app,consistency=cached \
  nginx:latest

-v

$ docker run -d \
  -it \
  --name devtest \
  -v "$(pwd)"/target:/app:cached \
  nginx:latest

作者 : Docker 官網
譯者 : 技術譯民
出品 : 技術譯站
連結 : 英文原文

相關文章