聲明:所有的實(shí)驗(yàn)示例大部分來(lái)自《learn-docker-in-a-month-of-lunches》的作者Elton Stoneman,但運(yùn)行結(jié)果并不都是照搬,大部分實(shí)驗(yàn)結(jié)果可能與原書不同
一、前言
到目前為止,你應(yīng)該已經(jīng)在容器上發(fā)現(xiàn)了這么一個(gè)特點(diǎn):你每次運(yùn)行一個(gè)新的容器,容器的內(nèi)的任何文件都回歸初始了,你乍一聽(tīng)會(huì)覺(jué)得這不是挺好的嘛,這樣會(huì)保證每一個(gè)新運(yùn)行的容器不論在何時(shí)不論在何地都從同一個(gè)狀態(tài)開始運(yùn)行。但你有沒(méi)有想過(guò)這么一種情況,你的容器存儲(chǔ)了一些重要信息在容器內(nèi)的文件系統(tǒng)里,但是這時(shí)因?yàn)檐浖?jí),你的鏡像也更新了,你必須以新的鏡像去運(yùn)行新的容器,也就是說(shuō)你得關(guān)停老的容器,那么這些重要數(shù)據(jù)豈不是丟失了?
不必?fù)?dān)心,強(qiáng)大的Docker早就想到了這一點(diǎn),它們給出了Docker Volumn和Docker Bind兩種文件共享方式,以適應(yīng)你數(shù)據(jù)持久化的需求。
不過(guò)在此之前,我們先來(lái)討論一下Docker容器的文件系統(tǒng)結(jié)構(gòu)。
二、Docker容器的文件系統(tǒng)結(jié)構(gòu)
Docker會(huì)將所有的文件系統(tǒng)源合并在一起呈現(xiàn)給Docker容器,而Docker容器能看到只是一個(gè)普普通通的塊存儲(chǔ)設(shè)備,Docker稱這樣的系統(tǒng)為union filesystem,下圖是union filesystem的一個(gè)基本組成結(jié)構(gòu):

