1 引言
容器就相當於一個簡易的作業系統,我們在上面部署我們的環境,不可避免地產生一些資料,但是,可能由於斷電等等原因,容器退出了,那麼之前容器中的資料就不符存在,則往往不是我們想要的,更多的,我們是希望資料能夠持久儲存到硬碟中,這就需要用到資料卷。
資料卷是指一種目錄或者說檔案,其存在於一個或者多個容器中,由docker掛載到容器,但不屬於UFS(Union File System,聯合檔案系統),因此能夠繞開UFS提供一些用於持續儲存或共享資料的特性。
資料卷的設計目的就是為了實現資料的持久化,完全獨立於容器的生存週期,因此docker不會在容器刪除時刪除其掛載的資料卷。除此以外,資料卷還可以實現容器間的資料繼承和共享。在使用上,資料卷的特點:
- 資料卷可以在容器之間共享或者重用資料
- 資料卷中的更改可以直接生效
- 資料卷中的更改不會包含在映象的更新中
- 資料卷的生命週期一直持續到沒有容器使用它為止
下面就開始來研究一下新增資料卷。新增資料卷有兩種方法,第一種是通過命令直接新增,第二種是通過dockerfile新增。
2 資料卷
2.1 通過命令新增資料卷
在上一篇博文中,提到過docker run命令,docker run命令中有一個引數-v就是用來新增資料卷的,也就是說,在docker run命令中使用-v引數,可以在啟動容器時,為容器新增一個資料卷。命令格式如下:
docker run -it -v /宿主機絕對路徑目錄:/容器內絕對 映象名
主義,命令中使用的路徑最好使用絕對路徑,否則可能會報錯。另外,有的時候,可能會省略宿主機路徑,只寫容器內的資料卷路徑,這時候,docker會在/var/lib/docker/volumes/目錄下自行建立一個目錄最為主機資料卷目錄。
例如將宿主機的當前使用者目錄下名為suzu的目錄,與容器內根目錄下名為rongqi的目錄進行對映作為資料卷。
$ docker run -it -v ~/suzhu:/rongqi ubuntu root@fd4af8e0b8b3:/#
啟動容器後,我們檢視一下容器內是否有容器這一目錄:
root@fd4af8e0b8b3:/# ll rongqi total 8 drwxr-xr-x 2 root root 4096 Jun 15 02:36 ./ drwxr-xr-x 1 root root 4096 Jun 15 02:36 ../
root@fd4af8e0b8b3:/# cd rongqi root@fd4af8e0b8b3:/rongqi# touch 123 root@fd4af8e0b8b3:/rongqi# ll total 8 drwxr-xr-x 2 root root 4096 Jun 15 02:40 ./ drwxr-xr-x 1 root root 4096 Jun 15 02:40 ../ -rw-r--r-- 1 root root 0 Jun 15 02:40 123
$ cd ~/suzhu ~/suzhu$ ll total 8 drwxr-xr-x 2 root root 4096 Jun 15 10:40 ./ drwxr-xr-x 16 chb chb 4096 Jun 15 10:41 ../ -rw-r--r-- 1 root root 0 Jun 15 10:40 123
看到了沒,宿主機rongqi目錄中也同步出現了123檔案。而且,無論是宿主機的容器目錄還是容器內的rongqi目錄都是由docker自動建立的。
再次,我們嘗試在宿主機中修改123檔案的內容,看容器內的123檔案是否會修改,注意一定要使用超級管理員許可權進行寫入才行,這時候主機只有只讀許可權。如下圖所示:
儲存後退出,再次回到容器內檢視123檔案內容:
root@fd4af8e0b8b3:/rongqi# tac 123 我是宿主機,我寫入了一行
證明宿主機與容器的同步是雙向的。
如果容器停止了,宿主機新增檔案,是否會在容器重新啟動後同步到呢?
我們先停止容器,然後在宿主機suzhu目錄下建立一個111.txt檔案:
root@fd4af8e0b8b3:/rongqi# exit exit $ cd ~/suzhu $ touch 111.txt $ sudo touch 111.txt
啟動剛剛的容器,看看是否出現111.txt檔案:
$ docker start fd4af8e0b8b3 fd4af8e0b8b3 $ docker attach fd4af8e0b8b3 root@fd4af8e0b8b3:/# cd rongqi root@fd4af8e0b8b3:/rongqi# ll total 12 drwxr-xr-x 2 root root 4096 Jun 15 03:07 ./ drwxr-xr-x 1 root root 4096 Jun 15 02:40 ../ -rw-r--r-- 1 root root 0 Jun 15 03:07 111.txt -rw-r--r-- 1 root root 37 Jun 15 02:55 123
看到了嗎?在容器重啟後,在rongqi目錄下也出現了111.txt檔案。
一個容器可以有多個資料卷嗎?
答案是可以的。需要指定多個-v引數來實現。
$ docker run -it -v ~/suzhu:/rongqi -v ~/suzhu2:/rongqi2 ubuntu
繼續研究,一個目錄可以掛載多個資料卷嗎?
$ docker run -it -v ~/suzhu:/rongqi ubuntu root@deb66cd44a57:/#
容器建立成功。我們繼續嘗試使用新建的容器,看看容器間的資料卷內檔案是否會同步:
root@deb66cd44a57:/# cd rongqi root@deb66cd44a57:/rongqi# ll total 12 drwxr-xr-x 2 root root 4096 Jun 15 03:07 ./ drwxr-xr-x 1 root root 4096 Jun 15 03:40 ../ -rw-r--r-- 1 root root 0 Jun 15 03:07 111.txt -rw-r--r-- 1 root root 37 Jun 15 02:55 123 root@deb66cd44a57:/rongqi# touch 222.txt
去最初建立的容器中檢視:
$ docker attach fd4af8e0b8b3 root@fd4af8e0b8b3:/rongqi# ll total 12 drwxr-xr-x 2 root root 4096 Jun 15 03:43 ./ drwxr-xr-x 1 root root 4096 Jun 15 02:40 ../ -rw-r--r-- 1 root root 0 Jun 15 03:07 111.txt -rw-r--r-- 1 root root 37 Jun 15 02:55 123 -rw-r--r-- 1 root root 0 Jun 15 03:43 222.txt
檔案同步了。證明一個目錄可以被多個容器掛在為資料卷,從而實現容器間的資料同步和共享。
既然一個目錄可以被多個容器掛在為資料卷,就需要涉及許可權的問題了,例如有的容器需要有讀和寫的許可權,但是,有的容器,只需要有讀不能由寫入許可權,這該怎麼辦呢?其實只需要掛在目錄後面加入許可權引數就好了,引數中,rw表示有讀和寫許可權,ro表示只有du的許可權,在預設情況下,是rw就同時具有讀寫許可權。我們繼續建立一個容器,對suzhu目錄只有只讀許可權,然後嘗試在容器中建立檔案:
$ docker run -it -v ~/suzhu:/rongqi:ro ubuntu root@6df9eaeb444a:/# cd rongqi root@6df9eaeb444a:/rongqi# touch 333.txt touch: cannot touch '333.txt': Read-only file system
- 在docker run命令中新增-v 宿主機目錄:容器目錄引數形式進行新增資料卷
- 如果目錄不存在,docker會自動建立
- 同步是雙向的
- 如果不對許可權進行制定,宿主機只有只讀許可權
- 容器重啟並不影響同步
- 通過多個-v引數,一個容器掛載多個資料卷
- 一個目錄可以被多個容器掛載,從而實現容器間的資料共享同步。
- 通過rw和ro引數,可以指定容器對資料卷的讀寫許可權。
- 指定資料卷目錄時,最好使用絕對路徑,不要使用相對路徑(上面並沒有演示)
2.2 通過dockerfile新增資料卷
dockerfile內容在前面的博文中已經介紹過了。在dockerfile中有一個專門的命令VOLUME是用來新增資料卷的,VOLUME命令格式如下:
VOLUME ["資料卷目錄1", "資料卷目錄2"]
注意:資料卷目錄1和2都指的是容器內的目錄。在docker run中通過-v引數指定宿主機目錄:容器目錄的方式在dockerfile中是行不通的。這是因為dockerfile是以建立容器的模板作用而存在,可能會應用於不同的宿主機甚至不同的系統平臺,不同的平臺路徑格式也不相同。雖然不能指定宿主機中的目錄,不過,通過dockerfile建立愛你的資料卷都預設存在於/var/lib/docker/volumes/目錄下。下面我們使用dockerfile建立資料卷,首先建立一個目錄,然後進入該目錄,在目錄內建立一個名為dockerfile的檔案,寫入一下內容:
FROM ubuntu VOLUME ["/dataVolume1","/dataVolume2"] CMD echo "Success to build volume" CMD /bin/bash
在命令列下使用docker build命令建立映象:
$ docker build -t docker_volume . Sending build context to Docker daemon 2.048kB Step 1/4 : FROM ubuntu ---> 7698f282e524 Step 2/4 : VOLUME ["/dataVolume1","/dataVolume2"] ---> Running in 42cee5bb0fc8 Removing intermediate container 42cee5bb0fc8 ---> 96e9ce4e0eae Step 3/4 : CMD echo "Success to build volume" ---> Running in 04fbe86e45cc Removing intermediate container 04fbe86e45cc ---> f66a3493edc6 Step 4/4 : CMD /bin/bash ---> Running in 6f39c6dbb2d8 Removing intermediate container 6f39c6dbb2d8 ---> 42bd7a7b12ff Successfully built 42bd7a7b12ff Successfully tagged docker_volume:latest
使用映象建立容器,然後進入容器檢視是否有資料卷:
$ docker run -it 42bd7a7b12ff root@266fdc2a5ad7:/# ll total 80 drwxr-xr-x 1 root root 4096 Jun 17 13:26 ./ drwxr-xr-x 1 root root 4096 Jun 17 13:26 ../ -rwxr-xr-x 1 root root 0 Jun 17 13:26 .dockerenv* drwxr-xr-x 2 root root 4096 May 15 14:07 bin/ drwxr-xr-x 2 root root 4096 Apr 24 2018 boot/ drwxr-xr-x 2 root root 4096 Jun 17 13:26 dataVolume1/ drwxr-xr-x 2 root root 4096 Jun 17 13:26 dataVolume2/
可以看到,dataVolume1和dataVolume2兩個目錄果然是存在的。
我們可以使用docker inspect命令檢視詳細資訊:
$ docker inspect 266fdc2a5ad7 …… "Mounts": [ { "Type": "volume", "Name": "d2d2f7ee61c5f8db0b8ccc2ff08f121079c3f16c5e48cd99e1d65538ef44e389", "Source": "/var/lib/docker/volumes/d2d2f7ee61c5f8db0b8ccc2ff08f121079c3f16c5e48cd99e1d65538ef44e389/_data", "Destination": "/dataVolume1", "Driver": "local", "Mode": "", "RW": true, "Propagation": "" }, { "Type": "volume", "Name": "eacb8887be7cee176c1901f0e61ab7d51998a11366cc5a4b5fd3313c79f6ba48", "Source": "/var/lib/docker/volumes/eacb8887be7cee176c1901f0e61ab7d51998a11366cc5a4b5fd3313c79f6ba48/_data", "Destination": "/dataVolume2", "Driver": "local", "Mode": "", "RW": true, "Propagation": "" } ] ……
查詢出來的資訊太多,上面只貼出了資料卷的掛載資訊,Name是指資料卷的名稱,是自動生成的,Source是宿主機內的資料卷目錄,使用超級管理員許可權開啟/var/lib/docker/volumes/目錄可以檢視到,Destination是容器內的資料卷目錄。
dockerfile建立的資料卷性質與docker run -v命令列建立的是一樣的,這裡不再多說。
3 資料卷容器
有時候,我們有一些經常性發生變化的資料需要在多個容器之間進行共享,這時候,一個更好的選擇就是使用資料卷容器。所謂資料卷容器,從名字上也可以看出也是一個容器,不過,這個容器是專門用來為其他容器提供資料捲進行掛在的。
我們先建立一個帶資料卷的容器:
$ docker run -it -v /dbdata --name dbContainer ubuntu
然後用這個容器為其他容器提供資料卷。使用--volumes-from來掛載資料卷容器。如下所示:
~/docker_test$ docker run -it --volumes-from dbContainer --name c1 ubuntu root@50ce83189ae3:/# ll total 76 …… drwxr-xr-x 2 root root 4096 Jun 17 14:57 dbdata/ ……
可以看到,容器內出現了dbdata目錄,這就是通過--volumes-from引數與dbContainer容器掛載而來的。
資料卷容器可以同時被多個容器掛載,甚至,已經掛載了資料卷的容器可以級聯掛載別的容器。
$ docker run -it --volumes-from c1 --name c2 ubuntu root@97e7dae4a04b:/# ll total 76 …… drwxr-xr-x 2 root root 4096 Jun 17 14:57 dbdata/ ……
可見,dbContainer、c1、c2逐級掛載,這是沒有問題的,而且,--volumes-from引數所掛載的資料卷的容器並不需要保持執行狀態,也即是說dbContainer、c1、c2任意一個容器退出也不會影響其他兩個容器。
另外,刪除掛載的容器(dbContainer、c1、c2任意一個),資料卷並不會被自動刪除,如果要刪除資料卷,需要在刪除最後一個掛載著這個資料卷的時候顯式的使用docker rm -v引數來同時刪除容器。