淺談docker學(xué)習(xí)之docker數(shù)據(jù)卷(volume)
1.什么是數(shù)據(jù)卷volume
為了了解什么是Docker Volume,首先我們需要明確Docker內(nèi)的文件系統(tǒng)是如何工作的。Docker鏡像被存儲(chǔ)在一系列的只讀層。當(dāng)我們開(kāi)啟一個(gè)容器,Docker讀取只讀鏡像并添加一個(gè)讀寫(xiě)層在頂部。如果正在運(yùn)行的容器修改了現(xiàn)有的文件,該文件將被拷貝出底層的只讀層到最頂層的讀寫(xiě)層。在讀寫(xiě)層中的舊版本文件隱藏于該文件之下,但并沒(méi)有被不破壞 - 它仍然存在于鏡像以下。當(dāng)Docker的容器被刪除,然后重新啟動(dòng)鏡像時(shí),將開(kāi)啟一個(gè)沒(méi)有任何更改的新的容器 - 這些更改會(huì)丟失。此只讀層及在頂部的讀寫(xiě)層的組合被Docker稱(chēng)為Union File System(聯(lián)合文件系統(tǒng))。
為了能夠保存(持久)數(shù)據(jù)以及共享容器間的數(shù)據(jù),Docker提出了Volumes的概念。很簡(jiǎn)單,volumes是目錄(或者文件),它們是外部默認(rèn)的聯(lián)合文件系統(tǒng)或者是存在于宿主文件系統(tǒng)正常的目錄和文件。
2.為什么使用數(shù)據(jù)卷volume
Docker的鏡像是由一系列的只讀層組合而來(lái),當(dāng)啟動(dòng)一個(gè)容器的時(shí)候,Docker加載鏡像的所有只讀層,并在最上層加入一個(gè)讀寫(xiě)層。這個(gè)設(shè)計(jì)使得Docker可以提高鏡像構(gòu)建、存儲(chǔ)和分發(fā)的效率,節(jié)省了時(shí)間和存儲(chǔ)空間,然而也存在如下問(wèn)題。
(1)容器中的文件在宿主機(jī)上存在形式復(fù)雜,不能在宿主機(jī)上很方便的對(duì)容器中的文件進(jìn)行訪問(wèn)
(2)多個(gè)容器之間的數(shù)據(jù)無(wú)法共享
(3)當(dāng)刪除容器時(shí),容器產(chǎn)生的數(shù)據(jù)將丟失
為了解決這些問(wèn)題,Docker引入了數(shù)據(jù)卷(volume)機(jī)制。volume是存在一個(gè)或多個(gè)容器中的特定文件或文件夾,這個(gè)目錄能夠獨(dú)立于聯(lián)合文件系統(tǒng)的形式在宿主機(jī)中存在,并為數(shù)據(jù)的共享與持久提供一下便利。
(1)volume在容器創(chuàng)建時(shí)就初始化,在容器運(yùn)行時(shí)就可以使用其中的文件
(2)volume能在不同的容器之間共享和重用
(3)對(duì)volume中的數(shù)據(jù)的操作會(huì)馬上生效
(4)對(duì)volume中數(shù)據(jù)操作不會(huì)影響到鏡像本身
(5)volume的生存周期獨(dú)立于容器的生存周期,即使刪除容器,volume仍然會(huì)存在,沒(méi)有任何容器使用的volume也不會(huì)被Docker刪除
3.如何使用數(shù)據(jù)卷
3.1 從容器掛載volume(-v /path)
在使用docker run創(chuàng)建新容器的時(shí)候,可以使用-v 標(biāo)簽為容器添加數(shù)據(jù)卷volume,以下用法是從容器中的某個(gè)文件夾創(chuàng)建volume,如果容器中指定的文件夾不存在,會(huì)自動(dòng)生成