- 最底下的是鏡像層文件系統(tǒng),文件來(lái)源是(或者說(shuō)是鏡像)Dockerfile中生成、復(fù)制或者其他操作產(chǎn)生的文件,這些文件是只讀的,但是因?yàn)槭褂昧薱opy-on-write技術(shù)的原因,這些文件某種意義上其實(shí)也是可以變更的
- Docker Volume是Docker提供的用于容器與容器之間共享數(shù)據(jù)的文件系統(tǒng)。來(lái)自Docker生成
- Docker Bind是Docker提供的掛載型文件系統(tǒng),用于交互主機(jī)(甚至是云文件系統(tǒng))與容器的文件系統(tǒng),文件來(lái)源于掛載源
- 可寫層是每個(gè)容器都獨(dú)有的文件系統(tǒng),這些文件僅在容器存在時(shí)存在,文件來(lái)源于容器本身
三、Docker 鏡像層文件系統(tǒng)與可寫層文件系統(tǒng)
- 概念:
鏡像層文件系統(tǒng)顧名思義來(lái)自于鏡像,鏡像是用來(lái)分享的,且必須保持一致,所以鏡像層文件系統(tǒng)是只讀的。但是實(shí)際應(yīng)用中,我們發(fā)現(xiàn)鏡像中原本就存在的文件其實(shí)是可以修改的,這是因?yàn)樵阽R像層文件系統(tǒng)上還有一層可寫層文件系統(tǒng)。
可寫層文件系統(tǒng)由Docker在容器運(yùn)行時(shí)特地為容器創(chuàng)建,在容器移除時(shí)移除??蓪憣硬粌H僅只應(yīng)用于新添加的文件,其實(shí)也應(yīng)用于修改鏡像層存在的文件,在我們嘗試修改鏡像層存在的文件時(shí),Docker其實(shí)會(huì)把該文件復(fù)制一份到可寫層上,你所有的修改操作實(shí)際上全部都操作在可寫層的文件上,所以對(duì)鏡像層文件不會(huì)有任何的影響。這種技術(shù)稱之為copy-on-write。 - 應(yīng)用領(lǐng)域:
鏡像層文件系統(tǒng)應(yīng)用于鏡像分享,因?yàn)樗闹蛔x屬性這保證了鏡像的一致性。
可寫層文件系統(tǒng)應(yīng)用于短期存儲(chǔ),比如緩存一些數(shù)據(jù)之類的。
四、Docker Volume容器共享文件系統(tǒng)
概念:
Docker Volume就相當(dāng)于一個(gè)U盤,只不過(guò)這個(gè)U盤由Docker創(chuàng)建,且服務(wù)于容器,Docker Volume是一個(gè)獨(dú)立的組件,和容器的生命周期互不相關(guān),就像你電腦關(guān)機(jī)了,但是U盤還在一樣。以容器的視角來(lái)看Docker Volume,它其實(shí)就只是一個(gè)普通的目錄,像平常一樣操作這個(gè)目錄就可以了。-
使用方法(兩種):
- 在Dockerfile中指定
VOLUME /data在Dockerfile中添加
Volume指令,告訴Docker在創(chuàng)建這個(gè)鏡像的容器時(shí),創(chuàng)建一個(gè)Docker Volume并掛載在/data目錄下,需要注意的是,這樣運(yùn)行的每個(gè)容器其Docker Volume都是不同的$ docker container run --name todo-list --detach --publish 80:80 diamol/ch06-todo-list 5b81d8fe9953224a9c768ebbceead8799474cc204ce5df294098e92a5ac7a4e8 $ docker volume ls DRIVER VOLUME NAME local f07bea1de6730f9aa91e2f631249c89eb8126d5827383fa8bfbb43ebdd4cc3fediamol/ch06-todo-list該鏡像的Dockerfile中使用過(guò)
volume指令,運(yùn)行該鏡像后查看Docker中存在的Volume確實(shí)發(fā)現(xiàn)生成了一個(gè)名為f07bea1de6730f9aa91e2f631249c89eb8126d5827383fa8bfbb43ebdd4cc3fe的Docker Volume
那另外一個(gè)容器想使用這個(gè)‘隨機(jī)’生成的Volume怎么辦呢?答案是在運(yùn)行容器時(shí)加上volumes-from <容器名>這個(gè)選項(xiàng)$ docker container run --name todo-list2 --volumes-from todo-list --detach --publish 80:80 diamol/ch06-todo-list運(yùn)行另一個(gè)容器,并指定todo-list的volume為自己的volume
$ docker volume ls DRIVER VOLUME NAME local f07bea1de6730f9aa91e2f631249c89eb8126d5827383fa8bfbb43ebdd4cc3fe $ docker container inspect --format '{{.Mounts}}' todo-list2 [{volume f07bea1de6730f9aa91e2f631249c89eb8126d5827383fa8bfbb43ebdd4cc3fe /var/lib/docker/volumes/f07bea1de6730f9aa91e2f631249c89eb8126d5827383fa8bfbb43ebdd4cc3fe/_data /data local true }]檢查Docker Volume的個(gè)數(shù),只有一個(gè)!檢查todo-list2的掛載信息,掛載的Volume是f07bea1de6730f9aa91e2f631249c89eb8126d5827383fa8bfbb43ebdd4cc3fe,的確是todo-list的那個(gè)volume
- 手動(dòng)創(chuàng)建Volume并添加給指定容器
$ docker volume create todo-list-volume todo-list-volume $ docker volume ls DRIVER VOLUME NAME local todo-list-volume創(chuàng)建一個(gè)名為todo-list-volume的Docker Volume
注意:執(zhí)行下一步之前,請(qǐng)確保diamol/ch06-todo-list鏡像的所有容器已經(jīng)移除$ docker container run --name todo-list1 --volume todo-list-volume:'/data' --detach --publish 5000:80 diamol/ch06-todo-list將名為todo-list-volume的Docker Volume掛載給容器todo-list1,接下來(lái)讓我打開瀏覽器去其中添加一些數(shù)據(jù)吧
todo-list$ docker container rm --force todo-list1 $ docker container run --name todo-list2 --volume todo-list-volume:'/data' --detach --publish 5000:80 diamol/ch06-todo-list移除上一個(gè)容器,重新創(chuàng)建一個(gè)容器并掛載todo-list-volume,去瀏覽器查看結(jié)果,正確的話,你會(huì)發(fā)現(xiàn)新的容器會(huì)含有上一個(gè)容器留存的數(shù)據(jù),如果沒(méi)有,你得檢查
:'/data'有沒(méi)有加上,它會(huì)告訴Docker這個(gè)Volume掛載在什么目錄上 -
注意事項(xiàng):
- Docker Volume可以同時(shí)掛載給多個(gè)容器,但同時(shí)你得考慮到這么一個(gè)問(wèn)題,那就是多個(gè)容器同時(shí)對(duì)一個(gè)目錄下的資源進(jìn)行操作,你的應(yīng)用可能工作得并不如你得預(yù)期(因?yàn)槟阋臄?shù)據(jù)可能正好被另一個(gè)容器中的應(yīng)用修改了)
- Docker Volume中Volume的優(yōu)先級(jí)以命令中指定的Volume為最優(yōu)先,如果同時(shí)Dockerfile也指定了Volume,則該指令無(wú)效。
- 作為鏡像創(chuàng)作者,你應(yīng)該在Dockerfile中指定Volume,以避免用戶不指定Volume而造成的各種問(wèn)題。作為用戶,你應(yīng)該避免使用鏡像制作者為你提供的默認(rèn)Volume。
應(yīng)用領(lǐng)域:鑒于上述注意事項(xiàng)第一條的原因,Docker Volume應(yīng)避免被同時(shí)共享給多個(gè)容器,所以,其最主要應(yīng)用于數(shù)據(jù)遷移這一方面。
五、Docker Bind容器掛載文件系統(tǒng)
- 概念:
Docker Bind允許你將任意文件系統(tǒng)(主機(jī)的文件系統(tǒng),云文件系統(tǒng))掛載給容器 - 使用方法:
$ mkdir database
$ source="$(pwd)/database"
$ docker container run --mount type=bind,source=$source,target='/data' --detach --publish 5000:80 diamol/ch06-todo-list
$ curl localhost:5000
$ ll database
-rw-r--r--. 1 root root 12288 Jan 21 18:19 todo-list.db
$ docker volume ls
DRIVER VOLUME NAME
在當(dāng)前目錄下創(chuàng)建一個(gè)database目錄,然后將該目錄掛載給新啟動(dòng)的容器,然后訪問(wèn)這個(gè)應(yīng)用,查看database目錄下的文件,你會(huì)發(fā)現(xiàn)多了一個(gè)數(shù)據(jù)庫(kù)文件。
然后再查看Docker Volume(運(yùn)行前已經(jīng)清空)一個(gè)都沒(méi)有,這說(shuō)明Docker Bind的設(shè)置將默認(rèn)的Docker Volume覆蓋了
這時(shí)候你會(huì)問(wèn),那如果同時(shí)在命令里面將Volume和其他文件系統(tǒng)掛載給同一個(gè)目錄呢?
當(dāng)然會(huì)報(bào)錯(cuò)啦!docker: Error response from daemon: Duplicate mount point: /data.
那如果Volume掛載給/data目錄,而其他文件系統(tǒng)掛載給/data/config目錄呢?
經(jīng)測(cè)試,是可以的
- 注意事項(xiàng):
- 使用Docker Bind畢竟會(huì)和實(shí)實(shí)在在的文件系統(tǒng)相關(guān)聯(lián),所以必須要注意不法分子通過(guò)該渠道經(jīng)由容器控制你的主機(jī)。
- 為了解決上述問(wèn)題,你應(yīng)該提供最小的權(quán)限給(在Dockerfile中使用
USER指令)容器,但是這樣就不能自由的在掛載的文件系統(tǒng)上讀寫了,因此你必須提升該容器的權(quán)限,比如本例中的todo-list使用的就是root權(quán)限 - 使用高權(quán)限畢竟很不安全,針對(duì)某些必定是只讀的目錄或者文件,你可以在容器運(yùn)行時(shí)指定該掛載具有只讀屬性,來(lái)限制容器在這個(gè)目錄或文件上進(jìn)行修改
$ docker container run --mount type=bind,source=$source,target='/data',readonly --detach --publish 5000:80 diamol/ch06-todo-list- 文件系統(tǒng)類型有很多種,并不是所有類型你的容器都可以兼容的,所以在掛載時(shí)務(wù)必確保掛載的文件系統(tǒng)是否兼容,否則將會(huì)導(dǎo)致你的應(yīng)用崩潰
- 應(yīng)用領(lǐng)域:
- 由于掛載并不僅僅局限于本地機(jī)器,所以你可以應(yīng)用Docker Bind來(lái)實(shí)現(xiàn)容器和遠(yuǎn)端存儲(chǔ)之間的數(shù)據(jù)共享。
- 你在本地被掛載的目錄中做的任何更改都將立即反映到容器之中。比如你可以將源代碼的目錄掛載給容器。
六、其他說(shuō)明
- 假如你將一個(gè)目錄掛載在容器中已經(jīng)存在的目錄上,則原目錄會(huì)被隱藏
- 如果你單單只將一個(gè)文件掛載給容器中已經(jīng)存在的目錄上,則該文件將加入該目錄,但要注意這個(gè)特性目前在Windows的容器中并不支持
參考文檔:
[1] learn-docker-in-a-month-of-lunches
[2] 官方文檔