這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
理解 Docker 中的 Volumes
Why
本文翻譯自 Understanding Volumes in Docker,和原文作者一樣,我在剛接觸 Docker 的時候,對 Docker 中 Volumes 的概念也有些困惑,docker run -it -v /some/dir:/another/dir someImage:someTag 中的 -v 選項和 Dockerfile 中定義的 VOLUME 能達到相同的效果嗎?看完這篇文章後才豁然開朗,所以翻譯過來一方面鞏固理解,另一方面分享一下。
譯文
從 Docker IRC Channel (Ross:用了一下這裡提到的 Docker IRC Channel,順便看了關於 Python 和 C++ 的聊天室,原來網路上還有一群人在用古老的聊天室,聯想到紙牌屋裡提到的 Deep Web,果然我們平時能看到的網上資訊只是冰山一角)和 StackOverflow 中經常看到的問題可以很清楚地看出,很多人對 Volumes 在 Docker 中如何工作是很困惑的。在這篇文章中,我將解釋 Volumes 如何工作以及使用它的一些 Best Practice。雖然本文定位於對 Docker 感興趣後者只有一點知識的人群,但是 Docker 的練家子也很可能從中學到一些東西,因為本文討論了一些很多人不是很瞭解的關於 Volumes 的話題。
如果要理解什麼是 Docker 中的 Volume,我們首先需要搞清楚 Docker 的檔案系統是如何工作的。Docker 的 Image 是以一系列 Layers 的形式儲存的。當我們啟動一個 Container 的時候,Docker 在唯讀 Image 的上層添加一個可讀寫的 Layer 。當一個運行中的 Container 修改了一個已存在的檔案時,這個檔案會被唯讀 Layer 中拷貝到最上層的可讀寫 Layer 中並修改儲存,這樣可讀寫 Layer 中的版本會隱藏唯讀 Layer 中受影響的檔案,而不是刪除原始檔案,原始檔案會一直存在於最初的 Image 中。當這個 Docker 的 Container 被刪除之後,重新使用這個 Image 建立一個新的 Container 會丟失之前 Container 中進行的所有修改。總之,Docker 會在調用者一系列唯讀 Layers 的時候在 Union File System 的最上層添加一個可讀寫的新 Layer。
為了能夠儲存或者說持久化資料並將資料在不同的 Containers 之間共用,Docker 提出了 Volumes 的概念。非常簡單,Volumes 就是獨立於預設的 Union File System 並且存在於 host 檔案系統中的普通檔案夾或者檔案。
有兩種方式可以建立 Volumes,這兩種方式有些細微的差別,需要小心對待。我們可以在建立一個 Container 的時候聲明一個 Volume:
$ docker run -it --name container-test -h CONTAINER -v /data debian /bin/bashroot@CONTAINER:/# ls /dataroot@CONTAINER:/#
這樣做會使用 Container 容器中的 /data 目錄獨立於 Union File System存在而且可以被 host 訪問。這個 Image 中 /data 目錄下的所有檔案都會被拷貝到這個 Volume 裡面,我們可以通過在 host 上執行 docker inspect 命令來得到這個 Volume 在 host 上的具體路徑。開啟一個新的命令列視窗並保持當前 Container 處於運行狀態並輸入下面的命令:
$ docker inspect -f container-test
我們會看到類似於下面的資訊:
map[/data:/var/lib/docker/vfs/dir/cde167197ccc3e138a14f1a4f7c0d65b32cecbada822b0db4cc92e79059437a9]
這說明 Docker 將 /var/lib/docker 目錄下的一個檔案夾在這個 Container 中掛載為 /data 目錄。現在我們從 host 在這個目錄中放入一個檔案:
$ sudo touch /var/lib/docker/vfs/dir/cde167197ccc3e138a14f1a4f7c0d65b32cecbada822b0db4cc92e79059437a9/test-file
然後回到我們之前建立的 Container 中並查看一下 /data 目錄:
$ root@CONTAINER:/# ls /datatest-file
上面的修改即時生效了,因為 Container 中的 /data 目錄就是 host 上的目錄的掛載。
我們可以通過在 Dockerfile 中定義 VOLUME 標籤來達到同樣的效果:
FROM debian:wheezyVOLUME /data
但是這裡和建立 Docker Container 的時候使用 -v 參數有一個區別,具體地說就是__-v__ 參數可以做到但是 Docker VOLUME 卻做不到的就是:我們可以掛載 host 上面的一個特定目錄到 Container:
$ docker run -v /home/adrian/data:/data debian ls /data
這個命令會將 host 上面的 /home/adrian/data 目錄掛載為 Container 的 /data 目錄。所有 /home/adrian/data 中已存在的檔案以及檔案夾都可以在 Container 中馬上訪問。這在需要 host 和 Container 共用資料的時候非常有用。舉個例子來說,將原始碼目錄掛載到 Container 中進行編譯。host 中用於 Volumes 的目錄不能在 Dockerfile 中進行定義,這是為了保證可移植性,因為無法保證所有 host 都有相同的目錄。當 -v 參數被使用時,任何 Image 中掛載後的目錄中的內容都 不會 被拷貝到這個 VOLUME 中。
共用資料
為了使得一個 Container 可以訪問另一個 Container 中的資料,我們可以在執行 docker run 命令的時候使用 –volumes-from 參數:
$ docker run -it -h NEWCONTAINER --volumes-from container-test debian /bin/bashroot@NEWCONTAINER:/# ls /datatest-fileroot@NEWCONTAINER:/#
需要注意的是,無論 container-test 這個 Container 是否運行。當一個 Volume 有 Container 與其串連時是不會被刪除的。
資料容器
使用一個 data-only 的 Container 來進行持久化資料庫、設定檔以及資料檔案等已經成為一個通行的做法。Docker website has some good documentation 就這個話題進行了討論。舉例來說:
$ docker run --name dbdata postgres echo "Data-only container for postgres"
這個命令會建立一個包含 Dockerfile 中定義的 Volume 的 Postgres Container,執行 echo 命令並退出。這裡 echo 命令對於協助我們瞭解 Container 的用途是很有用的。我們可以在建立另一個 Container 的時候來使用這裡定義的 Volume:
$ docker run -d --volumes-from dbdata --name db1 postgres
使用資料容器的時候有兩點需要特別注意:
- 沒有必要讓資料容器處於運行狀態,運行它純屬浪費資源。
- 不要使用 busybox 或者 scratch 這些最小 Images 用於資料容器,就是用資料庫 Image 本身就好。既然我們已經下載了資料庫 Image,建立一個 Container 不會增加更多資源佔用,而且 Volumes 還可以用來從 Image 的掛載目錄中擷取一些資料。
許可權和所有者
很多時候,我們會需要對一個 Volume 進行許可權和所有者控制,後者在初始化 Volume 的時候放入一些預設資料或者設定檔。關鍵一點在於認識到所有 VOLUME 標籤之後對於掛載目錄的修改都不會生效,比如:
FROM debian:wheezyRUN useradd fooVOLUME /dataRUN touch /data/xRUN chown -R foo:foo /data
這個 Dockerfile 不會按照我們的預期進行工作。我們期望 touch 命令的結果儲存在 Image 的檔案系統中,但是其實它只在一個臨時的 Container 中執行了而不會被儲存。下面的寫法才是正確的:
FROM debian:wheezyRUN useradd fooRUN mkdir /data && touch /data/xRUN chown -R foo:foo /dataVOLUME /data
Docker 會智能地從 Image 中掛載後的目錄裡拷貝所有內容到這個 Volume 下面並賦予合適的許可權和所有者。當我們使用 -v 參數將 host 上面的某個目錄掛載到 Container 中時,拷貝不會發生,以避免 host 上的內容被 Image 中的內容覆蓋。
雖然我們無法使用 RUN 命令進行許可權和所有者的設定,但是我們可以使用 CMD 或者 ENTRYPOINT 在 Container 被建立後運行指令碼來做這些事情。
刪除 Volumes
和很多預想的不同,當我們使用 docker rm 刪除 Containers 的時候,我們可能不會將這些 Containers 關聯的 Volumes 刪除。
Volumes 只有在下面的情況下才會被刪除:
- Container 被使用 docker rm -v 命令刪除並且沒有其他 Containers 於這個 Volume 關聯,也沒有 host 上的目錄和這個 Volume 關聯,-v 在這裡是必需的。
- -rm 參數和 docker run 是一對。
我們要遵循上面的規則進行 Containers 的管理,否則時間久了就會在 /var/lib/docker/vfs/dir 下產生很多垃圾檔案,而且無法分辨哪些可以被安全刪除。
更多資料
下面的資源提供了這個話題的更深層討論並且是本文寫作的參考:
- Data-only Container Madness
- Docker In-Depth: Volumes
- Managing Data in Containers
最後,關於管理 Volumes,我們有很多工具可以選擇:Proposal #8484
聲明:本文譯文的英文著作權屬於 原作者,本文轉載請註明出處。
-- EOF --
- Ubuntu 14.04 系統基於 Gunicorn 和 Nginx 部署 Flask 應用→
- ← Dockerfile 最佳實務
聲明: 本文採用 BY-NC-SA 協議進行授權. 轉載請註明轉自: 理解 Docker 中的 Volumes