在上面的概念中,有說(shuō)道,宿主機(jī)應(yīng)該會(huì)有一個(gè)文件夾綁定掛載到容器中的volume掛載點(diǎn),那默認(rèn)的宿主機(jī)上的文件夾在哪呢,使用docker inspect命令,查看下容器詳情(CRT令起一個(gè)SSH終端)
[root@localhost ~]# docker inspect volume-test01
[
{
"Id": "81a74152e6f45a3f780ac7cdc37c9a089814f9a70aad1d27747093ca3c3dae3e",
"Created": "2016-08-25T07:48:55.942949334Z",
"Path": "/bin/bash",
"Args": [],
"State": {
"Status": "running",
"Running": true,
"Paused": false,
"Restarting": false,
"OOMKilled": false,
"Dead": false,
"Pid": 10199,
"ExitCode": 0,
"Error": "",
"StartedAt": "2016-08-25T07:48:56.777918888Z",
"FinishedAt": "0001-01-01T00:00:00Z"
},
"Image": "sha256:4fd21defa24c8c07b3689b267a63d563ca0e26ef931b329fc3f3d46efb5bba2d",
"ResolvConfPath": "/var/lib/docker/containers/81a74152e6f45a3f780ac7cdc37c9a089814f9a70aad1d27747093ca3c3dae3e/resolv.conf",
"HostnamePath": "/var/lib/docker/containers/81a74152e6f45a3f780ac7cdc37c9a089814f9a70aad1d27747093ca3c3dae3e/hostname",
"HostsPath": "/var/lib/docker/containers/81a74152e6f45a3f780ac7cdc37c9a089814f9a70aad1d27747093ca3c3dae3e/hosts",
"LogPath": "",
"Name": "/volume-test01",
"RestartCount": 0,
"Driver": "devicemapper",
"MountLabel": "system_u:object_r:svirt_sandbox_file_t:s0:c47,c332",
"ProcessLabel": "system_u:system_r:svirt_lxc_net_t:s0:c47,c332",
"AppArmorProfile": "",
"ExecIDs": null,
"HostConfig": {
"Binds": null,
"ContainerIDFile": "",
"LogConfig": {
"Type": "journald",
"Config": {}
},
"NetworkMode": "default",
"PortBindings": {},
"RestartPolicy": {
"Name": "no",
"MaximumRetryCount": 0
},
"VolumeDriver": "",
"VolumesFrom": null,
"CapAdd": null,
"CapDrop": null,
"Dns": [],
"DnsOptions": [],
"DnsSearch": [],
"ExtraHosts": null,
"GroupAdd": null,
"IpcMode": "",
"Links": null,
"OomScoreAdj": 0,
"PidMode": "",
"Privileged": false,
"PublishAllPorts": false,
"ReadonlyRootfs": false,
"SecurityOpt": null,
"UTSMode": "",
"ShmSize": 67108864,
"ConsoleSize": [
0,
0
],
"Isolation": "",
"CpuShares": 0,
"CgroupParent": "",
"BlkioWeight": 0,
"BlkioWeightDevice": null,
"BlkioDeviceReadBps": null,
"BlkioDeviceWriteBps": null,
"BlkioDeviceReadIOps": null,
"BlkioDeviceWriteIOps": null,
"CpuPeriod": 0,
"CpuQuota": 0,
"CpusetCpus": "",
"CpusetMems": "",
"Devices": [],
"KernelMemory": 0,
"Memory": 0,
"MemoryReservation": 0,
"MemorySwap": 0,
"MemorySwappiness": -1,
"OomKillDisable": false,
"PidsLimit": 0,
"Ulimits": null
},
"GraphDriver": {
"Name": "devicemapper",
"Data": {
"DeviceId": "29",
"DeviceName": "docker-253:0-101330881-9cb32851050b1707022b475489686582a272d883a56a8ff52f3344f56b65639f",
"DeviceSize": "10737418240"
}
},
"Mounts": [
{
"Name": "5ddd734c53a38a78a9f739157c63074b4aff736d4045925616d7753402304137",
"Source": "/var/lib/docker/volumes/5ddd734c53a38a78a9f739157c63074b4aff736d4045925616d7753402304137/_data",
"Destination": "/opt/vol-data",
"Driver": "local",
"Mode": "",
"RW": true,
"Propagation": ""
}
],
"Config": {
"Hostname": "81a74152e6f4",
"Domainname": "",
"User": "",
"AttachStdin": true,
"AttachStdout": true,
"AttachStderr": true,
"Tty": true,
"OpenStdin": true,
"StdinOnce": true,
"Env": null,
"Cmd": [
"/bin/bash"
],
"Image": "test/mycentos:v1.0",
"Volumes": {
"/opt/vol-data": {}
},
"WorkingDir": "",
"Entrypoint": null,
"OnBuild": null,
"Labels": {}
},
"NetworkSettings": {
"Bridge": "",
"SandboxID": "c73841812aab480cf8e6a02071e36951dceb002d7b36f7dc0b38ccd9db833ba5",
"HairpinMode": false,
"LinkLocalIPv6Address": "",
"LinkLocalIPv6PrefixLen": 0,
"Ports": {},
"SandboxKey": "/var/run/docker/netns/c73841812aab",
"SecondaryIPAddresses": null,
"SecondaryIPv6Addresses": null,
"EndpointID": "3e492b611d9bd2c202c8a6c8fe4b4a755393545348b1aeb4e60995609dabb07c",
"Gateway": "172.17.0.1",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"IPAddress": "172.17.0.2",
"IPPrefixLen": 16,
"IPv6Gateway": "",
"MacAddress": "02:42:ac:11:00:02",
"Networks": {
"bridge": {
"IPAMConfig": null,
"Links": null,
"Aliases": null,
"NetworkID": "54001aaff29a231c9b2fe83459805d99dce0c21d6e2719f9b11bc90d8fe2f9c9",
"EndpointID": "3e492b611d9bd2c202c8a6c8fe4b4a755393545348b1aeb4e60995609dabb07c",
"Gateway": "172.17.0.1",
"IPAddress": "172.17.0.2",
"IPPrefixLen": 16,
"IPv6Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"MacAddress": "02:42:ac:11:00:02"
}
}
}
}
]
注意看Mounts節(jié)點(diǎn)(Docker的版本用的是1.10.3),數(shù)據(jù)卷的使用,類(lèi)似于 Linux 下對(duì)目錄或文件進(jìn)行 mount。
"Mounts": [
{
"Name": "5ddd734c53a38a78a9f739157c63074b4aff736d4045925616d7753402304137",
"Source": "/var/lib/docker/volumes/5ddd734c53a38a78a9f739157c63074b4aff736d4045925616d7753402304137/_data",
"Destination": "/opt/vol-data",
"Driver": "local",
"Mode": "",
"RW": true,
"Propagation": ""
}
]

