Docker存儲卷

Docker存儲卷

COW機制

Docker鏡像由多個只讀層疊加而成,啟動容器時,Docker會載入只讀鏡像層並在鏡像棧頂部添加一個讀寫層。

如果運行中的容器修改了現有的一個已經存在的文件,那麼該文件將會從讀寫層下面的只讀層複製到讀寫層,該文件的只讀版本依然存在,只是已經被讀寫層中該文件的副本所隱藏,這就是「寫時複製(COW)」機制。

對於這種方式來說,我們去訪問一個文件,修改和刪除等一類的操作,其效率會非常的低,因為隔著很多層鏡像。

而要想繞過這種限制,我們可以通過使用存儲卷的機制來實現。

什麼是存儲卷

存儲卷就是將宿主機的本地文件系統中存在的某個目錄直接與容器內部的文件系統上的某一目錄建立綁定關係。這就意味著,當我們在容器中的這個目錄下寫入數據時,容器會將其內容直接寫入到宿主機上與此容器建立了綁定關係的目錄。

在宿主機上的這個與容器形成綁定關係的目錄被稱作存儲卷。

使用存儲卷的好處

如果容器中跑的進程的所有有效數據都保存在存儲卷中,從而脫離容器自身文件系統之後,帶來的好處是當容器關閉甚至被刪除時,只要不刪除與此容器綁定的在宿主機上的這個存儲目錄,我們就不用擔心數據丟失了。因此就可以實現數據持久,脫離容器的生命周期而持久。

我們通過這種方式管理容器,容器就可以脫離主機的限制,可以在任意一台部署了docker的主機上跑容器,而其數據則可以置於一個共享存儲文件系統上,比如nfs。

Docker的存儲卷默認情況下是使用其所在的宿主機上的本地文件系統目錄的,也就是說宿主機上有一塊屬於自己的硬碟,這個硬碟並沒有共享給其他的Docker主機,而在這台主機上啟動的容器所使用的存儲卷是關聯到此宿主機硬碟上的某個目錄之上。

這就意味著容器在這台主機上停止運行或者被刪除了再重建,只要關聯到硬碟上的這個目錄下,那麼其數據還存在。但如果在另一台主機上啟動一個新容器,那麼數據就沒了。而如果在創建容器的時候我們手動的將容器的數據掛載到一台nfs伺服器上,那麼這個問題就不再是問題了。

為什麼要使用存儲卷

關閉並重啟容器,其數據不受影響,但刪除Docker容器,則其更改將會全部丟失。

因此Docker存在的問題有:

  • 存儲於聯合掛載文件系統中,不易於宿主機訪問
  • 容器間數據共享不便
  • 刪除容器其數據會丟失

而要解決這些問題,解決方案就是使用存儲卷。

存儲卷管理方式

存儲卷(Data Volume)於容器初始化時被自動創建,由base image提供的卷中的數據會於此期間完成複製。

Volume的初衷是獨立於容器的生命周期實現數據持久化,因此刪除容器之時既不會刪除卷,也不會對未被引用的卷做垃圾回收操作。

存儲卷為Docker提供了獨立於容器的數據管理機制,我們可以把鏡像想像成靜態文件,例如「程式」,把卷類比為動態內容,例如「數據」。所以鏡像可以重用,而卷則可以共享。

卷實現了「程式(鏡像)」和「數據(卷)」的分離,以及「程式(鏡像)」和「製作鏡像的主機」的分離,用戶製作鏡像時無須再考慮鏡像運行的容器所在的主機的環境。

存儲卷的分類

Docker有兩種類型的卷,每種類型都在容器中存在一個掛載點,但其在宿主機上的位置有所不同:

  • Bind mount volume(綁定掛載卷):

    宿主機上的路徑要人工的指定一個特定的路徑在容器中也需要指定一個特定的路徑兩個已知的路徑建立關聯關係

  • Docker-managed volume(docker管理卷):

    只需要在容器內指定容器的掛載點是什麼,而被綁定宿主機下的那個目錄,是由容器引擎daemon自行創建一個空的目錄,或者使用一個已經存在的目錄,與存儲卷建立存儲關係,這種方式極大解脫用戶在使用卷時的耦合關係,缺陷是用戶無法指定那些使用目錄,臨時存儲比較適合;

