首頁  >  文章  >  web前端  >  docker 資料卷之進階篇

docker 資料卷之進階篇

赶牛上岸
赶牛上岸原創
2018-03-06 15:09:231420瀏覽

Docker 是一個開源的應用容器引擎,讓開發者可以打包他們的應用以及依賴包到一個可移植的容器中,然後發佈到任何流行的 Linux 機器上,也可以實現虛擬化。 這篇文章主要介紹了docker 資料卷之進階篇,小編分享給大家,希望對大家有幫助。 

為什麼需要資料磁碟區?

這得從 docker 容器的檔案系統說起。出於效率等一系列原因,docker 容器的檔案系統在宿主機上存在的方式很複雜,這會帶來下面幾個問題:

  1. ##不能在宿主機上很方便地存取容器中的文件。

  2. 無法在多個容器之間共用資料。

  3. 當容器刪除時,容器中產生的資料將會遺失。

為了解決這些問題,docker 引入了資料卷(volume) 機制。資料卷是存在於一個或多個容器中的特定檔案或資料夾,這個檔案或資料夾以獨立於 docker 檔案系統的形式存在於宿主機器中。資料卷的最大特定是:其生存週期獨立於容器的生存週期。

使用資料卷的最佳場景

  1. 在多個容器之間共享數據,多個容器可以同時以唯讀或讀寫的方式掛載同一個資料卷,從而共享資料卷中的資料。

  2. 當宿主機無法保證一定存在某個目錄或一些固定路徑的檔案時,使用資料磁碟區可以規避這種限制所帶來的問題。

  3. 當你想把容器中的資料儲存在宿主機之外的地方時,例如遠端主機上或雲端儲存上。

  4. 當你需要把容器資料在不同的宿主機之間備份、還原或遷移時,資料卷是很好的選擇。

docker volume 子指令

#docker 專門提供了volume 子指令來操作資料卷:

create 建立資料卷
inspect 顯示資料卷的詳細資訊
ls 列出所有的資料卷
prune 刪除所有未使用的volumes,並且有-f 選項
rm 刪除一個或多個未使用的volumes,並且有- f 選項

先建立一個名稱為hello 的資料卷並透過ls 指令來檢視:

然後可以使用inspect 指令看看數據卷hello 的詳細資料:

在這裡我們可以看到創建資料卷的時間;該資料卷使用的驅動程式為預設的"local",表示資料卷使用宿主機的本機儲存;資料卷的掛載點,預設是本機/var/lib/docker/volumes 下的一個目錄。


最後我們可以使用 rm 或 prune 指令刪除資料卷,後面筆者會介紹一些實際使用中與資料卷的刪除有關的一些實作。

使用mount 語法掛載資料卷

之前我們使用--volume(-v) 選項來掛載資料卷,現在docker 提供了更強大的- -mount 選項來管理資料卷。 mount 選項可以透過逗號分隔的多個鍵值對一次提供多個配置項,因此 mount 選項可以提供比 volume 選項更詳細的配置。使用 mount 選項的常用

配置如下:

  1. type 指定掛載方式,我們這裡用到的是 volume,其實還可以有 bind 和 tmpfs。

  2. volume-driver 指定掛載資料卷的驅動程序,預設值是 local。

  3. source 指定掛載的來源,對於一個命名的資料卷,這裡應該指定這個資料卷的名稱。使用時可以寫 source,也可以簡寫為 src。

  4. destination 指定掛載的資料在容器中的路徑。使用時可以寫 destination,也可以簡寫為 dst 或 target。

  5. readonly 指定掛載的資料為唯讀。

  6. volume-opt 可以指定多次,用來提高更多的 mount 相關的配置。

下面我們來看個具體的例子:

$ docker volume create hello
$ docker run -id --mount type=volume,source=hello,target=/world ubuntu /bin/bash

我們建立了名稱為 hello 的資料卷,然後把它掛在到容器中的 /world 目錄。透過inspect 指令查看容器的詳情中的"Mounts" 資訊可以驗證實際的資料卷掛載結果:

使用volume driver 把資料儲存到其它地方

除了預設的把資料磁碟區中的資料儲存在宿主機,docker 還允許我們透過指定volume driver 的方式把資料卷中的資料儲存在其它的地方,例如Azrue Storge 或AWS的S3。


简单起见,我们接下来的 demo 演示如何通过 vieux/sshfs 驱动把数据卷的存储在其它的主机上。

docker 默认是不安装 vieux/sshfs 插件的,我们可以通过下面的命令进行安装:

$ docker plugin install --grant-all-permissions vieux/sshfs

然后通过 vieux/sshfs 驱动创建数据卷,并指定远程主机的登录用户名、密码和数据存放目录:

docker volume create --driver vieux/sshfs \
 -o sshcmd=nick@10.32.2.134:/home/nick/sshvolume \
 -o password=yourpassword \
 mysshvolume

注意,请确保你指定的远程主机上的挂载点目录是存在的(demo 中是 /home/nick/sshvolume 目录),否则在启动容器时会报错。
最后在启动容器时指定挂载这个数据卷:

docker run -id \
 --name testcon \
 --mount type=volume,volume-driver=vieux/sshfs,source=mysshvolume,target=/world \
 ubuntu /bin/bash


这就搞定了,你在容器中 /world 目录下操作的文件都存储在远程主机的 /home/nick/sshvolume 目录中。进入容器 testcon 然后在 /world 目录中创建一个文件,然后打开远程主机的 /home/nick/sshvolume 目录进行查看,你新建的文件是不是已经出现在那里了!

