深入理解 Docker Volume
阿新 • • 發佈:2018-12-31
本文主要介紹了Docker Volume的原理以及使用方式,是Docker入門教程的延伸。作者通過從資料的共享、資料容器、備份、許可權以及刪除Volume五方面深入介紹了Volume的工作原理,從實戰中幫助讀者瞭解Volume。
從Docker IRC頻道以及 stackoverflow 的問題來看,很多人還不是很明白Docker Volume的工作原理。在這篇文章中,我會盡最大的努力來解釋Volume是如何工作的,並展示一些最佳實踐。這篇文章主要是針對那些對Volume不瞭解的Docker使用者,當然有經驗的使用者也可以通過本文了解一些Volume的細節。
如果想要了解Docker Volume,首先我們需要知道Docker的檔案系統是如何工作的。Docker映象是由多個檔案系統(只讀層)疊加而成。當我們啟動一個容器的時候,Docker會載入只讀映象層並在其上(譯者注:映象棧頂部)新增一個讀寫層。如果執行中的容器修改了現有的一個已經存在的檔案,那該檔案將會從讀寫層下面的只讀層複製到讀寫層,該檔案的只讀版本仍然存在,只是已經被讀寫層中該檔案的副本所隱藏。當刪除Docker容器,並重新啟動通過該映象啟動時,之前的更改將會丟失。在Docker中,只讀層及在頂部的讀寫層的組合被稱為 Union File System (聯合檔案系統)。
為了能夠儲存(持久化)資料以及共享容器間的資料,Docker提出了Volume的概念。簡單來說,Volume就是目錄或者檔案,它可以繞過預設的聯合檔案系統,而以正常的檔案或者目錄的形式存在於宿主機上。
我們可以通過兩種方式來初始化Volume,這兩種方式有些細小而又重要的差別。我們可以在執行時使用 -v 來宣告Volume:
$ docker run -it --name container-test -h CONTAINER -v /data debian /bin/bash
[email protected]:/# ls /data
[email protected]:/#
上面的命令會將 /data 掛載到容器中,並繞過聯合檔案系統,我們可以在主機上直接操作該目錄。任何在該映象 /data 路徑的檔案會將被複制到Volume。我們可以使用 docker inspect 命令找到Volume在主機上的儲存位置:
$ docker inspect -f {{.Volumes}} container-test
你會看到類似的輸出:
map[/data:/var/lib/docker/vfs/dir/cde167197ccc3e138a14f1a4f...b32cec92e79059437a9]
這說明Docker把在 /var/lib/docker 下的某個目錄掛載到了容器內的 /data 目錄下。讓我們從主機上新增檔案到此資料夾下:
$ sudo touch /var/lib/docker/vfs/dir/cde167197ccc3e13814f...b32ce9059437a9/test-file
進入我們的容器內可以看到:
$ [email protected]:/# ls /data
test-file
只要將主機的目錄掛載到容器的目錄上,那改變就會立即生效。我們可以在Dockerfile中通過使用 VOLUME 指令來達到相同的目的:
FROM debian:wheezy
VOLUME /data
但還有另一件只有 -v 引數能做到而Dockerfile是做不到的事是在容器上掛載指定的主機目錄。例如:
$ docker run -v /home/adrian/data:/data debian ls /data
該命令將掛載主機的 /home/adrian/data 目錄到容器內的 /data 目錄上。任何在 /home/adrian/data 目錄的檔案都將會出現在容器內。這對於在主機和容器之間共享檔案是非常有幫助的,例如掛載需要編譯的原始碼。為了保證可移植性(並不是所有的系統的主機目錄都是可以用的),掛載主機目錄不需要從Dockerfile指定。當使用 -v 引數時,映象目錄下的任何檔案都不會被複制到Volume中。(譯者注:Volume會複製到映象目錄,映象不會複製到卷)
資料共享
如果要授權一個容器訪問另一個容器的Volume,我們可以使用 -volumes-from 引數來執行 docker run 。
$ docker run -it -h NEWCONTAINER --volumes-from container-test debian /bin/bash
[email protected]:/# ls /data
test-file
[email protected]:/#
值得注意的是不管container-test是否執行,它都會起作用。只要有容器連線Volume,它就不會被刪除。
資料容器
常見的使用場景是使用純資料容器來持久化資料庫、配置檔案或者資料檔案等。 官方的文件 上有詳細的解釋。例如:
$ docker run --name dbdata postgres echo "Data-only container for postgres"
該命令將會建立一個包含已經在Dockerfile裡定義過Volume的postgres映象,執行 echo 命令然後退出。當我們執行 docker ps 命令時, echo 可以幫助我們識別某映象的用途。我們可以用 -volumes-from 命令來其它容器的Volume:
$ docker run -d --volumes-from dbdata --name db1 postgres
使用資料容器的兩個注意點:
不要執行資料容器,這純粹是在浪費資源。
不要為了資料容器而使用“最小的映象”,如 busybox 或 scratch ,只使用資料庫映象本身就可以了。你已經擁有該映象,所以並不需要佔用額外的空間。
備份
如果你在用資料容器,那做備份相當容易:
$ docker run --rm --volumes-from dbdata -v $(pwd):/backup debian tar cvf /backup/backup.tar /var/lib/postgresql/data
該示例應該會將Volume裡所有的東西壓縮為一個tar包(官方的postgres Dockerfile在/var/lib/postgresql/data目錄下定義了一個Volume)
許可權與許可
通常你需要設定Volume的許可權或者為Volume初始化一些預設資料或者配置檔案。要注意的關鍵點是,在Dockerfile的 VOLUME 指令後的任何東西都不能改變該Volume,比如:
FROM debian:wheezy
RUN useradd foo
VOLUME /data
RUN touch /data/x
RUN chown -R foo:foo /data
該Docker file如預期那樣執行,我們本來希望 touch 命令在映象的檔案系統上執行,但是實際上它是在一個臨時容器的Volume上執行。如下所示:
FROM debian:wheezy
RUN useradd foo
RUN mkdir /data && touch /data/x
RUN chown -R foo:foo /data
VOLUME /data
Docker可以將映象中Volume下的檔案掛載到Volume下,並設定正確的許可權。如果你指定Volume的主機目錄將不會出現這種情況。
如果你沒有通過 RUN 指令設定許可權,那麼你就需要在容器啟動時使用 CMD 或 ENTRYPOINT 指令來執行(譯者注:CMD指令用於指定一個容器啟動時要執行的命令,與RUN類似,只是RUN是映象在構建時要執行的命令)。
刪除Volumes
這個功能可能會更加重要,如果你已經使用 docker rm 來刪除你的容器,那可能有很多的孤立的Volume仍在佔用著空間。
Volume只有在下列情況下才能被刪除:
該容器可以用 docker rm -v 來刪除且沒有其它容器連線到該Volume(以及主機目錄是也沒被指定為Volume)。注意, -v 是必不可少的。
docker run 中使用 rm 引數
除非你已經很小心的,總是像這樣來執行容器,否則你將會在 /var/lib/docker/vfs/dir 目錄下得到一些殭屍檔案和目錄,並且還不容易說出它們到底代表什麼。
從Docker IRC頻道以及 stackoverflow 的問題來看,很多人還不是很明白Docker Volume的工作原理。在這篇文章中,我會盡最大的努力來解釋Volume是如何工作的,並展示一些最佳實踐。這篇文章主要是針對那些對Volume不瞭解的Docker使用者,當然有經驗的使用者也可以通過本文了解一些Volume的細節。
如果想要了解Docker Volume,首先我們需要知道Docker的檔案系統是如何工作的。Docker映象是由多個檔案系統(只讀層)疊加而成。當我們啟動一個容器的時候,Docker會載入只讀映象層並在其上(譯者注:映象棧頂部)新增一個讀寫層。如果執行中的容器修改了現有的一個已經存在的檔案,那該檔案將會從讀寫層下面的只讀層複製到讀寫層,該檔案的只讀版本仍然存在,只是已經被讀寫層中該檔案的副本所隱藏。當刪除Docker容器,並重新啟動通過該映象啟動時,之前的更改將會丟失。在Docker中,只讀層及在頂部的讀寫層的組合被稱為 Union File System (聯合檔案系統)。
為了能夠儲存(持久化)資料以及共享容器間的資料,Docker提出了Volume的概念。簡單來說,Volume就是目錄或者檔案,它可以繞過預設的聯合檔案系統,而以正常的檔案或者目錄的形式存在於宿主機上。
我們可以通過兩種方式來初始化Volume,這兩種方式有些細小而又重要的差別。我們可以在執行時使用 -v 來宣告Volume:
$ docker run -it --name container-test -h CONTAINER -v /data debian /bin/bash
[email protected]:/#
上面的命令會將 /data 掛載到容器中,並繞過聯合檔案系統,我們可以在主機上直接操作該目錄。任何在該映象 /data 路徑的檔案會將被複制到Volume。我們可以使用 docker inspect 命令找到Volume在主機上的儲存位置:
$ docker inspect -f {{.Volumes}} container-test
你會看到類似的輸出:
map[/data:/var/lib/docker/vfs/dir/cde167197ccc3e138a14f1a4f...b32cec92e79059437a9]
這說明Docker把在 /var/lib/docker 下的某個目錄掛載到了容器內的 /data 目錄下。讓我們從主機上新增檔案到此資料夾下:
$ sudo touch /var/lib/docker/vfs/dir/cde167197ccc3e13814f...b32ce9059437a9/test-file
進入我們的容器內可以看到:
$
test-file
只要將主機的目錄掛載到容器的目錄上,那改變就會立即生效。我們可以在Dockerfile中通過使用 VOLUME 指令來達到相同的目的:
FROM debian:wheezy
VOLUME /data
但還有另一件只有 -v 引數能做到而Dockerfile是做不到的事是在容器上掛載指定的主機目錄。例如:
$ docker run -v /home/adrian/data:/data debian ls /data
該命令將掛載主機的 /home/adrian/data 目錄到容器內的 /data 目錄上。任何在 /home/adrian/data 目錄的檔案都將會出現在容器內。這對於在主機和容器之間共享檔案是非常有幫助的,例如掛載需要編譯的原始碼。為了保證可移植性(並不是所有的系統的主機目錄都是可以用的),掛載主機目錄不需要從Dockerfile指定。當使用 -v 引數時,映象目錄下的任何檔案都不會被複制到Volume中。(譯者注:Volume會複製到映象目錄,映象不會複製到卷)
資料共享
如果要授權一個容器訪問另一個容器的Volume,我們可以使用 -volumes-from 引數來執行 docker run 。
$ docker run -it -h NEWCONTAINER --volumes-from container-test debian /bin/bash
test-file
[email protected]:/#
值得注意的是不管container-test是否執行,它都會起作用。只要有容器連線Volume,它就不會被刪除。
資料容器
常見的使用場景是使用純資料容器來持久化資料庫、配置檔案或者資料檔案等。 官方的文件 上有詳細的解釋。例如:
$ docker run --name dbdata postgres echo "Data-only container for postgres"
該命令將會建立一個包含已經在Dockerfile裡定義過Volume的postgres映象,執行 echo 命令然後退出。當我們執行 docker ps 命令時, echo 可以幫助我們識別某映象的用途。我們可以用 -volumes-from 命令來其它容器的Volume:
$ docker run -d --volumes-from dbdata --name db1 postgres
使用資料容器的兩個注意點:
不要執行資料容器,這純粹是在浪費資源。
不要為了資料容器而使用“最小的映象”,如 busybox 或 scratch ,只使用資料庫映象本身就可以了。你已經擁有該映象,所以並不需要佔用額外的空間。
備份
如果你在用資料容器,那做備份相當容易:
$ docker run --rm --volumes-from dbdata -v $(pwd):/backup debian tar cvf /backup/backup.tar /var/lib/postgresql/data
該示例應該會將Volume裡所有的東西壓縮為一個tar包(官方的postgres Dockerfile在/var/lib/postgresql/data目錄下定義了一個Volume)
許可權與許可
通常你需要設定Volume的許可權或者為Volume初始化一些預設資料或者配置檔案。要注意的關鍵點是,在Dockerfile的 VOLUME 指令後的任何東西都不能改變該Volume,比如:
FROM debian:wheezy
RUN useradd foo
VOLUME /data
RUN touch /data/x
RUN chown -R foo:foo /data
該Docker file如預期那樣執行,我們本來希望 touch 命令在映象的檔案系統上執行,但是實際上它是在一個臨時容器的Volume上執行。如下所示:
FROM debian:wheezy
RUN useradd foo
RUN mkdir /data && touch /data/x
RUN chown -R foo:foo /data
VOLUME /data
Docker可以將映象中Volume下的檔案掛載到Volume下,並設定正確的許可權。如果你指定Volume的主機目錄將不會出現這種情況。
如果你沒有通過 RUN 指令設定許可權,那麼你就需要在容器啟動時使用 CMD 或 ENTRYPOINT 指令來執行(譯者注:CMD指令用於指定一個容器啟動時要執行的命令,與RUN類似,只是RUN是映象在構建時要執行的命令)。
刪除Volumes
這個功能可能會更加重要,如果你已經使用 docker rm 來刪除你的容器,那可能有很多的孤立的Volume仍在佔用著空間。
Volume只有在下列情況下才能被刪除:
該容器可以用 docker rm -v 來刪除且沒有其它容器連線到該Volume(以及主機目錄是也沒被指定為Volume)。注意, -v 是必不可少的。
docker run 中使用 rm 引數
除非你已經很小心的,總是像這樣來執行容器,否則你將會在 /var/lib/docker/vfs/dir 目錄下得到一些殭屍檔案和目錄,並且還不容易說出它們到底代表什麼。