容器數據管理

用戶在使用Docker的過程中,往往需要能查看容器內應用產生的數據,或者需要把容器內的數據進行備份,甚至多個容器之間進行數據的共享,這必然涉及容器的數據管理操作。

容器中管理數據主要有兩種方式:

  • 數據卷(Data Volumes)
  • 數據卷容器(Data Volumes Containers)

容器Volume使用語法:
Docker-managed volume

docker run -it --name CONTAINER_NAME -v VOLUMEDIR IMAGE_NAME

Bind mount volume

docker run -it --name CONTAINER_NAME -v HOSTDIR:VOLUMEDIR IMAGE_NAME

在容器中使用數據卷

在容器內創建一個數據卷

下面使用busybox鏡像創建一個zsl1容器,並創建一個數據卷掛載到容器的/data目錄下:

[root@localhost ~]# docker run -it --name zsl1 -v /data busybox
(另起一個終端)
root@localhost ~]# df -h
Filesystem           Size  Used Avail Use% Mounted on
overlay               17G  2.2G   15G  13% /var/lib/docker/overlay2/c4129ee11ded440f3ad6561b4dd47ef3ba66cfb6a8c5d69bc21a6f3548cdef6d/merged
[root@localhost ~]# docker inspect zsl1
......
"Mounts": [
            {
                "Type": "volume",
                "Name": "66d90c8e3765212d7cf54dde2d27e9590d1fb71b454b2d8a26a2a8b8ceae4dcc",
                "Source": "/var/lib/docker/volumes/66d90c8e3765212d7cf54dde2d27e9590d1fb71b454b2d8a26a2a8b8ceae4dcc/_data",
                "Destination": "/data",
                "Driver": "local",
                "Mode": "",
                "RW": true,
                "Propagation": ""
            }
        ],
......
# 本機里的/var/lib/docker/volumes/66d90c8e3765212d7cf54dde2d27e9590d1fb71b454b2d8a26a2a8b8ceae4dcc/_data和容器zsl1的/data建立綁定關係

# 在本機的/var/lib/docker/volumes/.../_data中創建abc
[root@localhost ~]# cd /var/lib/docker/volumes/66d90c8e3765212d7cf54dde2d27e9590d1fb71b454b2d8a26a2a8b8ceae4dcc/_data/
[root@localhost _data]# ls
[root@localhost _data]# echo "hello word" > abc
[root@localhost _data]# cat abc 
hello word

# 容器zsl1:
/ # ls
bin   data  dev   etc   home  proc  root  sys   tmp   usr   var
/ # cd data/
/data # ls
abc
/data # cat abc 
hello word

# 刪除容器zsl1,存儲卷中的文件不會刪除
[root@localhost _data]# docker rm -f zsl1
zsl1
[root@localhost _data]# docker ps -a
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES
[root@localhost _data]# ls
abc
[root@localhost _data]# cat abc 
hello word

掛載一個主機目錄作為數據卷

[root@localhost ~]# docker run -it --name zsl2 -v /mydata:/data busybox
(另開一個終端)
[root@localhost ~]# df -h
Filesystem           Size  Used Avail Use% Mounted on
overlay               17G  2.2G   15G  13% /var/lib/docker/overlay2/5cbe700b6509eb7ccd0f77b4a860afe49b8585795692ca1d18bb063f8de0ba82/merged

[root@localhost ~]# docker inspect zsl2
"Mounts": [
            {
                "Type": "bind",
                "Source": "/mydata",
                "Destination": "/data",
                "Mode": "",
                "RW": true,
                "Propagation": "rprivate"
            }
        ],
#本機里的/mydata和容器zsl2的/data建立綁定關係

# 在本機的/mydata中創建abc
[root@localhost ~]# cd /mydata/
[root@localhost mydata]# ls
[root@localhost mydata]# echo 'hello word' > abc
[root@localhost mydata]# cat abc 
hello word

# 容器zsl2:
/ # ls
bin   data  dev   etc   home  proc  root  sys   tmp   usr   var
/ # cd data/
/data # cat abc 
hello word

# 刪除容器zsl2,存儲卷中的文件不會刪除
[root@localhost mydata]# docker rm -f zsl2
zsl2
[root@localhost mydata]# docker ps -a
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES
[root@localhost mydata]# ls
abc
[root@localhost mydata]# cat abc 
hello word

上面的命令載入主機的mydata目錄到容器的/webapp目錄:
這個功能在進行測試的時候非常方便,比如用戶可以放置一些程式或數據到本地目錄中,然後在容器內運行和使用。另外,本地目錄的路徑必須是絕對路徑,如果目錄不存在,Docker會自動創建。

Docker掛載數據卷的默認許可權是讀寫(rw),用戶也可以通過(ro)指定為只讀:

[root@localhost ~]# docker run -it --name zsl2 -v /mydata:/data:ro busybox

加了:ro以後,容器內掛載的數據卷的數據就無法修改了。

掛載一個本地主機文件作為數據卷

-v選項也可以從主機掛載單個文件到容器中作為數據卷:

[root@localhost ~]# docker run -it -v ~/.bash_history:/.bash_history centos /bin/bash

這樣就可以記錄在容器輸入過的命令歷史了。

如果直接掛載一個文件到容器,使用文件編輯工具,包括vi或者sed去修改文件內容的時候,可能會造成inode的改變,這樣將會導致錯誤。所以推薦的方式是直接掛載文件所在的目錄。

數據卷容器

如果用戶需要在容器之間共享一些持續更新的數據,最簡單的方式是使用數據卷容器。數據卷容器其實就是一個普通的容器,專門用它提供數據卷供其他容器掛載使用,方法如下:

首先,創建一個數據卷容器dbdata,並在其中創建一個數據卷掛載到/dbdata:

[root@localhost ~]# docker run -dit --rm -v /dbdata --name dbdata centos
be8dec2b258efc53277b73f2544b2d112b97918037fb6ab2340a405e0f0f330a
[root@localhost ~]# docker ps
CONTAINER ID   IMAGE     COMMAND       CREATED         STATUS         PORTS     NAMES
be8dec2b258e   centos    "/bin/bash"   5 seconds ago   Up 3 seconds             dbdata

然後可以在其他容器中使用–volumes-from來掛載dbdata容器中的數據卷,例如創建db1和db2兩個容器,並從dbdata容器掛載數據卷:

[root@localhost ~]# docker run -dit --name db1 --volumes-from dbdata centos
4f1ef71cfcbefdddfac7ea0081398a319b275401399ae433bdcfa3107063a417
[root@localhost ~]# docker run -dit --name db2 --volumes-from dbdata centos
f10bd5829df2869bd33660a9ee2c7de2491e13722fd19607b63ae94fccbb5d43

此時,容器db1和db2都掛載同一個數據卷到相同的/dbdata目錄。三個容器任何一方在該目錄下的寫入,其他容器都可以看到。
例如,在dbdata容器中創建一個abc文件:

[root@localhost ~]# docker exec -it dbdata /bin/bash
[root@be8dec2b258e /]# ls
bin  dbdata  dev  etc  home  lib  lib64  lost+found  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var
[root@be8dec2b258e /]# cd dbdata/
[root@be8dec2b258e dbdata]# echo 'hello word' > abc
[root@be8dec2b258e dbdata]# ls
abc
[root@be8dec2b258e dbdata]# cat abc 
hello word

在db1容器中查看:

[root@localhost ~]# docker exec -it db1 /bin/bash
[root@4f1ef71cfcbe /]# cd dbdata/
[root@4f1ef71cfcbe dbdata]# ls
abc
[root@4f1ef71cfcbe dbdata]# cat abc  
hello word

在db1容器中查看:

[root@localhost ~]# docker exec -it db2 /bin/bash
[root@f10bd5829df2 /]# cd dbdata/
[root@f10bd5829df2 dbdata]# ls
abc
[root@f10bd5829df2 dbdata]# cat abc 
hello word

可以多次使用–volumes-from參數來從多個容器掛載多個數據卷。還可以從其他已掛載了容器卷的容器來掛載數據卷:

[root@localhost ~]# docker run -it --name db3 --volumes-from db1 centos
[root@2401a2dcba43 /]# ls
bin  dbdata  dev  etc  home  lib  lib64  lost+found  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var
[root@2401a2dcba43 /]# cd dbdata/
[root@2401a2dcba43 dbdata]# cat abc 
hello word