数据卷原理

下图描述了 docker 容器挂载数据的三种方式:

数据卷是完全被 docker 管理的,就像上图中的黄色区域描述的一样,docker 在宿主机的文件系统中找了个文件管理数据卷相关的数据。因此你可能根本不需要知道数据卷文件在宿主机上的存储位置(事实上抱着刨根问底的精神我们还是很想搞清楚它背后的工作原理!)。

docker 数据卷的本质是容器中的一个特殊目录。在容器创建的过程中,docker 会将宿主机上的指定目录(一个以数据卷 ID 为名称的目录)挂载到容器中指定的目录上。这里使用的挂载方式为绑定挂载(bind mount),所以挂载完成后的宿主机目录和容器内的目标目录表现一致。

比如我们执行下面的命令创建数据卷 hello,并挂载到容器 testcon 的 /world 目录:

$ docker volume create hello
$ docker run -id --name testcon --mount type=volume,source=hello,target=/world ubuntu /bin/bash

实际上在容器的创建过程中,类似于在容器中执行了下面的代码:

// 将数据卷 hello 在宿主机上的目录绑定挂载到 rootfs 中指定的挂载点 /world 上
mount("/var/lib/docker/volumes/hello/_data", "rootfs/world", "none", MS_BIND, NULL)

在处理完所有的 mount 操作之后(真正需要 docker 容器挂载的除了数据卷目录还包括 rootfs,init-layer 里的内容,/proc 设备等),docker 只需要通过 chdir 和 pivot_root 切换进程的根目录到 rootfs 中,这样容器内部进程就只能看见以 rootfs 为根的文件系统以及被 mount 到 rootfs 之下的各项目录了。例如我们启动的 testcon 中的文件系统为:

下面我们介绍几个数据卷在使用中比较常见的问题。

数据的覆盖问题

  1. 如果挂载一个空的数据卷到容器中的一个非空目录中,那么这个目录下的文件会被复制到数据卷中。

  2. 如果挂载一个非空的数据卷到容器中的一个目录中,那么容器中的目录中会显示数据卷中的数据。如果原来容器中的目录中有数据,那么这些原始数据会被隐藏掉。

这两个规则都非常重要,灵活利用第一个规则可以帮助我们初始化数据卷中的内容。掌握第二个规则可以保证挂载数据卷后的数据总是你期望的结果。

在 Dockerfile 中添加数据卷

在 Dockerfile 中我们可以使用 VOLUME 指令向容器添加数据卷:

VOLUME /data

在使用 docker build 命令生成镜像并且以该镜像启动容器时会挂载一个数据卷到 /data 目录。根据我们已知的数据覆盖规则,如果镜像中存在 /data 目录,这个目录中的内容将全部被复制到宿主机中对应的目录中,并且根据容器中的文件设置合适的权限和所有者。

注意,VOLUME 指令不能挂载主机中指定的目录。这是为了保证 Dockerfile 的可一致性,因为不能保证所有的宿主机都有对应的目录。

在实际的使用中,这里还有一个陷阱需要大家注意:在 Dockerfile 中使用 VOLUME 指令之后的代码,如果尝试对这个数据卷进行修改,这些修改都不会生效!下面是一个这样的例子:

FROM ubuntu
RUN useradd nick
VOLUME /data
RUN touch /data/test.txt
RUN chown -R nick:nick /data

通过这个 Dockerfile 创建镜像并启动容器后,该容器中存在用户 nick,并且能够看到 /data 目录挂载的数据卷。但是 /data 目录内并没有文件 test.txt,更别说 test.txt 文件的所有者属性了。要解释这个现象需要我们了解通过 Dockerfile 创建镜像的过程:

Dockerfile 中除了 FROM 指令的每一行都是基于上一行生成的临时镜像运行一个容器,执行一条指令并执行类似 docker commit 的命令得到一个新的镜像。这条类似 docker commit 的命令不会对挂载的数据卷进行保存。

所以上面的 Dockerfile 最后两行执行时,都会在一个临时的容器上挂载 /data,并对这个临时的数据卷进行操作,但是这一行指令执行并提交后,这个临时的数据卷并没有被保存。因而我们最终通过镜像创建的容器所挂载的数据卷是没有被最后两条指令操作过的。我们姑且叫它 "Dockerfile 中数据卷的初始化问题"。

下面的写法可以解决 Dockerfile 中数据卷的初始化问题:

FROM ubuntu
RUN useradd nick
RUN mkdir /data && touch /data/test.txt
RUN chown -R nick:nick /data
VOLUME /data

通过这个 Dockerfile 创建镜像并启动容器后,数据卷的初始化是符合预期的。这是由于在挂载数据卷时,/data 已经存在,/data 中的文件以及它们的权限和所有者设置会被复制到数据卷中。

还有另外一种方法可以解决 Dockerfile 中数据卷的初始化问题。就是利用 CMD 指令和 ENTRYPOINT 指令的执行特点:与 RUN 指令在镜像构建过程中执行不同,CMD 指令和 ENTRYPOINT 指令是在容器启动时执行。因此使用下面的 Dockerfile 也可以达到对数据卷的初始化目的:

FROM ubuntu
RUN useradd nick
VOLUME /data
CMD touch /data/test.txt && chown -R nick:nick /data && /bin/bash

总结

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家支持php中文网。


相关推荐:

docker中安装phpredis实例分享

Docker搭建PHP开发环境方法

docker搭建laravel开发环境实例


以上是docker 資料卷之進階篇的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn