談到 Docker 容器的資料儲存(storage)問題,基本直覺就是透過掛載 Volumes, 不過 Docker 的 Volumes 其實有 3 種不同類型(types):

  1. Bind mount
  2. Volume
  3. tmpfs mount

大家常用的 -v <Host 路徑>:<Container 路徑> 參數其實就是使用 bind mount, 例如以下指令:

$ docker run -it -v /home/user:/data debian /bin/bash

簡而言之,使用 bind mount 的 volume 其實就是透過 host machine 的檔案系統(filesystem)提供容器儲存的能力。

雖然不清楚 volume 的類型,並不會對日常使用上造成任何問題,不過理解其差異仍有其必要,因爲這些差異很可能會在 production 環境上產生重大影響。

本文環境

  • macOS 11.6
  • Docker Desktop 4.6.1

Volume

前文簡單介紹 bind mount 後,接著來認識 Docker 官方更為推薦的 volume 吧。

Volumes are the preferred mechanism for persisting data generated by and used by Docker containers. While bind mounts are dependent on the directory structure and OS of the host machine, volumes are completely managed by Docker.

Volume 與 bind mount 最大的不同在於 Volume 是由 Docker 全權進行管理,因此 volume 比起 bind mount 有幾個優點:

  1. Volumes 更好轉移(migration)與備份(backup)
  2. Volumes 能夠透過 Docker API 與 Docker CLI 進行管理與操作
  3. Volumes 可跨平台(Linux, Windows)
  4. Volumes 更加適合多個容器(container)共享使用的情境
  5. Volumes 提供整合遠端或雲端儲存服務的能力(詳見 Docker plugin),只要使用不同的 Volume driver 即可,例如可以安裝 vieux/sshfs plugin 掛載遠端 SSH 伺服器的檔案系統
  6. Volumes 在 macOS 與 Windows 上效能表現較好

基本上,如果你不知道用 volume 比較好還是 bind mount 比較好,使用 volume 就對了!

小試身手 - 新增 volume

建立 volume 相當簡單,只要使用指令 docker volume create <volume name> 即可,例如:

$ docker volume create myvolume
myvolume

小試身手 - 列出所有 volumes

使用指令 docker volume ls 可以列出所有的 volumes:

$ docker volume ls
DRIVER    VOLUME NAME
local     myvolume

小試身手 - 列出 volume 詳細資訊

如果要進一步檢視 volume 詳細資訊,可以使用 docker volume inspect <volume name> 指令,例如:

$ docker volume inspect myvolume
[
    {
        "CreatedAt": "2022-03-13T06:48:16Z",
        "Driver": "local",
        "Labels": {},
        "Mountpoint": "/var/lib/docker/volumes/myvolume/_data",
        "Name": "myvolume",
        "Options": {},
        "Scope": "local"
    }
]

上述指令結果可以看到 Mountpoint, 該資訊就是 volume 實際儲存資料的路徑位置。

但如果是 macOS 的使用者就會發現找不到 /var/lib/docker/ ( no such file or directory: /var/lib/docker ):

$ cd /var/lib/docker/volumes/
cd: no such file or directory: /var/lib/docker/volumes/

這是由於 macOS 的 Docker 是用 LinuxKit 模擬的,因此路徑又被包裝過一層,其大致在以下的路徑:

$ cd ~/Library/Containers/com.docker.docker/Data/vms/0/

可於上述資料夾內發現 1 個名稱為 tty 的檔案,用指令 screen tty 就能夠進入 LinuxKit 內,然後就能發現 /var/lib/docker/volumes 資料夾,然後就能順利找到相對應的 Volume 囉!

不過 Docker Desktop for Mac version 2.3.0.4 以後就沒有 tty 檔案了,但還是可以使用以下指令進入 Docker 的 host machine 並且找到 /var/lib/docker/volumes :

$ docker run -it --privileged --pid=host debian nsenter -t 1 -m -u -n -i sh
/ # ls /var/lib/docker/volumes/
backingFsBlockDev  metadata.db        myvolume

掛載(Mount) Volume

掛載 volume 也十分簡單,只要加上 -v <volume name>:<容器內掛載路徑> 即可,例如以下指令將 myvolume 掛載到 nginx 容器(container)內的 /data :

$ docker run -it -v myvolume:/data nginx /bin/bash

進到容器內就可以發現路徑 /data 可以使用:

root@1630d3b4ffb8:~# touch /data/hello.txt
root@1630d3b4ffb8:~# ls -alh /data
total 8.0K
drwxr-xr-x 2 root root 4.0K Mar 18 15:53 .
drwxr-xr-x 1 root root 4.0K Mar 18 15:51 ..
-rw-r--r-- 1 root root    0 Mar 18 15:53 hello.txt

如果有在 volume 內新增任何資料的話,也可以在 Docker Desktop 內看到:

前文提及 volume 可以共享,因此可以打開一個新的容器並且掛載同一個 volume 試試:

$ docker run -it -v myvolume:/data debian /bin/bash
root@12fa2630f490:/# ls /data
hello.txt

上述結果可以看到新啟動的容器內不僅有 /data 資料夾,該資料夾底下也有先前在其他容器建立的檔案。

Volumes 的 2 種參數

掛載 volume 時,除了 -v 參數可以使用之外,還有 --mount 參數可以使用,基本上 2 者功能是相同的,但差別在於參數格式不一樣之外, --mount 可設定的選項也相較多一點。

-v 參數

-v 的參數值是以 : 做為分隔的 3 個欄位值,分別是 <volume 名稱>:<容器內的掛載路徑>:<volume 選項>

  • <volume 名稱> 可以是 Docker 的 volume 名稱,該名稱可以用指令 docker volume ls 列出,其值對應的是 VOLUME NAME 一欄。如果這邊用的是實際系統路徑的話, Docker 就會自動轉為使用 bind mount, 而非 volume 。
  • <容器內的掛載路徑> 容器(container)內的路徑
  • <volume 選項> 例如 ro 代表唯讀(read only)

例如掛載唯讀(read only)的 volume:

$ docker run -it -v myvolume:/data:ro debian /bin/bash

–mount 參數

--mount 參數則可以視為 -v 參數的進階版。

--mount 更加口語(verbose)化,在 volumes 的設定上都會明確以 <key>=<value> 的形式進行設定(當 <key>readonly 時,可以直接省略 =<value> 的部分),多個 key 值之間則以逗號(,)進行分割,而且設定的順序上並不像 -v 參數那般有順序上的要求。

目前可使用的 <key> 值有:

  • source
  • destination
  • type
  • readonly
  • volume-driver
  • volume-opt
source

source 是欲掛載 volume 的名稱,該名稱可以用指令 docker volume ls 列出,其值對應的是 VOLUME NAME 一欄。

destination

destination 是容器(container)內掛載 volume 內的路徑。

type

其值可以是 bind , volume , tmpfs 其中 1 個,一般使用 volume 即可,是 Docker 推薦的方式;如果使用 bind 則代表使用 bind mounts ;最後 1 種 tmpfs 則代表 tmpfs mount , 通常 tmpfs 用以儲存非持久性(non-persistent)的資料,例如暫存檔案、快取(cache)等等,也可用以提升容器的效能,也由於 tmpfs 是非持久性(non-persistent)的儲存,所以當容器(container)關閉後,該 volume 內的資料也會跟著消失。

If your container generates non-persistent state data, consider using a tmpfs mount to avoid storing the data anywhere permanently, and to increase the container’s performance by avoiding writing into the container’s writable layer.

建立一個 tmpfs 的 volume 指令如下:

$ docker volume create -o type=tmpfs -o device=tmpfs <volume name>

例如以下指令建立 1 個名為 mytmpfs 的 tmpfs volume:

$ docker volume create -o type=tmpfs -o device=tmpfs mytmpfs

除了新增 volume 後掛載 tmpfs volume 之外,當然也可以直接以 --mount 參數方式掛載 tmpfs volume:

$ docker run -it --mount type=tmpfs,destination=/data debian /bin/bash

上述指令其實等同於, Docker 也有提供 --tmpfs 參數可以使用:

$ docker run -it --tmpfs /data debian /bin/bash
readonly

將 volume 設定為唯讀(read only)。等同於 -v 參數中的 ro 選項。

--mount 參數掛載唯讀 volume 的範例:

$ docker run -it --mount source=myvolume,destination=/data,readonly debian /bin/bash
volume-driver

Docker 將儲存(storage)的概念抽象化為 driver (或稱驅動器),透過使用不同的 driver 可以介接各式各樣的檔案系統,例如 NFS(Network File System), SSHFS(SSH Filesystem) 甚至是 AWS S3 等等,預設是使用 local driver, 也就是 volume-driver=local

如果想使用其他 driver 可以查看以下文件:

  1. Docker volume pluginsAvailable volume plugins
  2. Docker engine managed plugin system

這些文件列出第三方提供的各種 volume plugin 的安裝與使用方式之外,也紀錄如何使用 Docker 官方提供的指令(docker plugin install <plugin name> )安裝 volume plugin 以及 volume plugin 的開發方法。

volume-opt

volume-opt 用以設定 volume 相關的選項,volume-opt 的數量並沒有限制,端看 volume driver 提供哪些選項可以使用,詳細能夠使用的選項可以參考 FILESYSTEM-SPECIFIC MOUNT OPTIONS 章節。

值得注意的是 tmpfs 並不支援 volume-opt 的用法,如果 2 者合用就會出現以下錯誤:

cannot mix 'volume-*' options with mount type 'tmpfs'

總結

以上就是關於 Docker volume 的介紹!

Happy Coding!

References

https://docs.docker.com/storage/bind-mounts/

https://docs.docker.com/storage/volumes/

https://docs.docker.com/engine/reference/commandline/volume_create/