使用–volumes-from參數所掛載數據卷的容器自身並不需要保持在運行狀態。

如果刪除了掛載的容器(包括dbdata、db1和db2),數據卷並不會被自動刪除。如果要刪除一個數據卷,必須在刪除最後一個還掛載著它的容器時顯式使用docker rm -v命令來指定同時刪除關聯的容器。

利用數據卷容器遷移數據

可以利用數據卷容器對其中的數據卷進行備份、恢復,以實現數據的遷移。

備份

使用下面的命令來備份dbdata數據卷容器內的數據卷:

# 創建容器dbdata
[root@localhost ~]# docker run -dit --name dbdata -v /dbdata centos
963e962cfece193a42af85b1492af6c7dcd4d680bcface6d0661925f5ee1e008

# 進入容器dbdata
[root@localhost ~]# docker exec -it dbdata /bin/bash
[root@963e962cfece /]# ls
bin  dbdata  dev  etc  home  lib  lib64  lost+found  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var

# 在dbdata下創建數據
[root@963e962cfece /]# cd dbdata/ 
[root@963e962cfece dbdata]# echo 'hello word' > abc
[root@963e962cfece dbdata]# ls
abc
[root@963e962cfece dbdata]# dd if=/dev/zero of=test bs=1M count=10
10+0 records in
10+0 records out
10485760 bytes (10 MB, 10 MiB) copied, 0.00523796 s, 2.0 GB/s
[root@963e962cfece dbdata]# ls
abc  test
[root@963e962cfece dbdata]# du -sh *
4.0K	abc
10M	test

# 創建備份數據的容器backup
[root@localhost ~]#  docker run --name worker --volumes-from dbdata -v $(pwd):/backup centos tar cvf /backup/backup.tar /dbdata
tar: Removing leading `/' from member names
/dbdata/
/dbdata/abc
/dbdata/test
[root@localhost ~]# ls
anaconda-ks.cfg  backup.tar

# 刪除備份文件,測試啟動容器後是否會在備份
[root@localhost ~]# rm -f backup.tar 
[root@localhost ~]# docker ps -a
CONTAINER ID   IMAGE     COMMAND                  CREATED              STATUS                          PORTS     NAMES
5d21d931d708   centos    "tar cvf /backup/bac…"   About a minute ago   Exited (0) About a minute ago             worker
963e962cfece   centos    "/bin/bash"              10 minutes ago       Up 10 minutes                             dbdata
[root@localhost ~]# docker restart worker
worker

# 由此可見,啟動容器後會執行備份操作
[root@localhost ~]# ls
anaconda-ks.cfg  backup.tar

這個命令稍微有點複雜,具體分析下。
首先利用centos鏡像創建了一個容器worker。使用–volumes-from dbdata參數來讓worker容器掛載dbdata容器的數據卷(即dbdata數據卷);使用-v $(pwd):/backup參數來掛載本地的當前目錄到worker容器的/backup目錄。
worker容器啟動後,使用了tar cvf /backup/backup.tar /dbdata命令來將/dbdata下內容備份為容器內的/backup/backup.tar,即宿主主機當前目錄下的backup.tar。

恢復

如果要恢複數據到一個容器,可以按照下面的操作。首先創建一個帶有數據卷的容器dbdata2:

[root@localhost ~]# docker run -it --name dbdata2 -v /dbdata centos /bin/bash

然後創建另一個新的容器,掛載dbdata2容器,並使用untar解壓備份文件到所掛載的容器卷中即可:

[root@localhost ~]# docker run --rm --volumes-from dbdata2 -v $(pwd):/backup centos tar xvf /backup/backup.tar
dbdata/
dbdata/abc
dbdata/test
[root@localhost ~]# docker exec -it dbdata2 /bin/bash
[root@2f0cf0a0f425 /]# ls 
bin  dbdata  dev  etc  home  lib  lib64  lost+found  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var
[root@2f0cf0a0f425 /]# cd dbdata/
[root@2f0cf0a0f425 dbdata]# ls
abc  test
[root@2f0cf0a0f425 dbdata]# cat abc 
hello word
[root@2f0cf0a0f425 dbdata]# du -sh test 
10M	test
Tags: