本文收錄在容器技術學習系列文章總目錄
1、存儲卷介紹
1.1 背景
(1)docker 的 AFUS 分層文件系統
docker鏡像由多個只讀層疊加面成,啟動容器時,docker會加載只讀鏡像層並在鏡像棧頂部加一個讀寫層;
如果運行中的容器修改了現有的一個已經存在的文件,那該文件將會從讀寫層下面的只讀層復制到讀寫層,該文件版本仍然存在,只是已經被讀寫層中該文件的副本所隱藏,此即“寫時復制(COW)”機制。
(2)示意圖
描述:如果一個文件在最底層是可見的,如果在layer1上標記為刪除,最高的層是用戶看到的Layer2的層,在layer0上的文件,在layer2上可以刪除,但是只是標記刪除,用戶是不可見的,總之在到達最頂層之前,把它標記來刪除,對於最上層的用戶是不可見的,當標記一刪除,只有用戶在最上層建一個同名一樣的文件,才是可見的。
1.2 為什么要使用存儲卷
- 對於這類的操作,修改刪除等,一般效率非常低,如果對一於I/O要求比較高的應用,如redis在實現持化存儲時,是在底層存儲時的性能要求比較高。
- 假設底層運行一個存儲庫mysql,mysql本來對於I/O的要求就比較高,如果mysql又是運行在容器中自己的文件系統之上時,也就是容器在停止時,就意味着刪除,其實現數據存取時效率比較低,要避免這個限制要使用存儲卷來實現。
- 存在的問題:
- 存儲於聯合文件系統中,不易於宿主機訪問;
- 容器間數據共享不便
- 刪除容器其數據會丟失
1.3 存儲卷
(1)介紹
“卷”是容器上的一個或多個“目錄”,此類目錄可繞過聯合文件系統,與宿主機上的某個目錄“綁定(關聯)”;
類似於掛載一樣,宿主機的/data/web目錄與容器中的/container/data/web目錄綁定關系,然后容器中的進程向這個目錄中寫數據時,是直接寫在宿主機的目錄上的,繞過容器文件系統與宿主機的文件系統建立關聯關系,使得可以在宿主機和容器內共享數據庫內容,讓容器直接訪問宿主機中的內容,也可以宿主機向容器供集內容,兩者是同步的。
mount名稱空間本來是隔離的,可以讓兩個本來是隔離的文件系統,在某個子路徑上建立一定程度的綁定關系,從而使得在兩個容器之間的文件系統的某個子路徑上不再是隔離的,實現一定程度上共享的效果。
在宿主機上能夠被共享的目錄(可以是文件)就被稱為volume。
(2)存儲卷作用
優點是容器中進程所生成的數據,都保存在存儲卷上,從而脫離容器文件系統自身后,當容器被關閉甚至被刪除時,都不用擔心數據被丟失,實現數據可以脫離容器生命周期而持久,當再次重建容器時,如果可以讓它使用到或者關聯到同一個存儲卷上時,再創建容器,雖然不是之前的容器,但是數據還是那個數據,特別類似於進程的運行邏輯,進程本身不保存任何的數據,數據都在進程之外的文件系統上,或者是專業的存儲服務之上,所以進程每次停止,只是保存程序文件,對於容器也是一樣;容器就是一個有生命周期的動態對象來使用,容器關閉就是容器刪除的時候,但是它底層的鏡像文件還是存在的,可以基於鏡像再重新啟動容器。
但是容器有一個問題,一般與進程的啟動不太一樣,就是容器啟動時選項比較多,如果下次再啟動時,很容器會忘記它啟動時的選項,所以最好有一個文件來保存容器的啟動,這就是容器編排工具的作用。一般情況下,是使用命令來啟動操作docker,但是可以通過文件來讀,也就讀文件來啟動,讀所需要的存儲卷等,但是它也只是操作一個容器,這也是需要專業的容器編排工具的原因。
另一個優勢就是容器就可以不置於啟動在那台主機之上了,如幾台主機后面掛載一個NFS,在各自主機上創建容器,而容器上通過關聯到宿主機的某個目錄上,而這個目錄也是NFS所掛載的目錄中,這樣容器如果停止或者是刪除都可以不限制於只能在原先的宿主機上啟動才可以,可以實現全集群范圍內調試容器的使用,當再分配存儲、計算資源時,就不會再局限於單機之上,可以在集群范圍內建立起來,基本各種docker的編排工具都能實現此功能,但是后面嚴重依賴於共享存儲的使用。
(3)配合各服務應用狀態分析
考慮到容器應用是需要持久存儲數據的,可能是有狀態的,如果考慮使用NFS做反向代理是沒必要存儲數據的,應用可以分為有狀態和無狀態,有狀態是當前這次連接請求處理一定此前的處理是有關聯的,無狀態是前后處理是沒有關聯關系的,大多數有狀態應用都是數據持久存儲的,如mysql,redis有狀態應用,在持久存儲,如nginx作為反向代理是無狀態應用,tomcat可以是有狀態的,但是它有可能不需要持久存儲數據,因為它的session都是保存在內存中就可以的,會導致節點宕機而丟失session,如果有必要應該讓它持久,這也算是有狀態的。
應用狀態象限:是否有狀態或無狀態,是否需要持久存儲,可以定立一個正軸坐標系,第一象限中是那些有狀態需要存儲的,像mysql,redis等服務,有些有有狀態但是無需進行存儲的,像tomcat把會話保存在內存中時,無狀態也無需要存儲的數據,如各種反向代理服務器nginx,lvs請求連接都是當作一個獨立的連接來調度,本地也不需要保存數據,第四象限是無狀態,但是需要存儲數據是比較少見。
運維起來比較難的是有狀態且需要持久的,需要大量的運維經驗和大量的操作步驟才能操作起來的,如做一個Mysql主從需要運維知識、經驗整合進去才能實現所謂的部署,擴展或縮容,出現問題后修復,必須要了解集群的規模有多大,有多少個主節點,有多少個從節點,主節點上有多少個庫,這些都要一清二楚,才能修復故障,這些就強依賴於運維經驗,無狀態的如nginx一安裝就可以了,並不復雜,對於無狀態的應用可以迅速的實現復制,在運維上實現自動化是很容易的,對於有狀態的現狀比較難脫離運維人員來管理,即使是k8s在使用上也暫時沒有成熟的工具來實現。
總之:對於有狀態的應用的數據,不使用存儲卷,只能放在容器本地,效率比較低,而導致一個很嚴重問題就是無法遷移使用,而且隨着容器生命周期的停止,還不能把它刪除,只能等待下次再啟動狀態才可以,如果刪除了數據就可能沒了,因為它的可寫層是隨着容器的生命周期而存在的,所以只要持久存儲數據,存儲卷就是必需的。
docker存儲卷難度:對於docker存儲卷運行起來並不太麻煩,如果不自己借助額外的體系來維護,它本身並沒有這么強大,因為docker存儲卷是使用其所在的宿主機上的本地文件系統目錄,也就是宿主機有一塊磁盤,這塊磁盤並沒有共享給其他的docker主要,然后容器所使用的目錄,只是關聯到宿主機磁盤上的某個目錄而已,也就是容器在這宿主機上停止或刪除,是可以重新再創建的,但是不能調度到其他的主機上,這也是docker本身沒有解決的問題,所以docker存儲卷默認就是docker所在主機的本地,但是自己搭建一個共享的NFS來存儲docker存儲的數據,也可以實現,但是這個過程強依賴於運維人員的能力。
1.4 存儲卷原理
- volume於容器初始化之時會創建,由base image提供的卷中的數據會於此期間完成復制
- volume的初意是獨立於容器的生命周期實現數據持久化,因此刪除容器之時既不會刪除卷,也不會對哪怕未被引用的卷做垃圾回收操作
- 卷為docker提供了獨立於容器的數據管理機制
- 可以把“鏡像”想像成靜態文件,例如“程序”,把卷類比為動態內容,例如“數據”,於是,鏡像可以重用,而卷可以共享
- 卷實現了“程序(鏡像)"和”數據(卷)“分離,以及”程序(鏡像)“和"制作鏡像的主機”分離,用記制作鏡像時無須考慮鏡像運行在容器所在的主機的環境
1.5 存儲卷分類
Docker有兩種類型的卷,每種類型都在容器中存在一個掛載點,但其在宿主機上位置有所不同;
- Bind mount volume(綁定掛載卷):在宿主機上的路徑要人工的指定一個特定的路徑,在容器中也需要指定一個特定的路徑,兩個已知的路徑建立關聯關系
- Docker-managed volume(docker管理卷): 只需要在容器內指定容器的掛載點是什么,而被綁定宿主機下的那個目錄,是由容器引擎daemon自行創建一個空的目錄,或者使用一個已經存在的目錄,與存儲卷建立存儲關系,這種方式極大解脫用戶在使用卷時的耦合關系,缺陷是用戶無法指定那些使用目錄,臨時存儲比較適合;
2、使用存儲卷
為docker run 命令使用-v 選項即可使用volume
- docker-managed volume
docker run -it -name rbox1 -v /data busybox #/data指定docker的目錄
docker inspect -f {{.Mounts}} rbox1 查看rbox1容器的卷,卷標識符及掛載的主機目錄
- bind-mount volume
docker run -it -v HOSTDIR:VOLUMEDIR --name rbox2 busybox #宿主機目錄:容器目錄
docker inspect -f {{.Mounts}} rbox2
2.1 使用 docker-managed volume
(1)創建容器b1
[root@along ~]# docker run --name b1 -it -v /data --rm busybox
/ # ls /data/
/ #
注意:不要關閉此終端,另起一個終端進行一下操作;因為--rm 選項:一旦容器關閉,立即刪除容器
(2)查詢存儲卷信息
[root@along ~]# docker inspect b1
... ...
"Mounts": [
{
"Type": "volume",
"Name": "ca18526588ba7cbe3934086807a95415644aec17119c811338efb1db2c5f5201",
"Source": "/var/lib/docker/volumes/ca18526588ba7cbe3934086807a95415644aec17119c811338efb1db2c5f5201/_data",
"Destination": "/data",
"Driver": "local",
"Mode": "",
"RW": true,
"Propagation": ""
}
],
... ...
(3)因為inspect查詢的結果是列表的形式、所以可以精確查詢結果
[root@along ~]# docker inspect -f {{.Mounts}} b1
[{volume ca18526588ba7cbe3934086807a95415644aec17119c811338efb1db2c5f5201 /var/lib/docker/volumes/ca18526588ba7cbe3934086807a95415644aec17119c811338efb1db2c5f5201/_data /data local true }]
(4)在宿主機的存儲卷目錄添加任意東西
[root@along ~]# cd /var/lib/docker/volumes/ca18526588ba7cbe3934086807a95415644aec17119c811338efb1db2c5f5201/_data
[root@along _data]# echo "hello" > test.html
(5)在容器b1中查看,並在容器中任意修改存儲卷
/ # cat /data/test.html
hello
/ # echo "world" >> /data/test.html
(6)在宿主機上查看認證
[root@along _data]# cat test.html
hello
world
2.2 使用 docker mount volume
(1)創建容器b2
[root@along ~]# docker run --name b2 -it -v /data/volumes/b2:/data --rm busybox
/ # ls /data/
/ #
注:如果設置存儲卷的目錄不存在,會自動創建
(2)查詢存儲卷信息
[root@along ~]# docker inspect -f {{.Mounts}} b2
[{bind /data/volumes/b2 /data/web/html true rprivate}]
(3)在宿主機的存儲卷上進行簡單操作
[root@along ~]# cd /data/volumes/b2/ [root@along b2]# echo "<h1>Bustbox httpd server</h1>" > index.html
(4)在容器中驗證
/ # cat /data/index.html <h1>Bustbox httpd server</h1>
(5)即使容器被刪除,再新創建容器b3,修改存儲卷路徑,存儲卷也不會改變,證明持久功能
[root@along ~]# docker run --name b3 -it -v /data/volumes/b2:/data/web/html --rm busybox / # cat /data/web/html/index.html <h1>Bustbox httpd server</h1>
(6)多個docker容器同時關聯到同一個宿主機的目錄中
實現共享使用同一個存儲卷,容器之間的數據共享
[root@along ~]# docker run --name b4 -it -v /data/volumes/b2:/data/ --rm busybox / # cat /data/index.html <h1>Bustbox httpd server</h1>
2.3 volumes-from 基於已有容器的存儲器,創建容器
(1)先創建一個 infracon container
[root@along ~]# docker run --name infracon -it -v /data/infracon/volume/:/data/web/html busybox:latest / # echo "<h1>Nginx server</h1>" > /data/web/html/index
宿主機的存儲卷可以查詢
[root@along ~]# cat /data/infracon/volume/index.html <h1>Nginx server</h1>
(2)基於infracon container 的存儲器,啟動一個 nginx container:
[root@along ~]# docker run --name nginx --network container:infracon --volumes-from infracon -it --rm busybox:latest / # cat /data/web/html/index.html <h1>Nginx server</h1>
其實,對nginx 這個容器來說,volume 的本質沒變,它只是將infracon 容器的/data/web/html 目錄映射的主機上的目錄映射到自身的/data/web/html 目錄。
[root@along ~]# docker inspect -f {{.Mounts}} nginx
[{bind /data/infracon/volume /data/web/html true rprivate}]
但是,其好處是,可以不管其目錄的臨時性而不斷地重復使用它。
3、Volume 刪除和孤單 volume 清理
3.1 在刪除容器時刪除 volume
可以使用 docker rm -v 命令在刪除容器時刪除該容器的卷。
[root@along ~]# docker run --name web2 -v /data/ -d nginx:1.14-alpine
59a3db695835a9f1a8be97c0ca0f70bc792f5303302264dba913c7c1b6d81ebd
[root@along ~]# docker volume ls
DRIVER VOLUME NAME
local 17ac2071805d1609cf5501f81bec81d3d19467ea5a0c3428d2e77b414607775b
local 1e28bac2454d8c92ba39e8e22b9d88004284310a776e50dc379282de63c0e149
[root@along ~]# docker kill web2
web2
[root@along ~]# docker rm -v web2
web2
[root@along ~]# docker volume ls
DRIVER VOLUME NAME
local 1e28bac2454d8c92ba39e8e22b9d88004284310a776e50dc379282de63c0e149
3.2 批量刪除孤單 volumes
從上面的介紹可以看出,使用 docker run -v 啟動的容器被刪除以后,在主機上會遺留下來孤單的卷。可以使用下面的簡單方法來做清理:
[root@along ~]# docker volume ls -qf dangling=true
1e28bac2454d8c92ba39e8e22b9d88004284310a776e50dc379282de63c0e149
[root@along ~]# docker volume rm $(docker volume ls -qf dangling=true)
1e28bac2454d8c92ba39e8e22b9d88004284310a776e50dc379282de63c0e149
[root@along ~]# docker volume ls
DRIVER VOLUME NAME
3.3 github 上有很多腳本可以自動化地清理孤單卷
比如:
- https://github.com/chadoe/docker-cleanup-volumes/blob/master/docker-cleanup-volumes.sh
- https://github.com/meltwater/docker-cleanup