當(dāng)我們?cè)谌萜鞯膙olume上操作時(shí),宿主機(jī)上對(duì)應(yīng)的文件是否也會(huì)跟著變動(dòng)呢?測(cè)試一下,在容器的volume上創(chuàng)建一個(gè)文件test.txt,然后查看宿主機(jī)是不是也會(huì)同步存在


經(jīng)測(cè)試,當(dāng)容器上的volume有變動(dòng)時(shí),宿主機(jī)也會(huì)跟著變動(dòng),那反過(guò)來(lái)呢?經(jīng)測(cè)試也是一樣的。不管是容器掛載點(diǎn)發(fā)生變動(dòng)還是宿主機(jī)對(duì)掛載目錄進(jìn)行操作,令一方都會(huì)跟著變動(dòng)。
利用docker commit生成新鏡像,然后docker run -it 運(yùn)行新鏡像,發(fā)現(xiàn)容器掛載目錄下沒(méi)有任何文件了。說(shuō)明生成新鏡像時(shí),是不保存掛載文件的。
3.2從宿主機(jī)掛載volume(-v /host-path:/container-path)
將主機(jī)的文件或文件夾作為volume掛載時(shí),可以用多個(gè) -v標(biāo)簽為容器添加多個(gè)volume,還可以使用:ro指定該volume為只讀。注意:如果容器中指定的掛載目錄存在相同的文件時(shí),會(huì)被宿主機(jī)覆蓋掉

在宿主機(jī)上建立了/opt/vol-01和/opt/vol-02掛載點(diǎn),分別和容器中的/opt/vol-test-1和/opt/vol-test-2對(duì)應(yīng),前者權(quán)限默認(rèn)讀寫(xiě),后者只能讀,用docker inspect

當(dāng)在容器的vol-test-2上新建操作時(shí),會(huì)提示只讀.在宿主機(jī)上,2個(gè)掛載點(diǎn)新增,修改,刪除操作都OK,但是在容器中居然2個(gè)都不行,按理說(shuō)應(yīng)用是第二個(gè)vol-test-2不行才對(duì),不知是哪里出問(wèn)題了。
利用docker commit生成新鏡像,然后docker run -it 運(yùn)行新鏡像,發(fā)現(xiàn)容器掛載目錄下沒(méi)有任何文件了。說(shuō)明生成新鏡像時(shí),是不保存掛載文件的。
3.3使用Dockerfile添加volume
使用VOLUME指令向容器添加volume
VOLUME /data
多個(gè)時(shí)VOLUME ["/data1","/data2"]
這種情況和第一個(gè)中情況docker run -v /data是一樣的。注意,dockerfile中使用volume是不能和第二種方法那樣掛載宿主機(jī)中指定的文件夾。這時(shí)為了保證Dockerfile的可移植性,因?yàn)椴荒鼙WC所有的宿主機(jī)都有對(duì)應(yīng)的文件夾。
需要注意的是,在Dockerfile中使用VOLUME指令后,如果嘗試對(duì)這個(gè)volume進(jìn)行修改,這些修改指令都不會(huì)生效,比如下面例子,嘗試添加一個(gè)文件,并修改文件并改變文件所有權(quán)限
FROM test/mycent:v1.0 RUN useradd foo VOLUME /data RUN touch /data/x RUN chown -R foo:foo /data
通過(guò)該Dockerfile創(chuàng)建鏡像并啟動(dòng)容器后,該容器中存在用戶(hù)foo,并且能看到在/data掛載的volume,但是/data文件夾的所有者并沒(méi)有被改變?yōu)閒oo,而且/data下也沒(méi)有/data/x文件。但是如果順序反過(guò)來(lái),先建文件,先授權(quán),再掛載volume,那就得到期待的結(jié)果。
4.共享volume/數(shù)據(jù)卷容器(--volumes-from)
如果你有一些持續(xù)更新的數(shù)據(jù)需要在容器之間共享,最好創(chuàng)建數(shù)據(jù)卷容器。數(shù)據(jù)卷容器,其實(shí)就是一個(gè)正常的容器,專(zhuān)門(mén)用來(lái)提供數(shù)據(jù)卷供其它容器掛載的。
先創(chuàng)建一個(gè)名為dbdata的數(shù)據(jù)卷容器,專(zhuān)門(mén)共其他容器掛載。

在/opt/dbdata下創(chuàng)建了一個(gè)文件db.properties。
再啟動(dòng)2個(gè)容器,a,b都a--volumes-from dbdata,b --volumes-from a.


可以看到vol_a,vol_b容器的/opt/dbdata下都有db.properties文件。在vol_b的/opt/dbdata下創(chuàng)建文件vol_b.txt時(shí),容器dbdata,vol_a也都同時(shí)同步了。
對(duì)dbdata,vol_a,vol_b使用命令docker inspect時(shí),發(fā)現(xiàn)他們的Mounts下的Source都是一樣的,說(shuō)明它們都綁定到宿主機(jī)的同一個(gè)目錄,所以當(dāng)某個(gè)容器的掛載修改時(shí),其他容器也看到了同樣的效果

如果掛載源有多個(gè)時(shí),可以使用多個(gè)--volumes-from,如
docker run -it --name vol_use --volumes-from vol_a --volumes-from vol_b test/mycentos:v1.0 /bin/bash.
如果一個(gè)容器掛在了volume,即使容器停止了運(yùn)行,該volume仍然存在,其他容器仍然可以繼續(xù)--volumes-from它。

5.刪除volume
如果創(chuàng)建容器時(shí)掛載了volume,
在/var/lib/docker/volumes/04b003b21b873157433deffbaf08bb0c89d234d3ec3c6576fdd7b61f5d41163e/_data下會(huì)生成相應(yīng)的文件(路徑,不同版本,不同操作系統(tǒng)會(huì)有所不同,具體可以用docker inspect查看容器具體信息),當(dāng)刪除容器時(shí),宿主機(jī)上的掛載目錄時(shí)不會(huì)刪除的,并且目錄名稱(chēng)是隨機(jī)字符,不知意義,所以在刪除容器時(shí),需要妥善處理容器的volume。刪除容器時(shí)一并刪除volume有2中方法
(1)docker rm -v 刪除容器。就是刪除容器時(shí),加上-v
(2)docker run --rm .就是啟動(dòng)容器的時(shí)候加上--rm,那么當(dāng)容器運(yùn)行停止時(shí)會(huì)自動(dòng)刪除容器以及容器所掛載的volume
上面創(chuàng)建了容器dbdata,vol_a,vol_b,vol_c,現(xiàn)在使用docker rm -v看看有什么效果。

vol_c是--volumes-from dbdata的,刪除vol_c時(shí),宿主機(jī)的掛載目錄仍存在,沒(méi)刪掉,猜測(cè)那是因?yàn)檫€有其他容器在連著或者說(shuō)是dbdata -v的時(shí)候創(chuàng)建的。那現(xiàn)在刪除dbdata容器試試看(vol_a是--volumes-from dbdata的,vol_a還沒(méi)刪除,驗(yàn)證下能否刪除dbdata)。

發(fā)現(xiàn)dbdata刪除時(shí),宿主機(jī)那目錄仍然存在,同時(shí)也說(shuō)明哪怕vol_a是--volumes-from dbdata的,vol_a還沒(méi)刪除,那也沒(méi)影響。同時(shí)也說(shuō)明只要還有一個(gè)容器在掛載這宿主機(jī)的目錄,那宿主機(jī)的目錄就不會(huì)刪除。那接下來(lái),把所有容器都刪除,再看結(jié)果。

可以看到,當(dāng)最后一個(gè)容器刪除后,宿主機(jī)那volume目錄終于刪除了.
6.備份、恢復(fù)或遷移volume
上面有測(cè)試過(guò),當(dāng)使用docker commit等手段生成新鏡像,然后再啟動(dòng)鏡像生成新容器時(shí),原先volume目錄下的文件不見(jiàn)了,可以生成新鏡像時(shí),并沒(méi)有把volume下的文件一起打包生成鏡像。
volume作為數(shù)據(jù)的載體,在很多情況下需要對(duì)其中的數(shù)據(jù)進(jìn)行備份、遷移,或是從已有數(shù)據(jù)恢復(fù)。一個(gè)很容易想到的方法就是用docker inspect命令查找到volume對(duì)應(yīng)宿主機(jī)上對(duì)應(yīng)的那個(gè)目錄位置,然后復(fù)制其中內(nèi)容或使用tar打包。當(dāng)這些笨拙的做法并不值得推薦,因?yàn)椴檎抑鳈C(jī)上文件夾后再操作容易出錯(cuò),也不適合腳本的自動(dòng)化執(zhí)行。
備份volume可以使用以下方法
docker run --rm --volumes-from dbdata -v $(pwd):/backup test/mycentos:v1.0 tar cvf /back/data.tar /data
這行指令啟動(dòng)了一個(gè)臨時(shí)的容器,這個(gè)容器掛載了兩個(gè)volume,第一個(gè)volume與要備份的volume共享,第二個(gè)volume將宿主機(jī)的當(dāng)前目錄(也可以絕對(duì)路徑)掛載到容器的/backup下。容器運(yùn)行后將要備份的容器(/data)備份到/backup/data.tar,然后刪除容器,備份后的data.tar就留在了當(dāng)前目錄。操作驗(yàn)證一下

居然報(bào)錯(cuò)了,說(shuō)沒(méi)有權(quán)限。進(jìn)入容器-it時(shí),是docker隨機(jī)生成一個(gè)用戶(hù)的,至于怎樣給該用戶(hù)授權(quán),以后再研究吧。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
DOCKERFILE學(xué)習(xí)及使用注意事項(xiàng)
Dockerfile 由一行行命令語(yǔ)句組成,并且支持以 # 開(kāi)頭的注釋行。一般的,Dockerfile分為四部分:基礎(chǔ)鏡像信息、維護(hù)者信息、鏡像操作指令、容器啟動(dòng)時(shí)執(zhí)行指令2017-02-02
詳解如何使用Docker快速部署ELK環(huán)境(最新5.5.1版本)
這篇文章主要介紹了詳解如何使用Docker快速部署ELK環(huán)境(最新5.5.1版本),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-08-08
docker run -e傳遞環(huán)境變量的過(guò)程
這篇文章主要介紹了docker run -e傳遞環(huán)境變量的過(guò)程,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2024-01-01
Docker?安裝啟動(dòng)Jenkins的方法(docker-compose)
這篇文章主要介紹了Docker?安裝啟動(dòng)?Jenkins(docker-compose)的方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-09-09
詳解如何用docker安裝laravel開(kāi)發(fā)環(huán)境
本篇文章主要介紹了詳解如何用docker安裝laravel開(kāi)發(fā)環(huán)境,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-02-02
Docker容器在系統(tǒng)啟動(dòng)時(shí)自動(dòng)運(yùn)行配置方法
docker容器化可以使得環(huán)境相對(duì)獨(dú)立,減少污染,這篇文章主要給大家介紹了關(guān)于Docker容器在系統(tǒng)啟動(dòng)時(shí)自動(dòng)運(yùn)行配置的相關(guān)資料,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-09-09
詳解Docker容器運(yùn)行多條命令(supervisor)
本篇文章主要介紹了詳解Docker容器運(yùn)行多條命令(supervisor),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-04-04
Docker實(shí)踐--部署Nodejs應(yīng)用
本篇文章主要介紹了Docker實(shí)踐--部署Nodejs應(yīng)用,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-01-01

