上一篇中簡單介紹了Docker中的基本概念和相關術語,整體上對Docker的結構和功能有了一個大概的認識,接下來的事就是使用Docker進行簡單的實際操作了。我們知道Docker的鏡像是由多個只讀層堆疊起來的,當我們啟動一個容器時,在這些只讀層的頂部會加載一個讀寫層,可以供容器寫入數據。但是當容器銷毀時,這個讀寫層也會一起銷毀,所以想要在容器中實現數據的持久化就需要使用到其他手段。
容器中的數據持久化
在Docker中,有兩種方式可將文件保存到主機中,換言之即使容器停用后文件依然存在,這兩者分別是 volume (卷)和 Bind mounts (綁定掛載)。如果你的Docker運行在Linux系統下,還可以使用 tmpfs mount 將數據寫入宿主機器的內存中,若你的Docker運行在Windows系統下你也可以選擇使用命名管道,這兩個方法在這里就不做介紹了。
關於Bind mounts方法,是將宿主機器上的一個路徑(或文件)和容器中的一個路徑(或文件)建立聯接,可以簡單理解為在建立了聯結后,容器中對這個路徑下的所有讀寫操作就是映射在宿主機器上的關聯的路徑中。在Windows系統中,與容器相關聯的路徑可以是Windows系統的路徑也可以是運行Docker守護進程的Linux虛擬機上的路徑。這種方式語義直觀簡單,並且性能不差,但是遷移的兼容性差。
關於Volume方法,我們只需要指定容器中某個路徑作為掛載點(mount point),而與之相關聯的宿主機器上的路徑是由docker自行創建與管理的,並與卷建立存儲關系。存儲卷的默認路徑是“/var/lib/docker/volumes/”。這種方式解耦了與容器掛載點相關聯的宿主機器的路徑,用戶無需指定使用哪個宿主路徑,全由Docker管理。從官方文檔的口氣來看貌似還是更推崇使用Volume。
上圖概括的描述了在Windows系統中使用Volume和Bind mount與宿主機文件系統的對應關系,當然在Linux系統下由於Docker守護進程直接寄宿在主機下,省略了虛擬機的環節是對應關系更加簡潔。接下來分別介紹Volume和Bind mounts的使用。
Volume的使用
1. 創建和管理“卷”
創建一個“卷”的命令是使用 docker volume create %卷的名稱% 的寫法,如
> docker volume create my-first-volume
列出Docker中所有的卷使用 docker volume ls 命令,如
> docker volume ls DRIVER VOLUME NAME local my-first-volume
接着可以去查看下某個卷的具體信息,使用 docker volume inspect %卷名% 命令即可,得到Json格式的卷的信息
> docker volume inspect my-first-volume [ { "CreatedAt": "2020-05-12T08:46:10Z", "Driver": "local", "Labels": {}, "Mountpoint": "/var/lib/docker/volumes/my-first-volume/_data", "Name": "my-first-volume", "Options": {}, "Scope": "local" } ]
注意看下返回的數據中, Mountpoint 這一項表示的是剛剛創建的卷所在的位置。由於本次實驗是在Windows系統中進行的,此路徑並非是Windows機上的某一路徑,而是Docker守護進程所在的Linux虛擬機上的路徑,我們可以做如下的驗證。
可以使用如下的命令依次執行可以進入承載Docker守護進程的Linux虛擬機。
獲取可訪問Docker守護程序的容器 | docker run --privileged -it -v /var/run/docker.sock:/var/run/docker.sock jongallant/ubuntu-docker-client |
運行具有完全root訪問權限的容器 | docker run --net=host --ipc=host --uts=host --pid=host -it --security-opt=seccomp=unconfined --privileged --rm -v /:/host alpine /bin/sh |
切換到主機文件系統 | chroot /host |
進入虛擬機之后,我們按照Mountpoint顯示的路徑進入,便可以看到剛剛創建的卷“my-first-volume”。可以看出這個卷其實在這里也是一個路徑,里面還存在一個_data的子路徑。
# cd /var/lib/docker/volumes/ /var/lib/docker/volumes # ls -l total 28 -rw------- 1 root root 32768 May 12 08:46 metadata.db drwxr-xr-x 3 root root 4096 May 12 08:46 my-first-volume
最后提一下,刪除卷的命令 docker volume rm %卷名% 。
2. 使用卷
在運行容器時,若要指定使用卷就需要用到 -v 或 --volume 標識來指定。之前的Docker版本中,針對單個獨立的容器一般使用 -v 或者 --volume ,針對集群(Swarm)服務使用 --mount 。然后自動Docker 17.06版本之后, --mount 也可以使用在單個獨立的容器上,而且 --mount 參數會更加的明確和詳細。
語法:
(1)使用 -v 或者 --volume ,會跟3個可選參數,注意參數的順序是不能改變,3個參數使用 “:” 分割。
- 在使用“命名卷”的情形下,第一個參數就是指這個卷的名字,並且這個名字在宿主機上是唯一的。而針對“匿名卷”來說,第一個參數便是省略的;
- 第二個參數是一個路徑,這個路徑指定的是卷掛載在容器中的路徑;
- 第三個參數是可選參數,並且是一個用逗號分隔的參數集合,來指定卷的一些屬性,比如ro(read-only)表示只讀
(2)使用 --mount ,后跟的參數是用“鍵值對”的形式來指定,鍵值對之間用逗號分隔,語法相對 -v 的語法會復雜一些,但是擺脫了參數順序的限制,參數的語義更加容易理解。
鍵 | 值 |
type | 指定掛載的類型,可選值為 volume 、 bind 或 tmpfs , 由於這里是使用卷,那么就應該選擇 volume |
source (或src) | 指定掛載的源,當使用“volume”模式時,指定卷的名稱。 若是“匿名卷”的情形,這個參數是可以省略的 |
destination (或dst、target) | 指定source指定的卷掛載到容器中的哪個路徑或則文件上 |
readonly | 如果標記了這個參數,那么卷便是只讀的 |
volume-%option% | 還可以指定更多的屬性,后面用到再說 |
接下來創建一個使用卷的容器。首先使用 -v 的語法來創建。簡單起見我們就直接使用Ubuntu這個容器。
> docker run -it --name=TestContainer01 -v my-first-volume:/usr/data ubuntu
我們將卷“my-first-volume”掛載到路徑“/usr/data”上,如果容器中不存在這個路徑會自動創建,如果容器中存在這個路徑並且這個路徑中存在子路徑和文件,那么這些路徑和文件會被復制到卷中。接下來在容器中打開“/usr/data”路徑並在其中手動創建一個路徑,並添加一個txt文件。
root@20fe562e185a:/# cd usr/data root@20fe562e185a:/usr/data# ls root@20fe562e185a:/usr/data# mkdir test_volume01 root@20fe562e185a:/usr/data# cd test_volume01/ root@20fe562e185a:/usr/data/test_volume01# touch test1.txt root@20fe562e185a:/usr/data/test_volume01# echo 3.1415926 >> "test1.txt" root@20fe562e185a:/usr/data/test_volume01# cat test1.txt 3.1415926
接下來回到Docker守護進程所在的虛擬機,檢查剛剛創建的Ubuntu容器。
# docker inspect TestContainer01
在輸出中可以看到這樣的數據,這顯示了掛載的方式是使用“volume”卷的方式,並可以看到具體的掛載路徑。
接着在Docker宿主(Linux虛擬機)上查看卷內的內容,進入卷的路徑下可以看到存在了剛剛新建的文件,並且文件中有相應的內容。
/ # cd /var/lib/docker/volumes/my-first-volume/_data /var/lib/docker/volumes/my-first-volume/_data # ls test_volume01 /var/lib/docker/volumes/my-first-volume/_data # cd test_volume01/ /var/lib/docker/volumes/my-first-volume/_data/test_volume01 # ls test1.txt /var/lib/docker/volumes/my-first-volume/_data/test_volume01 # cat test1.txt 3.1415926
如果在創建容器時,沒有給定“卷”,只指定了卷要掛載的路徑,那么Docker會幫我們自動創建一個匿名卷,並將其掛載到我們指定的路徑上。如下面這個命令
> docker run -t -i --rm -v /usr/data ubuntu
我們檢視剛剛創建的這個容器,可以發現Docker為我們創建了一個匿名卷。
來到DockerDocker宿主(Linux虛擬機)上查看卷的目錄同樣也可以看到出現了一個新的卷。
/var/lib/docker/volumes # ls -l total 32 drwxr-xr-x 3 root root 4096 May 12 11:07 424db435375a6e9c590f7ae3828abb0de75b9ed704c13b078b5820f6fbccb2b6 -rw------- 1 root root 32768 May 12 11:07 metadata.db drwxr-xr-x 3 root root 4096 May 12 08:46 my-first-volume
接下來使用 --mount 的語法來指定卷,注意target前的逗號之前不能有空格。
> docker run -it --rm --name=TestContainer02 | --mount source=my-first-volume,target=/usr/data | ubuntu
進入容器后我們去掛載卷“my-first-volume”的路徑下,仍然可以看到我們在容器TestContainer01中創建的路徑test_volume01和1.txt文件,這說明卷中的文件得到了持久化,也說明卷是可以跨容器訪問的。
root@c95b048d05c4:/# cd usr/data root@c95b048d05c4:/usr/data# ls test_volume01 root@c95b048d05c4:/usr/data# cd test_volume01/ root@c95b048d05c4:/usr/data/test_volume01# ls test1.txt root@c95b048d05c4:/usr/data/test_volume01# cat test1.txt 3.1415926
3. 容器間共享卷
當多個容器都想使用同一個或多個卷的情形下,除了顯式指定卷的名稱外,還可以使用 --volumes-from %容器% 的方法將某個容器中的卷掛載到新建的容器中。有些情形下的做法是建立一個只用來存放共用卷的容器,稱之為數據卷容器(參考這里)。其他容器通過引用數據卷容器中的卷來將這些卷掛載到自己路徑下。
創建一個用來聲明數據卷的數據卷容器,如下,為這個容器指定了兩個卷。
> docker run --name volumeContainer -d -v /var/app/config -v /var/app/resource ubuntu
創建一個新的容器直接掛載”volumeContainer“中的卷。
> docker run -t -i --rm --volumes-from volumeContainer --name MyContainer ubuntu
root@0127778fb068:/# ls /var/app/config root@0127778fb068:/# ls /var/app/resource root@0127778fb068:/# echo "data from MyContainer" > /var/app/resource/1.txt
可以看到MyContainer容器中看到這兩個卷,並向其中一個卷中寫入了一份數據。隨后再創建第三個容器去從數據卷容器中去掛載這兩個卷(當然也可以通過容器MyContainer來掛載)。
docker run -t -i --rm --volumes-from volumeContainer --name xuhyContainer ubuntu root@e841004ea40e:/# ls /var/app/resource 1.txt root@e841004ea40e:/# cat /var/app/resource/1.txt data from MyContainer
可以看到這兩個卷,並看到在第二個容器MyContainer容器中所作的修改。
Bind Mount的使用
在使用Bind Mounts方式時,容器中需要掛載的是宿主機器上的路徑或者文件,這些路徑或者文件在主機上需要事先存在,這種方式有點類似於軟鏈接。Bind Mount方式的性能是很好的,但是這種方式依賴宿主機器特定的文件目錄結構,在容器進行遷移時,特別時不同操作系統遷移時就會出現問題了,因為Windows操作系統的路徑和Linux的路徑肯定時截然不同的。
1.語法
與卷一樣,bind mounts中也是通過 -v 或者 --mount 來做參數標識的。
(1)使用 -v 或者 --volume 時同樣會跟3個可選參數,注意參數的順序是不能改變,3個參數使用 “:” 分割。
- 第一個參數是宿主機器上的路徑或者文件
- 第二個參數是在容器中要掛載的路徑或者文件
- 第三個是可選參數集合,使用英文逗號分隔,和使用卷時類似
可以看到在使用 -v 時用法和卷是很類似的,區別在於第一個參數指定的是本機的路徑。
(2)使用 --mount 時參數依然是使用鍵值對的模式
鍵 | 值 |
type | 指定掛載的類型,這里介紹使用bind mounts,type參數給定 bind 即可 |
source(或src) | Docker宿主機器的路徑或者文件 |
target(或destination,dst) | 與容器中關聯的路徑或者文件 |
readonly | 可選參數,如果標記了這個參數,那么容器對掛載的路徑是只讀的權限 |
bind-propagation | 可選參數,設定綁定傳播的屬性,不常用參數,可參考這篇文檔或這篇文檔 |
使用 -v 和使用 --mount 是有些區別的,當使用 -v 來bind一個路徑或者文件時,如果路徑或文件在宿主機器上不存在,會在主機上自動創建這個路徑。但是當我們使用 --mount 時如果路徑不存在便會報錯。
2.使用bind mount
我們先使用Windows系統的目錄進行綁定,這里使用的D盤下的dockerTest目錄掛載到容器中的“/usr/data"目錄。注意一點,這里Windows路徑的寫法不能使用我們在windows系統下常用的”D:\dockerTest“寫法,因為會把路徑中的冒號當成參數的分隔符了。
> docker run -t -i --name TestContainer03 -v/d/dockerTest:/usr/data --rm ubuntu
當執行上面命令后Docker會彈出一個提示對話框詢問共享”D:\dockerTest“目錄的權限,點擊”Share it“后路徑便綁定完成了。
在容器中打開掛載的路徑,可以看到原先存在於”D:\dockerTest“中的目錄和文件都可以訪問得到。
使用Docker的檢視命令inspect下這個容器,可以看到掛載的方式時bind,並且可看到源路徑和目標路徑。
這時我們打開Docker的設置面板,在”File sharing“項中也可以看到當前可以使用bind mount與容器關聯的路徑。其實現在你如果進入承載Docker的Linux虛擬機中,也可以看到在虛擬機的根目錄下多了一個”D:“的目錄。
接下來試着使用容器在掛載的路徑下寫入數據,如下圖所示,寫入完成后回到Windows下查看文件得到更改,關雲長得到了赤兔馬(手動滑稽~)。
以上使用了Windows系統上的路徑進行綁定,如果綁定的路徑是Linux虛擬機中的路徑結果也是一樣的,並且Docker不會再詢問Share的權限了。
> docker run -t -i --name TestContainer04 -v/var/app:/usr/data --rm ubuntu
使用 --mount 來綁定目錄的話需要注意輸入的主機上的路徑必須是已存在的,否則會報錯。使用 --mount 后賦值參數的語義更簡明,官文中也是更推薦使用。
> docker run -t -i --rm --mount type=bind,source="${pwd}",target=/usr/data --name TestContainer05 ubuntu root@8f66285f7e64:/# cd usr/data/5-general root@8f66285f7e64:/usr/data/5-general# ls GuanYunChang HuangHanSheng MaMengQi ZhangYiDe ZhaoZiLong
參考文獻:
[1] 官文:https://docs.docker.com/storage/volumes/
[2] 官文:https://docs.docker.com/storage/bind-mounts/
[3] 官文:https://docs.docker.com/storage/
[4] 博客:https://www.cnblogs.com/along21/p/10237219.html
[5] 博客:https://www.cnblogs.com/kevingrace/p/6238195.html
[6] 訪問Windows上的docker虛擬機:https://blog.jongallant.com/2017/11/ssh-into-docker-vm-windows/