Docker 數據卷之進階篇

筆者在《Docker 基礎 : 數據管理》一文中介紹了 docker 數據卷(volume) 的基本用法。隨着使用的深刻,筆者對 docker 數據卷的理解與認識也在不斷的加強。本文將在前文的基礎上介紹 docker 數據卷的原理及一些高級用法。若是您想先了解 docker 數據卷的基本概念與用法,請先移步這裏html

爲何須要數據卷?

這得從 docker 容器的文件系統提及。出於效率等一系列緣由,docker 容器的文件系統在宿主機上存在的方式很複雜,這會帶來下面幾個問題:docker

  • 不能在宿主機上很方便地訪問容器中的文件。
  • 沒法在多個容器之間共享數據。
  • 當容器刪除時,容器中產生的數據將丟失。

爲了解決這些問題,docker 引入了數據卷(volume) 機制。數據卷是存在於一個或多個容器中的特定文件或文件夾,這個文件或文件夾以獨立於 docker 文件系統的形式存在於宿主機中。數據卷的最大特定是:其生存週期獨立於容器的生存週期ubuntu

使用數據卷的最佳場景

  • 在多個容器之間共享數據,多個容器能夠同時以只讀或者讀寫的方式掛載同一個數據卷,從而共享數據卷中的數據。
  • 當宿主機不能保證必定存在某個目錄或一些固定路徑的文件時,使用數據卷能夠規避這種限制帶來的問題。
  • 當你想把容器中的數據存儲在宿主機以外的地方時,好比遠程主機上或雲存儲上。
  • 當你須要把容器數據在不一樣的宿主機之間備份、恢復或遷移時,數據卷是很好的選擇。

docker volume 子命令

docker 專門提供了 volume 子命令來操做數據卷:
create        建立數據卷
inspect      顯示數據卷的詳細信息
ls               列出全部的數據卷
prune        刪除全部未使用的 volumes,而且有 -f 選項
rm             刪除一個或多個未使用的 volumes,而且有 -f 選項
先建立一個名稱爲 hello 的數據卷並經過 ls 命令進行查看:bash

而後可使用 inspect 命令看看數據卷 hello 的詳細信息:ssh

在這裏咱們能夠看到建立數據卷的時間;該數據卷使用的驅動程序爲默認的 "local",表示數據卷使用宿主機的本地存儲;數據卷的掛載點,默認是本機 /var/lib/docker/volumes 下的一個目錄。
最後咱們可使用 rm 或 prune 命令刪除數據卷,後面筆者會介紹一些實際使用中與數據卷的刪除有關的一些實踐。ui

使用 mount 語法掛載數據卷

以前咱們使用 --volume(-v) 選項來掛載數據卷,如今 docker 提供了更強大的 --mount 選項來管理數據卷。mount 選項能夠經過逗號分隔的多個鍵值對一次提供多個配置項,所以 mount 選項能夠提供比 volume 選項更詳細的配置。使用 mount 選項的經常使用配置以下:
type 指定掛載方式,咱們這裏用到的是 volume,其實還能夠有 bind 和 tmpfs。
volume-driver 指定掛載數據卷的驅動程序,默認值是 local。
source 指定掛載的源,對於一個命名的數據卷,這裏應該指定這個數據卷的名稱。在使用時能夠寫 source,也能夠簡寫爲 src。
destination 指定掛載的數據在容器中的路徑。在使用時能夠寫 destination,也能夠簡寫爲 dst 或 target。
readonly 指定掛載的數據爲只讀。
volume-opt 能夠指定屢次,用來提升更多的 mount 相關的配置。
下面咱們看個具體的例子:spa

$ docker volume create hello
$ docker run -id --mount type=volume,source=hello,target=/world ubuntu /bin/bash

咱們建立了名稱爲 hello 的數據卷,而後把它掛在到容器中的 /world 目錄。經過 inspect 命令查看容器的詳情中的 "Mounts" 信息能夠驗證明際的數據卷掛載結果 :插件

使用 volume driver 把數據存儲到其它地方

除了默認的把數據卷中的數據存儲在宿主機,docker 還容許咱們經過指定 volume driver 的方式把數據卷中的數據存儲在其它的地方,好比 Azrue Storge 或 AWS 的 S3。
簡單起見,咱們接下來的 demo 演示如何經過 vieux/sshfs 驅動把數據卷的存儲在其它的主機上。
docker 默認是不安裝 vieux/sshfs 插件的,咱們能夠經過下面的命令進行安裝:code

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

而後經過 vieux/sshfs 驅動建立數據卷,並指定遠程主機的登陸用戶名、密碼和數據存放目錄:htm

docker volume create --driver vieux/sshfs \
    -o sshcmd=nick@10.32.2.134:/home/nick/sshvolume \
    -o password=yourpassword \
    mysshvolume

注意,請確保你指定的遠程主機上的掛載點目錄是存在的(demo 中是 /home/nick/sshvolume 目錄),不然在啓動容器時會報錯。
最後在啓動容器時指定掛載這個數據卷:

docker run -id \
    --name testcon \
    --mount type=volume,volume-driver=vieux/sshfs,source=mysshvolume,target=/world \
    ubuntu /bin/bash

這就搞定了,你在容器中 /world 目錄下操做的文件都存儲在遠程主機的 /home/nick/sshvolume 目錄中。進入容器 testcon 而後在 /world 目錄中建立一個文件,而後打開遠程主機的  /home/nick/sshvolume 目錄進行查看,你新建的文件是否是已經出如今那裏了!

數據卷原理

下圖描述了 docker 容器掛載數據的三種方式:

數據卷是徹底被 docker 管理的,就像上圖中的黃色區域描述的同樣,docker 在宿主機的文件系統中找了個文件管理數據卷相關的數據。所以你可能根本不須要知道數據卷文件在宿主機上的存儲位置(事實上抱着刨根問底的精神咱們仍是很想搞清楚它背後的工做原理!)。

docker 數據卷的本質是容器中的一個特殊目錄。在容器建立的過程當中,docker 會將宿主機上的指定目錄(一個以數據卷 ID 爲名稱的目錄)掛載到容器中指定的目錄上。這裏使用的掛載方式爲綁定掛載(bind mount),因此掛載完成後的宿主機目錄和容器內的目標目錄表現一致。
好比咱們執行下面的命令建立數據卷 hello,並掛載到容器 testcon 的 /world 目錄:

$ docker volume create hello
$ docker run -id --name testcon --mount type=volume,source=hello,target=/world ubuntu /bin/bash

實際上在容器的建立過程當中,相似於在容器中執行了下面的代碼:

// 將數據卷 hello 在宿主機上的目錄綁定掛載到 rootfs 中指定的掛載點 /world 上
mount("/var/lib/docker/volumes/hello/_data", "rootfs/world", "none", MS_BIND, NULL)

在處理完全部的 mount 操做以後(真正須要 docker 容器掛載的除了數據卷目錄還包括 rootfs,init-layer 裏的內容,/proc 設備等),docker 只須要經過 chdir 和 pivot_root 切換進程的根目錄到 rootfs 中,這樣容器內部進程就只能看見以 rootfs 爲根的文件系統以及被 mount 到 rootfs 之下的各項目錄了。例如咱們啓動的 testcon 中的文件系統爲:

下面咱們介紹幾個數據卷在使用中比較常見的問題。

數據的覆蓋問題

  • 若是掛載一個空的數據捲到容器中的一個非空目錄中,那麼這個目錄下的文件會被複制到數據卷中。
  • 若是掛載一個非空的數據捲到容器中的一個目錄中,那麼容器中的目錄中會顯示數據卷中的數據。若是原來容器中的目錄中有數據,那麼這些原始數據會被隱藏掉。

這兩個規則都很是重要,靈活利用第一個規則能夠幫助咱們初始化數據卷中的內容。掌握第二個規則能夠保證掛載數據卷後的數據老是你指望的結果。

在 Dockerfile 中添加數據卷

在 Dockerfile 中咱們可使用 VOLUME 指令向容器添加數據卷:

VOLUME /data

在使用 docker build 命令生成鏡像而且以該鏡像啓動容器時會掛載一個數據捲到 /data 目錄。根據咱們已知的數據覆蓋規則,若是鏡像中存在 /data 目錄,這個目錄中的內容將所有被複制到宿主機中對應的目錄中,而且根據容器中的文件設置合適的權限和全部者。
注意,VOLUME 指令不能掛載主機中指定的目錄。這是爲了保證 Dockerfile 的可一致性,由於不能保證全部的宿主機都有對應的目錄
在實際的使用中,這裏還有一個陷阱須要你們注意:在 Dockerfile 中使用 VOLUME 指令以後的代碼,若是嘗試對這個數據捲進行修改,這些修改都不會生效!下面是一個這樣的例子:

FROM ubuntu
RUN useradd nick
VOLUME /data
RUN touch /data/test.txt
RUN chown -R nick:nick /data

經過這個 Dockerfile 建立鏡像並啓動容器後,該容器中存在用戶 nick,而且可以看到 /data 目錄掛載的數據卷。可是 /data 目錄內並無文件 test.txt,更別說 test.txt 文件的全部者屬性了。要解釋這個現象須要咱們瞭解經過 Dockerfile 建立鏡像的過程:
Dockerfile 中除了 FROM 指令的每一行都是基於上一行生成的臨時鏡像運行一個容器,執行一條指令並執行相似 docker commit 的命令獲得一個新的鏡像。這條相似 docker commit 的命令不會對掛載的數據捲進行保存。
因此上面的 Dockerfile 最後兩行執行時,都會在一個臨時的容器上掛載 /data,並對這個臨時的數據捲進行操做,可是這一行指令執行並提交後,這個臨時的數據卷並無被保存。於是咱們最終經過鏡像建立的容器所掛載的數據卷是沒有被最後兩條指令操做過的。咱們姑且叫它 "Dockerfile 中數據卷的初始化問題"。

下面的寫法能夠解決 Dockerfile 中數據卷的初始化問題:

FROM ubuntu
RUN useradd nick
RUN mkdir /data && touch /data/test.txt
RUN chown -R nick:nick /data
VOLUME /data

經過這個 Dockerfile 建立鏡像並啓動容器後,數據卷的初始化是符合預期的。這是因爲在掛載數據卷時,/data 已經存在,/data 中的文件以及它們的權限和全部者設置會被複制到數據卷中。
還有另一種方法能夠解決 Dockerfile 中數據卷的初始化問題。就是利用 CMD 指令和 ENTRYPOINT 指令的執行特色:與 RUN 指令在鏡像構建過程當中執行不一樣,CMD 指令和 ENTRYPOINT 指令是在容器啓動時執行。所以使用下面的 Dockerfile 也能夠達到對數據卷的初始化目的:

FROM ubuntu
RUN useradd nick
VOLUME /data
CMD touch /data/test.txt && chown -R nick:nick /data && /bin/bash

總結

數據卷解決了用戶數據的持久化問題,可以讓用戶在容器中產生的數據超出容器自身的生命週期。所以對於容器技術來講掌握數據卷的使用很是必要。但願本文可以幫助您理解數據卷相關的內容。

參考:
Docker doc: Use volumes
《docker 容器與容器雲第二版》

相關文章
相關標籤/搜索