Docker系列05—Docker 儲存卷詳解
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
[[email protected] ~]# docker run --name b1 -it -v /data --rm busybox
/ # ls /data/
/ #
注意:不要關閉此終端,另起一個終端進行一下操作;因為--rm 選項:一旦容器關閉,立即刪除容器
(2)查詢儲存卷資訊
[[email protected] ~]# 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查詢的結果是列表的形式、所以可以精確查詢結果
[[email protected] ~]# docker inspect -f {{.Mounts}} b1
[{volume ca18526588ba7cbe3934086807a95415644aec17119c811338efb1db2c5f5201 /var/lib/docker/volumes/ca18526588ba7cbe3934086807a95415644aec17119c811338efb1db2c5f5201/_data /data local true }]
(4)在宿主機的儲存卷目錄新增任意東西
[[email protected] ~]# cd /var/lib/docker/volumes/ca18526588ba7cbe3934086807a95415644aec17119c811338efb1db2c5f5201/_data
[[email protected] _data]# echo "hello" > test.html
(5)在容器b1中檢視,並在容器中任意修改儲存卷
/ # cat /data/test.html
hello
/ # echo "world" >> /data/test.html
(6)在宿主機上檢視認證
[[email protected] _data]# cat test.html
hello
world
2.2 使用 docker mount volume
(1)建立容器b2
[[email protected] ~]# docker run --name b2 -it -v /data/volumes/b2:/data --rm busybox
/ # ls /data/
/ #
注:如果設定儲存卷的目錄不存在,會自動建立
(2)查詢儲存卷資訊
[[email protected] ~]# docker inspect -f {{.Mounts}} b2
[{bind /data/volumes/b2 /data/web/html true rprivate}]
(3)在宿主機的儲存捲上進行簡單操作
[[email protected] ~]# cd /data/volumes/b2/ [[email protected] b2]# echo "<h1>Bustbox httpd server</h1>" > index.html
(4)在容器中驗證
/ # cat /data/index.html <h1>Bustbox httpd server</h1>
(5)即使容器被刪除,再新建立容器b3,修改儲存卷路徑,儲存卷也不會改變,證明持久功能
[[email protected] ~]# 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容器同時關聯到同一個宿主機的目錄中
實現共享使用同一個儲存卷,容器之間的資料共享
[[email protected] ~]# 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
[[email protected] ~]# docker run --name infracon -it -v /data/infracon/volume/:/data/web/html busybox:latest / # echo "<h1>Nginx server</h1>" > /data/web/html/index
宿主機的儲存卷可以查詢
[[email protected] ~]# cat /data/infracon/volume/index.html <h1>Nginx server</h1>
(2)基於infracon container 的儲存器,啟動一個 nginx container:
[[email protected] ~]# 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 目錄。
[[email protected] ~]# docker inspect -f {{.Mounts}} nginx
[{bind /data/infracon/volume /data/web/html true rprivate}]
但是,其好處是,可以不管其目錄的臨時性而不斷地重複使用它。
3、Volume 刪除和孤單 volume 清理
3.1 在刪除容器時刪除 volume
可以使用 docker rm -v 命令在刪除容器時刪除該容器的卷。
[[email protected] ~]# docker run --name web2 -v /data/ -d nginx:1.14-alpine
59a3db695835a9f1a8be97c0ca0f70bc792f5303302264dba913c7c1b6d81ebd
[[email protected] ~]# docker volume ls
DRIVER VOLUME NAME
local 17ac2071805d1609cf5501f81bec81d3d19467ea5a0c3428d2e77b414607775b
local 1e28bac2454d8c92ba39e8e22b9d88004284310a776e50dc379282de63c0e149
[[email protected] ~]# docker kill web2
web2
[[email protected] ~]# docker rm -v web2
web2
[[email protected] ~]# docker volume ls
DRIVER VOLUME NAME
local 1e28bac2454d8c92ba39e8e22b9d88004284310a776e50dc379282de63c0e149
3.2 批量刪除孤單 volumes
從上面的介紹可以看出,使用 docker run -v 啟動的容器被刪除以後,在主機上會遺留下來孤單的卷。可以使用下面的簡單方法來做清理:
[[email protected] ~]# docker volume ls -qf dangling=true
1e28bac2454d8c92ba39e8e22b9d88004284310a776e50dc379282de63c0e149
[[email protected] ~]# docker volume rm $(docker volume ls -qf dangling=true)
1e28bac2454d8c92ba39e8e22b9d88004284310a776e50dc379282de63c0e149
[[email protected] ~]# 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