卷(volumes)是 Docker 容器生產和使用持久化資料的首選機制。繫結掛載(bind mounts)依賴於主機的目錄結構,卷(volumes)完全由 Docker 管理。卷與繫結掛載相比有幾個優勢:
- 卷比繫結掛載更容易備份或遷移。
- 您可以使用 Docker CLI 命令或 Docker API 來管理卷。
- 卷可以在 Linux 和 Windows 容器上工作。
- 卷可以更安全地在多個容器之間共享。
- 卷驅動程式允許您在遠端主機或雲提供商上儲存卷、加密卷的內容或新增其他功能。
- 新卷的內容可以由容器預先填充。(New volumes can have their content pre-populated by a container.)
此外,與將資料持久化到容器的可寫層相比,卷通常是更好的選擇,因為卷不會增加使用它的容器的大小,而且卷的內容存在於給定容器的生命週期之外。
如果容器生成非永續性狀態資料,請考慮使用 tmpfs 掛載(tmpfs mount)以避免將資料永久儲存在任何位置,並通過避免寫入容器的可寫層來提高容器的效能。
卷使用 rprivate
繫結傳播,並且繫結傳播對於卷是不可配置的。
選擇 -v 或 --mount 標記
最初,-v
或 --volume
標記用於獨立容器,--mount
標記用於叢集服務。但是,從 Docker 17.06 開始,您也可以將 --mount
用於獨立容器。通常,--mount
標記表達更加明確和冗長。最大的區別是 -v
語法將所有選項組合在一個欄位中,而 --mount
語法將選項分離。下面是每個標記的語法比較。
新使用者推薦使用
--mount
語法,它比--volume
語法更簡單。
如果需要指定卷驅動程式選項,則必須使用 --mount
。
-v
或--volume
: 由三個欄位組成,以冒號(:)分隔。欄位必須按照正確的順序排列,且每個欄位的含義不夠直觀明顯。- 對於命名卷,第一個欄位是卷的名稱,在給定的主機上是惟一的。對於匿名卷,省略第一個欄位。
- 第二個欄位是容器中檔案或目錄掛載的路徑。
- 第三個欄位是可選的,是一個逗號分隔的選項列表,比如
ro
。這些選項會在本文下面討論。
--mount
:由多個鍵-值對組成,以逗號分隔,每個鍵-值對由一個<key>=<value>
元組組成。--mount
語法比-v
或--volume
更冗長,但是鍵的順序並不重要,標記的值也更容易理解。- 掛載的型別(
type
),可以是bind
、volume
或者tmpfs
。本主題討論卷(volume),因此型別(type
)始終為卷(volume
)。 - 掛載的源(
source
),對於命名卷,這是卷的名稱。對於匿名卷,此欄位被省略。可以用source
或者src
來指定。 - 目標(
destination
),將容器中檔案或目錄掛載的路徑作為其值。可以用destination
、dst
或者target
來指定。 readonly
選項(如果存在),則會將繫結掛載以只讀形式掛載到容器中。volume-opt
選項,可以被指定多次,接受由選項名及其值組成的鍵-值對。
- 掛載的型別(
從外部 CSV 解析器轉義值
如果卷驅動程式接受以逗號分隔的列表作為選項,則必須從外部 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
# 輸出結果:
DRIVER VOLUME NAME
local my-vol
檢查卷:
$ docker volume inspect my-vol
# 輸出結果:
[
{
"CreatedAt": "2020-07-04T07:06:47Z",
"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/
中。
下面的 --mount
和 -v
示例會產生相同的結果。除非在執行第一個示例之後刪除了 devtest
容器和 myvol2
卷,否則不能同時執行它們。
--mount
:
$ docker run -d \
--name devtest \
--mount source=myvol2,target=/app \
nginx:latest
-v
:
$ docker run -d \
--name devtest \
-v myvol2:/app \
nginx:latest
使用 docker inspect devtest
驗證卷的建立和掛載是否正確。檢視 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
啟動帶有卷的服務
啟動服務並定義卷時,每個服務容器都使用自己的本地卷。 如果使用本地(local
)卷驅動程式,則沒有任何容器可以共享此資料,但某些卷驅動程式確實支援共享儲存。Docker for AWS 和 Docker for Azure 都支援使用 Cloudstor 外掛的持久儲存。
下面的示例使用四個副本啟動 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/
)中有檔案或目錄,那麼該目錄的內容將複製到新卷中。然後容器掛載並使用該卷,使用該卷的其他容器也可以訪問預填充的內容。
為了說明這一點,這個例子啟動了一個 nginx
容器,並用容器的 /usr/share/nginx/html
目錄中的內容填充新的卷 nginx-vol
,這個目錄是 Nginx 儲存預設的 HTML 內容的地方。
下面的 --mount
和 -v
示例具有相同的最終結果。
--mount
:
$ docker run -d \
--name=nginxtest \
--mount source=nginx-vol,destination=/usr/share/nginx/html \
nginx:latest
-v
:
$ 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
示例有相同的結果。
--mount
:
$ docker run -d \
--name=nginxtest \
--mount source=nginx-vol,destination=/usr/share/nginx/html,readonly \
nginx:latest
-v
:
$ docker run -d \
--name=nginxtest \
-v nginx-vol:/usr/share/nginx/html:ro \
nginx:latest
使用 docker inspect nginxtest
驗證是否正確建立了只讀掛載。檢視 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
在機器之間共享資料
在構建故障容錯的應用程式時,您可能需要配置同一服務的多個副本,以訪問相同的檔案。
在開發應用程式時,有幾種方法可以實現這一點。一種方法是向您的應用程式新增邏輯,在雲物件儲存系統(如 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 \
--volume-driver vieux/sshfs \
--mount 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=10.0.0.10,rw,nfsvers=4,async"' \
nginx:latest
備份、還原或遷移資料卷
卷對於備份、還原和遷移非常有用。使用 --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 引擎守護程式刪除它們。
刪除匿名卷
要自動刪除匿名卷,請使用 --rm
選項。例如,這個命令建立一個匿名的 /foo
卷。當容器被刪除時,Docker 引擎會刪除 /foo
卷,但不會刪除 awesome
卷。
$ docker run --rm -v /foo -v awesome:/bar busybox top
刪除所有卷
要刪除所有未使用的卷並釋放空間,請執行以下操作:
$ docker volume prune