卷
卷是容器的持久化資料儲存,由 Docker 建立和管理。您可以使用 docker volume create
命令顯式建立一個卷,或者 Docker 可以在建立容器或服務期間建立一個卷。
當您建立一個卷時,它會儲存在 Docker 主機上的一個目錄中。當您將卷掛載到容器中時,這個目錄就是掛載到容器中的內容。這與繫結掛載的工作方式類似,不同之處在於卷由 Docker 管理,並且與主機核心功能隔離。
何時使用卷
卷是持久化 Docker 容器生成和使用的資料的首選機制。雖然繫結掛載依賴於主機的目錄結構和作業系統,但卷完全由 Docker 管理。卷是以下用例的良好選擇:
- 卷比繫結掛載更容易備份或遷移。
- 您可以使用 Docker CLI 命令或 Docker API 管理卷。
- 卷在 Linux 和 Windows 容器上都可用。
- 卷可以更安全地在多個容器之間共享。
- 新卷的內容可以由容器或構建預先填充。
- 當您的應用程式需要高效能 I/O 時。
如果您需要從主機訪問檔案,卷不是一個好的選擇,因為卷完全由 Docker 管理。如果您需要從容器和主機訪問檔案或目錄,請使用繫結掛載。
卷通常比直接向容器寫入資料更好,因為卷不會增加使用它的容器的大小。使用卷也更快;寫入容器的可寫層需要儲存驅動程式來管理檔案系統。儲存驅動程式提供了一個聯合檔案系統,使用 Linux 核心。與直接寫入主機檔案系統的卷相比,這種額外的抽象降低了效能。
如果您的容器生成非永續性狀態資料,請考慮使用 tmpfs 掛載,以避免永久儲存資料,並透過避免寫入容器的可寫層來提高容器的效能。
卷使用 rprivate
(遞迴私有)繫結傳播,並且繫結傳播對於卷是不可配置的。
卷的生命週期
卷的內容存在於特定容器的生命週期之外。當容器被銷燬時,可寫層也隨之銷燬。使用卷可以確保即使使用它的容器被刪除,資料仍然是持久的。
一個給定的卷可以同時掛載到多個容器中。當沒有正在執行的容器使用卷時,該卷仍然對 Docker 可用,並且不會被自動刪除。您可以使用 docker volume prune
刪除未使用的卷。
在現有資料上掛載卷
如果您將一個非空卷掛載到容器中一個已存在檔案或目錄的目錄中,那麼預先存在的檔案會被掛載所遮蓋。這類似於您在 Linux 主機上將檔案儲存到 /mnt
,然後將一個 USB 驅動器掛載到 /mnt
。/mnt
的內容將被 USB 驅動器的內容所遮蓋,直到 USB 驅動器被解除安裝。
對於容器,沒有直接的方法可以移除掛載以再次顯示被遮蔽的檔案。最好的選擇是重新建立沒有該掛載的容器。
如果您將一個空卷掛載到容器中一個已存在檔案或目錄的目錄中,這些檔案或目錄預設會被傳播(複製)到卷中。類似地,如果您啟動一個容器並指定一個尚不存在的卷,Docker 會為您建立一個空卷。這是預填充另一個容器所需資料的好方法。
要防止 Docker 將容器的預先存在的檔案複製到空卷中,請使用 volume-nocopy
選項,請參閱 --mount 的選項。
命名卷和匿名卷
卷可以是命名的或匿名的。匿名卷會被賦予一個隨機名稱,保證在給定的 Docker 主機內是唯一的。與命名卷一樣,匿名卷即使在刪除使用它們的容器後也會持久存在,除非您在建立容器時使用 --rm
標誌,在這種情況下,與該容器關聯的匿名卷將被銷燬。請參閱刪除匿名卷。
如果您連續建立多個容器,每個容器都使用匿名卷,那麼每個容器都會建立自己的卷。匿名卷不會自動在容器之間重用或共享。要在兩個或多個容器之間共享一個匿名卷,您必須使用隨機的卷 ID 來掛載該匿名卷。
語法
要使用 docker run
命令掛載卷,您可以使用 --mount
或 --volume
標誌。
$ docker run --mount type=volume,src=<volume-name>,dst=<mount-path>
$ docker run --volume <volume-name>:<mount-path>
總的來說,--mount
更受推薦。主要區別在於 --mount
標誌更明確,並支援所有可用選項。
如果您想執行以下操作,必須使用 --mount
:
--mount 選項
--mount
標誌由多個鍵值對組成,用逗號分隔,每個鍵值對由一個 <key>=<value>
元組構成。鍵的順序不重要。
$ docker run --mount type=volume[,src=<volume-name>],dst=<mount-path>[,<key>=<value>...]
--mount type=volume
的有效選項包括:
選項 | 描述 |
---|---|
source , src | 掛載的來源。對於命名卷,這是卷的名稱。對於匿名卷,此欄位被省略。 |
destination , dst , target | 檔案或目錄在容器中掛載的路徑。 |
volume-subpath | 要掛載到容器中的卷內子目錄的路徑。在將卷掛載到容器之前,該子目錄必須存在於卷中。請參閱掛載卷子目錄。 |
readonly , ro | 如果存在,會導致卷以只讀方式掛載到容器中。 |
volume-nocopy | 如果存在,當卷為空時,目標位置的資料不會被複制到卷中。預設情況下,如果掛載的卷為空,目標位置的內容會被複制到其中。 |
volume-opt | 可以指定多次,接受一個鍵值對,由選項名稱及其值組成。 |
$ docker run --mount type=volume,src=myvolume,dst=/data,ro,volume-subpath=/foo
--volume 的選項
--volume
或 -v
標誌由三個欄位組成,用冒號(:
)分隔。這些欄位必須按正確的順序排列。
$ docker run -v [<volume-name>:]<mount-path>[:opts]
對於命名卷,第一個欄位是卷的名稱,在給定的主機上是唯一的。對於匿名卷,第一個欄位被省略。第二個欄位是檔案或目錄在容器中掛載的路徑。
第三個欄位是可選的,是一個逗號分隔的選項列表。對於資料卷,--volume
的有效選項包括:
選項 | 描述 |
---|---|
readonly , ro | 如果存在,會導致卷以只讀方式掛載到容器中。 |
volume-nocopy | 如果存在,當卷為空時,目標位置的資料不會被複制到卷中。預設情況下,如果掛載的卷為空,目標位置的內容會被複制到其中。 |
$ docker run -v myvolume:/data:ro
建立和管理卷
與繫結掛載不同,您可以在任何容器的範圍之外建立和管理卷。
建立一個卷
$ 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
刪除服務不會刪除由該服務建立的任何卷。刪除卷是一個獨立的步驟。
使用容器填充卷
如果您啟動一個建立新卷的容器,並且該容器在要掛載的目錄(如 /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 主機。而在其他時候,容器只需要對資料進行只讀訪問。多個容器可以掛載同一個卷。您可以同時將單個卷對某些容器掛載為可讀寫
,而對其他容器掛載為只讀
。
以下示例更改了前一個示例。它透過在容器內的掛載點後,向(預設為空的)選項列表新增 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
卷,並在該卷中初始化子目錄 app1
和 app2
。然後,它啟動兩個容器,並將 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
卷的不同子目錄中。容器無法訪問其他容器的日誌。
在機器之間共享資料
在構建容錯應用程式時,您可能需要配置同一服務的多個副本以訪問相同的檔案。


在開發應用程式時,有幾種方法可以實現這一點。一種是在您的應用程式中新增邏輯,將檔案儲存在像 Amazon S3 這樣的雲物件儲存系統上。另一種是使用支援將檔案寫入外部儲存系統(如 NFS 或 Amazon S3)的驅動程式建立卷。
卷驅動程式讓您可以將底層儲存系統與應用程式邏輯分離開來。例如,如果您的服務使用帶有 NFS 驅動程式的卷,您可以更新服務以使用不同的驅動程式。例如,將資料儲存在雲中,而無需更改應用程式邏輯。
使用卷驅動程式
當您使用 docker volume create
建立卷時,或者當您啟動一個使用尚未建立的卷的容器時,您可以指定一個卷驅動程式。以下示例使用 rclone/docker-volume-rclone
卷驅動程式,首先在建立獨立卷時,然後是啟動一個建立新卷的容器時。
注意如果您的卷驅動程式接受一個逗號分隔的列表作為選項,您必須從外部 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>
初始設定
以下示例假設您有兩個節點,其中第一個是 Docker 主機,並且可以使用 SSH 連線到第二個節點。
在 Docker 主機上,安裝 rclone/docker-volume-rclone
外掛:
$ docker plugin install --grant-all-permissions rclone/docker-volume-rclone --aliases rclone
使用卷驅動程式建立卷
此示例將主機 1.2.3.4
上的 /remote
目錄掛載到一個名為 rclonevolume
的卷中。每個卷驅動程式可以有零個或多個可配置選項,您可以使用 -o
標誌指定每個選項。
$ docker volume create \
-d rclone \
--name rclonevolume \
-o type=sftp \
-o path=remote \
-o sftp-host=1.2.3.4 \
-o sftp-user=user \
-o "sftp-password=$(cat file_containing_password_for_remote_host)"
該卷現在可以被掛載到容器中。
啟動一個使用卷驅動程式建立卷的容器
注意如果卷驅動程式要求您傳遞任何選項,您必須使用
--mount
標誌來掛載卷,而不是-v
。
$ docker run -d \
--name rclone-container \
--mount type=volume,volume-driver=rclone,src=rclonevolume,target=/app,volume-opt=type=sftp,volume-opt=path=remote, volume-opt=sftp-host=1.2.3.4,volume-opt=sftp-user=user,volume-opt=-o "sftp-password=$(cat file_containing_password_for_remote_host)" \
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 cifs-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 核心版本。
建立一個檔案併為其分配一些空間:
$ fallocate -l 1G disk.raw
在
disk.raw
檔案上構建一個檔案系統:$ mkfs.ext4 disk.raw
建立一個迴圈裝置:
$ losetup -f --show disk.raw /dev/loop5
注意losetup
建立一個臨時的迴圈裝置,該裝置在系統重啟後或透過losetup -d
手動移除後會被刪除。執行一個將迴圈裝置掛載為卷的容器:
$ 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
檔案作為塊裝置掛載。完成後,當裝置從容器中解除安裝時,分離迴圈裝置以從主機系統中移除該裝置:
$ 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
後續步驟
- 瞭解繫結掛載。
- 瞭解tmpfs 掛載。
- 瞭解儲存驅動程式。
- 瞭解第三方卷驅動程式外掛。