Docker映象由多個唯讀層疊加而成,啟動容器時,Docker會載入唯讀映象層並在映象棧頂部新增一個讀寫層。
如果執行中的容器修改了現有的一個已經存在的檔案,那麼該檔案將會從讀寫層下面的唯讀層複製到讀寫層,該檔案的唯讀版本依然存在,只是已經被讀寫層中該檔案的副本所隱藏,這就是「寫時複製(COW)」機制。
描述:如果一個檔案在最底層是可見的,如果在layer1上標記為刪除,最高的層是使用者看到的Layer2的層,在layer0上的檔案,在layer2上可以刪除,但是隻是標記刪除,使用者是不可見的,總之在到達最頂層之前,把它標記來刪除,對於最上層的使用者是不可見的,當標記一刪除,只有使用者在最上層建一個同名一樣的檔案,才是可見的。
對於這種方式來說,我們去存取一個檔案,修改和刪除等一類的操作,其效率會非常的低,因為隔著很多層映象。
而要想繞過這種限制,我們可以通過使用儲存卷的機制來實現。
儲存卷就是將宿主機的本地檔案系統中存在的某個目錄直接與容器內部的檔案系統上的某一目錄建立繫結關係。這就意味著,當我們在容器中的這個目錄下寫入資料時,容器會將其內容直接寫入到宿主機上與此容器建立了繫結關係的目錄。
在宿主機上的這個與容器形成繫結關係的目錄被稱作儲存卷。
如果容器中跑的程序的所有有效資料都儲存在儲存卷中,從而脫離容器自身檔案系統之後,帶來的好處是當容器關閉甚至被刪除時,只要不刪除與此容器繫結的在宿主機上的這個儲存目錄,我們就不用擔心資料丟失了。因此就可以實現資料持久,脫離容器的生命週期而持久。
我們通過這種方式管理容器,容器就可以脫離主機的限制,可以在任意一臺部署了docker的主機上跑容器,而其資料則可以置於一個共用儲存檔案系統上,比如nfs。
Docker的儲存卷預設情況下是使用其所在的宿主機上的本地檔案系統目錄的,也就是說宿主機上有一塊屬於自己的硬碟,這個硬碟並沒有共用給其他的Docker主機,而在這臺主機上啟動的容器所使用的儲存卷是關聯到此宿主機硬碟上的某個目錄之上。
這就意味著容器在這臺主機上停止執行或者被刪除了再重建,只要關聯到硬碟上的這個目錄下,那麼其資料還存在。但如果在另一臺主機上啟動一個新容器,那麼資料就沒了。而如果在建立容器的時候我們手動的將容器的資料掛載到一臺nfs伺服器上,那麼這個問題就不再是問題了。
關閉並重啟容器,其資料不受影響,但刪除Docker容器,則其更改將會全部丟失。
因此Docker存在的問題有:
而要解決這些問題,解決方案就是使用儲存卷。
儲存卷(Data Volume)於容器初始化時被自動建立,由base image提供的卷中的資料會於此期間完成複製。
Volume的初衷是獨立於容器的生命週期實現資料持久化,因此刪除容器之時既不會刪除卷,也不會對未被參照的卷做垃圾回收操作。
儲存卷為Docker提供了獨立於容器的資料管理機制,我們可以把映象想象成靜態檔案,例如「程式」,把卷類比為動態內容,例如「資料」。所以映象可以重用,而卷則可以共用。
卷實現了「程式(映象)」和「資料(卷)」的分離,以及「程式(映象)」和「製作映象的主機」的分離,使用者製作映象時無須再考慮映象執行的容器所在的主機的環境。
Docker有兩種型別的卷,每種型別都在容器中存在一個掛載點,但其在宿主機上的位置有所不同:
Bind mount volume(繫結掛載卷):
在宿主機上的路徑要人工的指定一個特定的路徑,在容器中也需要指定一個特定的路徑,兩個已知的路徑建立關聯關係
Docker-managed volume(docker管理卷):
只需要在容器內指定容器的掛載點是什麼,而被繫結宿主機下的那個目錄,是由容器引擎daemon自行建立一個空的目錄,或者使用一個已經存在的目錄,與儲存卷建立儲存關係,這種方式極大解脫使用者在使用卷時的耦合關係,缺陷是使用者無法指定那些使用目錄,臨時儲存比較適合;
使用者在使用Docker的過程中,往往需要能檢視容器內應用產生的資料,或者需要把容器內的資料進行備份,甚至多個容器之間進行資料的共用,這必然涉及容器的資料管理操作。
容器中管理資料主要有兩種方式:
容器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