磁碟區

磁碟區是保存 Docker 容器產生和使用之資料的首選機制。雖然繫結掛載取決於主機的目錄結構和作業系統,但磁碟區則完全由 Docker 管理。磁碟區比繫結掛載有幾個優點:

  • 磁碟區比繫結掛載更容易備份或遷移。
  • 您可以使用 Docker CLI 命令或 Docker API 管理磁碟區。
  • 磁碟區適用於 Linux 和 Windows 容器。
  • 磁碟區可以在多個容器之間更安全地共享。
  • 磁碟區驅動程式可讓您將磁碟區儲存在遠端主機或雲端提供商上、加密磁碟區的內容或新增其他功能。
  • 新的磁碟區可以由容器預先填入其內容。
  • Docker Desktop 上的磁碟區比 Mac 和 Windows 主機的繫結掛載效能高得多。

此外,磁碟區通常比在容器的可寫入層中保存資料更好,因為磁碟區不會增加使用它的容器的大小,而且磁碟區的內容存在於特定容器的生命週期之外。

Volumes on the Docker host

如果您的容器產生非永久狀態資料,請考慮使用 tmpfs 掛載,以避免將資料永久儲存在任何地方,並透過避免寫入容器的可寫入層來提高容器的效能。

磁碟區使用 rprivate 繫結傳播,且繫結傳播無法針對磁碟區進行設定。

選擇 -v 或 --mount 旗標

一般來說,--mount 更明確且更詳細。最大的差異在於 -v 語法將所有選項組合在一個欄位中,而 --mount 語法則將它們分開。以下是每個旗標語法的比較。

如果您需要指定磁碟區驅動程式選項,則必須使用 --mount

  • -v--volume:由三個欄位組成,以冒號 (:) 分隔。這些欄位必須按正確的順序排列,而且每個欄位的含義並不立即可見。

    • 如果是具名磁碟區,則第一個欄位是磁碟區的名稱,並且在指定的zh-TW: 主機上是唯一的。如果是匿名磁碟區,則會省略第一個欄位。
    • 第二個欄位是檔案或目錄在容器中掛載的路徑。
    • 第三個欄位是選擇性的,它是由逗號分隔的選項清單,例如 ro。這些選項將在下方討論。
  • --mount:由多個鍵值對組成,以逗號分隔,每個鍵值對都包含一個 <key>=<value> 元組。 --mount 語法比 -v--volume 更詳細,但鍵的順序並不重要,而且旗標的值更容易理解。

    • 掛載的 type,可以是 bindvolumetmpfs。本主題討論磁碟區,因此類型始終是 volume
    • 掛載的 source。如果是具名磁碟區,則這是磁碟區的名稱。如果是匿名磁碟區,則會省略此欄位。可以指定為 sourcesrc
    • destination 的值是檔案或目錄在容器中掛載的路徑。可以指定為 destinationdsttarget
    • volume-subpath 選項採用磁碟區中子目錄的路徑來掛載到容器中。子目錄必須在將磁碟區掛載到容器之前存在於磁碟區中。請參閱掛載磁碟區子目錄
    • 如果存在 readonly 選項,則會導致繫結掛載以唯讀方式掛載到容器中。可以指定為 readonlyro
    • 可以指定多次的 volume-opt 選項,採用由選項名稱及其值組成的鍵值對。

警告

