關於Docker的掛載

 底層原理不懂就上手,上手出了問題就懵逼,最近在對接阿里雲時遇到Docker存儲驅動的神坑,爬了幾天爬不出來,最後發現是節點中Docker存儲驅動的問題,由此引發此次學習,避免類似問題再次懵逼。

 關於鏡像images,核心首先必須明確一點,鏡像都是隻讀的,如果需要進行寫操作,必須在該鏡像上創建一個新的鏡像層(我們所有的寫操作其實都是在一個可寫的鏡像層上操作的)。同時鏡像也是共享的,那些依賴於同一個 image 的多個容器,並不會將單獨複製需要的鏡像到自己的容器中進行啓動,那樣會浪費巨大的空間,實際節點上只會有一個鏡像,多個容器是共享這一個鏡像的。存儲驅動可以將容器中的數據進行持久化同時避免性能問題。存儲驅動允許我們在容器的可寫層創建數據,這些數據在容器銷燬後也就沒有了,且這些文件的讀寫速度都比本地文件系統的性能低。

 默認情況下,容器內所有創建的文件都存儲在一個可寫層,所以這些文件僅僅存活於容器運行時,一旦容器被銷燬這些文件也會被銷燬。容器的可寫層和宿主機器緊密耦合,很難將可寫層的文件或數據遷移到非宿主機器外的地方。要將數據寫入容器的可寫層必須要有一個存儲驅動(storage driver)來管理文件系統,存儲驅動程序使用Linux內核提供的聯合文件系統,這種方式與使用直接寫入主機文件系統的數據卷相比降低了性能。Docker提供幾種種持久化容器中數據或文件的方式:

  • volumes:Volumes是持久化Docker中數據最好的方式,存在於宿主機器文件系統的一部分(Linux中位於/var/lib/docker/volumes/目錄下的xx.db文件),由Docker自己管理,非Docker進程去修改這部分的文件;
  • bind mounts:這種方式可以將容器中的數據持久化到宿主機器的任何位置(文件或目錄),任何進程都可以進行修改
  • tmpfs mount:Linux 上專用,tmpfs掛載僅存儲在主機系統的內存中,永遠不會寫入主機系統的文件系統;
  • named pipe:windowns 上專用,同上;

關於上述的幾種方式的區別,官網形象的圖文解釋:

幾種持久化方式

1. Volumes(最推薦的掛載方式)

 Volumes(數據卷) 由 Docker 自行創建和管理,可以使用docker volume create顯式的創建一個Volume,當然如果不人爲創建,Docker會在創建容器或服務時自行創建。在使用docker volume create xxx創建 Volume 時,Docker會在宿主機器的/var/lib/docker/volumes/目錄下創建xxx文件夾用於容器數據的持久化。當將xxx的Volume掛載到容器上時,此目錄就是容器中對應的目錄。這種方式和bind mounts很相似,但 Volumes 方式是和宿主機器核心功能隔離並且是由Docker自己管理的。注意同一個Volume是可以同時掛載到多個容器內的,當沒有使用該存儲卷的容器時,該存儲卷並不會自動銷燬(因爲主要是解決容器可寫層數據持久化的問題,當然不會自動銷燬),如果需要刪除某個存儲卷需要手動調用docker volume prune命令,這個命令將會移除所有宿主機器本地的Volumes(謹慎操作)。

 當掛載一個 Volume 時,該存儲卷可以進行命名或直接匿名,匿名存儲卷首次掛載到容器時並不會顯式的給它一個名字,而是Docker給它們一個隨機名字,且保證該名字是Docker主機中唯一的,匿名和命名的存儲卷唯一的區別就是它們名字。

 Volumes 是支持 volume driver 的,它允許使用遠程主機的存儲卷或者其他雲供應商提供的數據卷或者其他的存儲形式。

下面是一個示例:

# 創建數據卷
docker volume create testvolumes
# 直接拉一個ubuntu鏡像來實驗
docker pull ubuntu
# 通過 '-v/--volume 存儲卷' 的方式即可掛載  /var/lib/docker/volumes/testvolumes:
docker run -it -v testvolumes ubuntu:latest
# 在容器中的 /testvolumes/_data 目錄下創建空文件 addDatas,退出容器並查看宿主機器的 /var/lib/docker/volumes/testvolumes/_data 創建成功
touch addDatas

 Volum應用場景包含:

  • Volume可以讓多容器共享的數據(讀寫或只讀),若不顯式創建它,會在被掛載前自動創建,Volume只有在顯式移除時纔會被移除;
  • Docker宿主機不具備目錄結構或者文件系統,Voulme 可以幫助將Docker宿主機的配置從容器運行時解耦;
  • 遠程存儲,這點應該是最廣泛的場景,目前很多應用都“上雲”,使用的是雲提供商或者遠程機器,而不是宿主機器本身的存儲;
  • Volume可以在備份、還原、遷移Dokcer宿主機發揮很人性化的優勢,可以先停止使用某個Volume的容器,然後備份Volume(通常目錄爲/var/lib/docker/volumes/<volume-name>)即可;

 Volume 方式有點注意點:

  • 當將一個空數據卷(volume)掛載到容器中某個已存在的文件或目錄時,那麼容器該文件或目錄中的內容將會被複制到這個空數據卷中;
  • 當掛載一個不存在的數據卷時,Docker會自動創建一個空的數據卷,這是預先填充另一個容器所需數據的好方法,比如B容器需要一個數據卷VolumeX,他會去檢測VolumeX是否存在,那如果A容器在B容器之前啓動並且創建了一個VolumeX,那麼B的檢測就能通過;

 數據卷驅動,當創建數據卷docker volume create xxx或啓動容器掛載一個還未創建的數據卷時,此時可以指定一個數據卷驅動。下面是一個示例

 數據卷 Volume 支持--mount--volume/-v的語法,當和服務(即swarm services)一起使用數據卷時,僅支持--mount參數,所以官方也是一直推薦使用--mount,完整的示例:

# 1. 單個容器的示例
# 創建數據卷
docker volume create my-vol
# 查看數據卷列表
docker volume ls
# 使用ubuntu鏡像創建一個容器devtest,將數據卷my-vol掛載到目錄/app下,並且該數據卷是隻讀的
docker run -d --name devtest --mount src=my-vol,dst=/app,readonly ubuntu:latest
# 檢查容器信息(注意查看 Mounts 字段是否和期望的一樣)
docker volume inspect devtest
# 移除數據卷
docker container stop devtest
docker container rm devtest
docker volume rm my-vol

# 2. 容器服務的示例
# 將當前節點進行 swarm 初始化,並且成爲master
docker swarm init
# 創建副本爲4的service,service僅支持--mount,不支持-v/--volume的形式
docker service create -d --replicas=4 --name devtest-service --mount src=my-vol,dst=/app/test ubuntu:latest
# 查看service的狀態
docker service ps devtest-service
# 移除所有 devtest-service 的task
docker service rm devtest-service

2. Bind mounts

 Bind mounts (綁定加載)的方式在Dokcer早期版本就提供了,相對於 Volumes 方式功能有限。Bind mounts 就是將宿主機器中一個文件或者目錄掛載到容器中,掛載時必須以該文件或目錄的絕對路徑指定,而且指定的宿主機器上的路徑不一定非要在宿主機器上存在,Docker在加載時會去按需創建,而且綁定加載的性能非常好,但它依賴於宿主機器上特定的目錄結構。所以目前爲官網還是推薦使用數據卷(Volumes)的形式來作存儲驅動。此外,bind mounts 不能使用使用Dokcer命令行直接管理這些綁定加載(bind mounts)。

 此外 bind mounts 方式必須對一些敏感文件(比如host文件)有權限,可以通過運行在容器中的進程修改(增長改查)系統host文件,這個功能可能會引發安全問題,因爲此時Dokcer對宿主機器上非Docker進程甚至系統級別的文件都可以隨意更改。

下面是一個示例:

# 通過 `-v/--volume 宿主機目錄:容器目錄` 的形式將宿主機器的 /root/helm 目錄掛載到容器的 /usr/local/helm 目錄
docker run --volume /root/helm:/usr/local/helm -it ubuntu:latest

 綁定掛載適用的場景有:

  • 給容器共享宿主機上的配置文件,這也是 Docker 通過將宿主機上的/etc/resolv.conf掛載到容器內從而給容器提供的默認DNS策略;
  • 在開發環境的宿主機器和容器之間共享代碼或者構建的jar包、war包,比如對於maven項目,直接將服務模塊的target目錄綁定掛載到容器中,這樣就不需要要在容器內再次構建了;
  • 當確保Docker主機的文件或目錄結構與綁定掛載所需的容器目錄一致時;

 綁定掛載和數據卷同時有一個注意點,如果將一個非空宿主機目錄掛載到容器中已存在的文件或者目錄,那麼容器內的這些文件或目錄將會被宿主機的文件或者目錄將會被遮擋,就像你把文件存在宿主機的/mnt目錄,然後將一個USB驅動也掛載到/mnt,那此時/mnt目錄中的內容將會被USB驅動遮擋直到USB驅動取消掛載,被遮擋的文件或目錄並不會被移除或修改,只是使用綁定掛載或者數據卷形式時這些文件是不可得的;

3. tmpfs mounts

tmpfs掛載不會持久化在磁盤上持久化,也不會存儲在宿主機器上,可以在容器的生命聲明週期內使用,用於存儲不需要持久化或者一些敏感信息,典型應用在內部,羣集服務使用tmpfs掛載將機密安裝到服務的容器中。使用--tmpfs參數進行掛載。

tmpfs掛載適用於那些不需要在宿主機器或容器中持久化數據的場景,比如可能爲了安全原因或者是爲了在application 需要寫大量非持久化數據時保證容器的性能(我覺得這個原因靠譜點)。

4. named pipes

npipe掛載可以用於宿主機器和Docker容器的通信,典型應用就是在容器中運行第三方工具時使用named pipe連接 Docker Engine 的 API。

注:上述4種掛載方式在語法上不太一樣,使用-v/--volume來使用,在17.06+可以不區分掛載方式直接使用--mount進行掛載(本來這個參數是用於 swarm services,而單個容器使用-v/--volume),因爲之前使用的 -v/--volume 參數名和數據卷Volume的形式一樣,很容易誤導開發者是轉專用於數據掛載方式的參數,而且對於參數的定義較爲嚴格,使用--mount參數語義更加清晰,諸如:docker run -it --mount source=testvolumes,target=/usr/local/helm ubuntu:latest(前提是建立自己的Volume)。

5.關於語法

5.1 -v/–volume

-v/--volume命令實際是有三個參數的,之間以:分隔,而且必須以規定的順序出現,三個參數的具體含義爲:

  • 第一個參數:對於有名字的數據卷表示數據卷的名字,該名字必須在宿主機上唯一;對於匿名數據卷,第一個參數需要省略;
  • 第二個參數:掛載到容器內的路徑;
  • 第三個參數(可選):是一系列以,分隔的參數;

5.2 --mount

--mount命令參數是由多個<key>=<value>形式的鍵值對組成,之間以,分隔,不要求順序,主要參數的鍵值對有:

  • type=xxxtype參數用來表示掛載的方式,value可以是volumebind或者tmpfs
  • source/src=xxxsource或者src參數表示數據卷(有名字的,匿名數據卷該參數省略),value爲數據卷的名字;
  • target/destination/dst=xxxtarget或者destination或者dst參數表示內容器的掛載路徑,value即爲容器內的目錄;
  • readonly:表示綁定掛載是否只讀bind mount,如果出現(該字段非鍵值對),那容器內對該文件/目錄只具備讀的能力;
  • volume-opt:表示除上述參數之外的其他參數,可以出現多個volume-opt參數,每個都以鍵值對的形式出現;

 如果數據卷驅動可以接受多個以,分隔的數組,那在傳參時必須在CSV編譯器檢查語法時正常通過,爲此用雙引號""volume-opt這個參數對象包裹起來,然後整個--mount參數對象使用單引號''包裹,示例:

$ docker service create \
     --mount 'type=volume,src=<VOLUME-NAME>,dst=<CONTAINER-PATH>,volume-driver=local,volume-opt=type=nfs,volume-opt=device=<nfs-server>:<nfs-path>,"volume-opt=o=addr=<nfs-address>,vers=4,soft,timeo=180,bg,tcp,rw"'
    --name myservice \
    <IMAGE>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章