如果您的磁碟區驅動程式接受逗號分隔清單作為選項,則您必須從外部 CSV 解析器逸出該值。要逸出 volume-opt,請用雙引號 (") 將其括起來,並用單引號 (') 將整個掛載參數括起來。

例如,local 驅動程式接受以逗號分隔的掛載選項列表作為 o 參數。這個例子展示了正確的列表跳脫方式。

$ docker service create \
 --mount 'type=volume,src=<VOLUME-NAME>,dst=<CONTAINER-PATH>,volume-driver=local,volume-opt=type=nfs,volume-opt=device=<nfs-server>:<nfs-path>,"volume-opt=o=addr=<nfs-address>,vers=4,soft,timeo=180,bg,tcp,rw"'
 --name myservice \
 <IMAGE>

以下範例盡可能同時顯示 --mount-v 語法,並以 --mount 優先。

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

與繫結掛載相反,所有磁碟區選項都適用於 --mount-v 旗標。

與服務搭配使用的磁碟區僅支援 --mount

建立和管理磁碟區

與繫結掛載不同,您可以在任何容器範圍之外建立和管理磁碟區。

建立磁碟區

$ docker volume create my-vol

列出磁碟區

$ docker volume ls

local               my-vol

檢查磁碟區

$ docker volume inspect my-vol
[
    {
        "Driver": "local",
        "Labels": {},
        "Mountpoint": "/var/lib/docker/volumes/my-vol/_data",
        "Name": "my-vol",
        "Options": {},
        "Scope": "local"
    }
]

移除磁碟區

$ docker volume rm my-vol

使用磁碟區啟動容器

如果您使用尚不存在的磁碟區啟動容器,Docker 會為您建立磁碟區。以下範例將磁碟區 myvol2 掛載到容器中的 /app/

以下 -v--mount 範例產生相同的結果。除非您在執行第一個範例後移除 devtest 容器和 myvol2 磁碟區,否則您無法同時執行它們。


$ docker run -d \
  --name devtest \
  --mount source=myvol2,target=/app \
  nginx:latest
$ docker run -d \
  --name devtest \
  -v myvol2:/app \
  nginx:latest

使用 docker inspect devtest 來驗證 Docker 是否已建立磁碟區並正確掛載。尋找 Mounts 區段

"Mounts": [
    {
        "Type": "volume",
        "Name": "myvol2",
        "Source": "/var/lib/docker/volumes/myvol2/_data",
        "Destination": "/app",
        "Driver": "local",
        "Mode": "",
        "RW": true,
        "Propagation": ""
    }
],

這顯示掛載是一個磁碟區,顯示正確的來源和目標,並且掛載是可讀寫的。

停止容器並移除磁碟區。請注意,移除磁碟區是一個單獨的步驟。

$ docker container stop devtest

$ docker container rm devtest

$ docker volume rm myvol2

將磁碟區與 Docker Compose 搭配使用

以下範例顯示具有磁碟區的單個 Docker Compose 服務

services:
  frontend:
    image: node:lts
    volumes:
      - myapp:/home/node/app
volumes:
  myapp:

第一次執行 docker compose up 會建立一個磁碟區。當您隨後執行命令時,Docker 會重複使用相同的磁碟區。

您可以使用 docker volume create 在 Compose 之外直接建立磁碟區,然後在 compose.yaml 中參考它,如下所示

services:
  frontend:
    image: node:lts
    volumes:
      - myapp:/home/node/app
volumes:
  myapp:
    external: true

有關將磁碟區與 Compose 搭配使用的更多資訊,請參閱 Compose 規格中的磁碟區區段。

使用磁碟區啟動服務

當您啟動服務並定義磁碟區時,每個服務容器都會使用自己的本地磁碟區。如果您使用 local 磁碟區驅動程式,則任何容器都無法共用此資料。但是,某些磁碟區驅動程式確實支援共用儲存。

以下範例啟動一個具有四個複本的 nginx 服務,每個複本都使用名為 myvol2 的本地磁碟區。

$ docker service create -d \
  --replicas=4 \
  --name devtest-service \
  --mount source=myvol2,target=/app \
  nginx:latest

使用 docker service ps devtest-service 來驗證服務是否正在執行

$ docker service ps devtest-service

ID                  NAME                IMAGE               NODE                DESIRED STATE       CURRENT STATE            ERROR               PORTS
4d7oz1j85wwn        devtest-service.1   nginx:latest        moby                Running             Running 14 seconds ago

您可以移除服務以停止正在執行的任務

$ docker service rm devtest-service

移除服務不會移除服務建立的任何磁碟區。移除磁碟區是一個單獨的步驟。

服務的語法差異

docker service create 命令不支援 -v--volume 旗標。將磁碟區掛載到服務的容器中時,您必須使用 --mount 旗標。

使用容器填入磁碟區

如果您啟動一個建立新磁碟區的容器,且該容器在要掛載的目錄(例如 /app/)中具有檔案或目錄,Docker 會將目錄的內容複製到磁碟區中。然後,容器會掛載並使用該磁碟區,而使用該磁碟區的其他容器也可以存取預先填入的內容。

為了說明這一點,以下範例啟動一個 nginx 容器,並使用容器的 /usr/share/nginx/html 目錄的內容填入新的磁碟區 nginx-vol。這是 Nginx 儲存其預設 HTML 內容的位置。

--mount-v 範例具有相同的最終結果。


$ docker run -d \
  --name=nginxtest \
  --mount source=nginx-vol,destination=/usr/share/nginx/html \
  nginx:latest
$ docker run -d \
  --name=nginxtest \
  -v nginx-vol:/usr/share/nginx/html \
  nginx:latest

執行這些範例中的任何一個之後,請執行以下命令來清除容器和磁碟區。請注意,移除磁碟區是一個單獨的步驟。

$ docker container stop nginxtest

$ docker container rm nginxtest

$ docker volume rm nginx-vol

使用唯讀磁碟區

對於某些開發應用程式,容器需要寫入繫結掛載,以便將變更傳播回 Docker 主機。在其他情況下,容器只需要對資料的讀取權限。多個容器可以掛載相同的磁碟區。您可以同時將單個磁碟區掛載為某些容器的 read-write(讀寫),以及其他容器的 read-only(唯讀)。

以下範例變更了上述範例。它透過在容器內的掛載點之後,將 ro 新增到選項列表(預設為空)中,將目錄掛載為唯讀磁碟區。如果存在多個選項,您可以使用逗號分隔它們。

--mount-v 範例具有相同的結果。


$ docker run -d \
  --name=nginxtest \
  --mount source=nginx-vol,destination=/usr/share/nginx/html,readonly \
  nginx:latest
$ docker run -d \
  --name=nginxtest \
  -v nginx-vol:/usr/share/nginx/html:ro \
  nginx:latest

使用 docker inspect nginxtest 來驗證 Docker 是否已正確建立唯讀掛載。尋找 Mounts 區段

"Mounts": [
    {
        "Type": "volume",
        "Name": "nginx-vol",
        "Source": "/var/lib/docker/volumes/nginx-vol/_data",
        "Destination": "/usr/share/nginx/html",
        "Driver": "local",
        "Mode": "",
        "RW": false,
        "Propagation": ""
    }
],

停止並移除容器,然後移除磁碟區。移除磁碟區是一個單獨的步驟。

$ docker container stop nginxtest

$ docker container rm nginxtest

$ docker volume rm nginx-vol

掛載磁碟區子目錄

將磁碟區掛載到容器時,您可以使用 --mount 旗標的 volume-subpath 參數指定要使用的磁碟區子目錄。您指定的子目錄必須在嘗試將其掛載到容器之前存在於磁碟區中;如果它不存在,則掛載會失敗。

如果您只想與容器共用磁碟區的特定部分,則指定 volume-subpath 很有用。例如,假設您有多個容器正在執行,並且您想要將每個容器的日誌儲存在共用磁碟區中。您可以在共用磁碟區中為每個容器建立一個子目錄,然後將子目錄掛載到容器。

以下範例建立一個 logs 磁碟區,並在磁碟區中初始化子目錄 app1app2。然後,它啟動兩個容器,並將 logs 磁碟區的其中一個子目錄掛載到每個容器。此範例假設容器中的程序將其日誌寫入 /var/log/app1/var/log/app2

$ docker volume create logs
$ docker run --rm \
  --mount src=logs,dst=/logs \
  alpine mkdir -p /logs/app1 /logs/app2
$ docker run -d \
  --name=app1 \
  --mount src=logs,dst=/var/log/app1/,volume-subpath=app1 \
  app1:latest
$ docker run -d \
  --name=app2 \
  --mount src=logs,dst=/var/log/app2,volume-subpath=app2 \
  app2:latest

使用此設定,容器會將其日誌寫入 logs 磁碟區的個別子目錄。容器無法存取其他容器的日誌。

在機器之間共享資料

構建容錯應用程式時,您可能需要將同一個服務的多個複本設定為可以存取相同的檔案。

shared storage

開發應用程式時,有幾種方法可以實現此目標。一種方法是在您的應用程式中新增邏輯,以將檔案儲存在雲端物件儲存系統(例如 Amazon S3)上。另一種方法是使用支援將檔案寫入外部儲存系統(例如 NFS 或 Amazon S3)的驅動程式來建立磁碟區。

磁碟區驅動程式允許您從應用程式邏輯中抽象化底層儲存系統。例如,如果您的服務使用具有 NFS 驅動程式的磁碟區,則您可以更新服務以使用不同的驅動程式。例如,將資料儲存在雲端中,而無需更改應用程式邏輯。

使用磁碟區驅動程式

當您使用 docker volume create 建立磁碟區時,或者當您啟動使用尚未建立的磁碟區的容器時,您可以指定磁碟區驅動程式。以下範例使用 vieux/sshfs 磁碟區驅動程式,首先在建立獨立磁碟區時使用,然後在啟動建立新磁碟區的容器時使用。

初始設定

以下範例假設您有兩個節點,第一個節點是 Docker 主機,並且可以使用 SSH 連線到第二個節點。

在 Docker 主機上,安裝 vieux/sshfs 外掛程式

$ docker plugin install --grant-all-permissions vieux/sshfs

使用磁碟區驅動程式建立磁碟區

此範例指定了 SSH 密碼,但如果兩個主機已設定共用金鑰,則可以排除密碼。每個磁碟區驅動程式可能有零個或多個可設定選項,您可以使用 -o 旗標指定每個選項。

$ docker volume create --driver vieux/sshfs \
  -o sshcmd=test@node2:/home/test \
  -o password=testpassword \
  sshvolume

啟動使用磁碟區驅動程式建立磁碟區的容器

以下範例指定了 SSH 密碼。但是,如果兩個主機已設定共用金鑰,則可以排除密碼。每個磁碟區驅動程式可能都有零個或多個可組態選項。

注意事項

如果磁碟區驅動程式要求您傳遞任何選項,您必須使用 --mount 旗標來掛載磁碟區,而不是使用 -v

$ docker run -d \
  --name sshfs-container \
  --mount type=volume,volume-driver=vieux/sshfs,src=sshvolume,target=/app,volume-opt=sshcmd=test@node2:/home/test,volume-opt=password=testpassword \
  nginx:latest

建立使用 NFS 磁碟區的服務

以下範例顯示如何在建立服務時建立 NFS 磁碟區。它使用 10.0.0.10 作為 NFS 伺服器,並使用 /var/docker-nfs 作為 NFS 伺服器上的匯出目錄。請注意,指定的磁碟區驅動程式是 local

NFSv3

$ docker service create -d \
  --name nfs-service \
  --mount 'type=volume,source=nfsvolume,target=/app,volume-driver=local,volume-opt=type=nfs,volume-opt=device=:/var/docker-nfs,volume-opt=o=addr=10.0.0.10' \
  nginx:latest

NFSv4

$ docker service create -d \
    --name nfs-service \
    --mount 'type=volume,source=nfsvolume,target=/app,volume-driver=local,volume-opt=type=nfs,volume-opt=device=:/var/docker-nfs,"volume-opt=o=addr=10.0.0.10,rw,nfsvers=4,async"' \
    nginx:latest

建立 CIFS/Samba 磁碟區

您可以在 Docker 中直接掛載 Samba 共享,而無需在主機上設定掛載點。

$ docker volume create \
	--driver local \
	--opt type=cifs \
	--opt device=//uxxxxx.your-server.de/backup \
	--opt o=addr=uxxxxx.your-server.de,username=uxxxxxxx,password=*****,file_mode=0777,dir_mode=0777 \
	--name cif-volume

如果您指定主機名稱而不是 IP,則需要 addr 選項。這讓 Docker 可以執行主機名稱查詢。

區塊儲存裝置

您可以將區塊儲存裝置(例如外接硬碟或硬碟分割區)掛載到容器。以下範例顯示如何建立和使用檔案作為區塊儲存裝置,以及如何將區塊裝置掛載為容器磁碟區。

重要

以下程序僅為範例。這裡說明的解決方案不建議作為一般做法。除非您對自己的操作充滿信心,否則請勿嘗試此方法。

區塊裝置掛載的運作方式

在底層,使用 local 儲存驅動程式的 --mount 旗標會呼叫 Linux 的 mount 系統呼叫,並將您傳遞給它的選項原封不動地轉發。Docker 並未在 Linux 核心支援的原生掛載功能之上實作任何額外功能。

如果您熟悉 Linux 的 mount 指令,您可以將 --mount 選項視為以下列方式轉發到 mount 指令

$ mount -t <mount.volume-opt.type> <mount.volume-opt.device> <mount.dst> -o <mount.volume-opts.o>

為了進一步說明,請考慮以下 mount 指令範例。此指令將 /dev/loop5 裝置掛載到系統上的 /external-drive 路徑。

$ mount -t ext4 /dev/loop5 /external-drive

從正在執行的容器的角度來看,以下 docker run 指令達到了類似的結果。使用此 --mount 選項執行容器,其設定掛載的方式與您從先前範例執行 mount 指令的方式相同。

$ docker run \
  --mount='type=volume,dst=/external-drive,volume-driver=local,volume-opt=device=/dev/loop5,volume-opt=type=ext4'

您無法直接在容器內執行 mount 指令,因為容器無法存取 /dev/loop5 裝置。這就是 docker run 指令使用 --mount 選項的原因。

範例:在容器中掛載區塊裝置

以下步驟建立 ext4 檔案系統並將其掛載到容器中。您的系統的檔案系統支援取決於您正在使用的 Linux 核心版本。

  1. 建立檔案並配置一些空間給它

    $ fallocate -l 1G disk.raw
    
  2. disk.raw 檔案上建置檔案系統

    $ mkfs.ext4 disk.raw
    
  3. 建立迴圈裝置

    $ losetup -f --show disk.raw
    /dev/loop5
    

    注意事項

    losetup 建立一個暫時的迴圈裝置,該裝置會在系統重新啟動後移除,或使用 losetup -d 手動移除。

  4. 執行將迴圈裝置掛載為磁碟區的容器

    $ docker run -it --rm \
      --mount='type=volume,dst=/external-drive,volume-driver=local,volume-opt=device=/dev/loop5,volume-opt=type=ext4' \
      ubuntu bash
    

    當容器啟動時,路徑 /external-drive 會將主機檔案系統中的 disk.raw 檔案作為區塊裝置掛載。

  5. 完成後,且裝置已從容器卸載,請卸載迴圈裝置以從主機系統中移除裝置

    $ losetup -d /dev/loop5
    

備份、還原或遷移資料磁碟區

磁碟區適用於備份、還原和遷移。使用 --volumes-from 旗標建立掛載該磁碟區的新容器。

備份磁碟區

例如,建立一個名為 dbstore 的新容器

$ docker run -v /dbdata --name dbstore ubuntu /bin/bash

在下一個指令中

  • 啟動一個新容器並從 dbstore 容器掛載磁碟區
  • 將本機主機目錄掛載為 /backup
  • 傳遞將 dbdata 磁碟區內容打包到 /backup 目錄中 backup.tar 檔案的指令。
$ docker run --rm --volumes-from dbstore -v $(pwd):/backup ubuntu tar cvf /backup/backup.tar /dbdata

當指令完成且容器停止時,它會建立 dbdata 磁碟區的備份。

從備份還原磁碟區

使用剛建立的備份,您可以將其還原到同一個容器,或還原到您在其他地方建立的另一個容器。

例如,建立一個名為 dbstore2 的新容器

$ docker run -v /dbdata --name dbstore2 ubuntu /bin/bash

然後,在新容器的資料磁碟區中解壓縮備份檔案

$ docker run --rm --volumes-from dbstore2 -v $(pwd):/backup ubuntu bash -c "cd /dbdata && tar xvf /backup/backup.tar --strip 1"

您可以使用上述技巧,利用您慣用的工具自動化備份、遷移和還原測試。

移除磁碟區

刪除容器後,Docker 資料磁碟區會持續存在。有兩種类型的磁碟區需要考慮

  • 具名磁碟區具有來自容器外部的特定來源,例如,awesome:/bar
  • 匿名磁碟區沒有特定來源。因此,當容器被刪除時,您可以指示 Docker Engine 守护程序將其刪除。

移除匿名磁碟區

要自動移除匿名磁碟區,請使用 --rm 選項。例如,此指令會建立一個匿名的 /foo 磁碟區。當您移除容器時,Docker Engine 會移除 /foo 磁碟區,但不會移除 awesome 磁碟區。

$ docker run --rm -v /foo -v awesome:/bar busybox top

注意事項

如果另一個容器使用 --volumes-from 綁定磁碟區,則會*複製*磁碟區定義,並且在移除第一個容器後,匿名磁碟區也會保留。

移除所有磁碟區

要移除所有未使用的磁碟區並釋放空間

$ docker volume prune

後續步驟