怎样让 Docker 容器使用 NFS 进行数据存储
NFS 的部署与配置参照前文:NFS 容器化部署 & 配置参数详解,本文则专注于讲解怎样让 Docker 容器通过 Volume 来挂载并使用 NFS 作为容器持久化数据存储。
本文将会涉及:
如何使用命令创建、使用、管理 NFS 存储卷
如何在 Compose 中创建和使用 NFS 存储卷
本文将不会涉及:
Docker & Docker Compose 的基础知识,本文假设你已经了解这些基础知识
什么是 NFS 存储卷,为什么要使用它?
首先要说明的是,NFS 存储卷并不是什么“全新的玩意”,实际上它只是 Docker 存储卷在指定 driver 为 nfs 时所创建的存储卷而已。
通过官方文档可以知道 Docker 自身有两种方式可以为容器提供持久化数据存储,分别是 bind-mount 和存储卷(Volume)。其中存储卷作为官方推荐使用的持久化存储方式,具有诸多优点。而其中一个非常关键的是存储卷支持通过指定 Volume driver 的方式来拓展它的功能,在这其中又有一项就是可以拓展对于网络存储协议(如 NFS / SMB / CIFS)的支持,而且对于上述三项的支持是内置于 Docker Engine 的,这意味着不需要对 Docker Engine 进行任何设置就能使用,简直是太美好了。
但是!这还是没有回答为什么要使用它。很简单,这是为了让运算与存储分离,通过使用 NFS 存储卷,在预算不足的情况下我可以先搞个小工控机来跑服务,然后让数据存储在另一台专用的文件服务器上。这样如果后续我需要跑更多的服务,性能不够用了,我可以随时换一台更高性能的电脑跑服务,只需要迁移容器编排,不需要再进行存储数据迁移,既节省了时间也降低了迁移数据带来的数据损坏风险。
当然我写这篇文章主要是面向于想搞家庭服务器的进阶用户,不会涉及任何数据安全以及灾备的设置,只会针对于怎么用进行讲解罢了,还有 AIO 老法师可以绕道走了,只能说祝你好运。
通过命令行使用 NFS 存储卷
NFS 存储卷的创建
首先,在指定 driver 的情况下,完整的 Docker 存储卷创建命令为:
docker volume create \
--driver <DRIVER_NAME> \ # driver 名称
--opt|-o <DRIVER_OPTIONS> \ # driver 设置项,由 driver 定义,通常为键值对,一个driver可以定义一个或多个可设置项
<VOLUME_NAME> # volume 名称
那么我们可以按图填空了,但是稍有不同。首先这里使用的 driver 是 local,并不是 nfs,你需要在 driver 设置里指定 type=nfs|cifs
才是使用对应网络存储协议。此外挂载参数的设置也是通过 driver 设置指定的,所以对于 NFS 存储卷的完整创建命令如下:
docker volume create \
--driver local \
--opt type=nfs \
--opt device=:<NFS_EXPORT_PATH> \ # NFS 导出路径,注意有个冒号开头
--opt o=addr=<NFS_SERVER_IP>,<NFS_OPTION> \ # NFS 挂载参数
<VOLUME_NAME> # volume 名称
其中 device 填写你的 NFS 导出路径,比如你在 NFS 服务器的 /etc/exports 里设置了 /nfs *(rw,sync,no_root_squash)
,那么这里对应填写的就是 device=:/nfs
。
其中 o 参数里的 NFS_OPTION 是用于指定 NFS 挂载时使用的参数,参数释义可以点这里查看,参照你的需求进行设置即可,这里给出几个常用的挂载参数设置:
# NFSv3 标准挂载参数
addr=10.10.0.10,rw
# NFSv3 常用挂载参数,应对单一导出但使用子文件夹多处挂载的情况
addr=10.10.0.10,rw,nolock,soft
# NFSv4 标准挂载参数
addr=10.10.0.10,rw,nfsvers=4
好,到此为止你学废了吗?让我们实践一下,假设你的 NFS 服务器位于 192.168.1.10
,导出路径设置为 /opt/nfs
,要创建一个名为 my_nfs_volume
的卷来挂载,那么完整命令就是:
docker volume create \
--driver local \
--opt type=nfs \
--opt device=:/opt/nfs \
--opt o=addr=192.168.1.10,rw \
my_nfs_volume
在容器中使用 NFS 存储卷
这就很简单了,只需要像通常挂载一样把存储卷挂给容器就能用了:
docker run -d \
--name <CONTAINER_NAME> \ # 容器名称
--mount source=<VOLUME_NAME>,target=<CONTAINER_PATH> \ # 上一步创建的存储卷名以及要在容器内部映射的目标路径
<IMAGE_NAME>:<IMAGE_VER> # 镜像名称:版本
当然,你也可以通过直接创建服务的方式使用存储卷:
docker service create -d \
--name <SERVICE_NAME> \ # 服务名称
--mount source=<VOLUME_NAME>,target=<CONTAINER_PATH> \ # 上一步创建的存储卷名以及要在容器内部映射的目标路径
<IMAGE_NAME>:<IMAGE_VER> # 镜像名称:版本
在创建服务的方式使用存储卷时,可以将存储卷的创建和使用放到一起,简单来讲就是直接所有存储卷的设定参数直接给压缩成一行文本丢给 --mount
,非常不直观不是很推荐使用:
docker service create -d \
--name <SERVICE_NAME> \ # 服务名称
--mount 'type=volume,source=nfsvolume,target=<CONTAINER_PATH>,volume-driver=local,volume-opt=type=nfs,volume-opt=device=:<NFS_EXPORT_PATH>,volume-opt=o=addr=<NFS_SERVER_IP>,<NFS_OPTION>' \ # NFS 存储卷设定,参数释义参照上文
<IMAGE_NAME>:<IMAGE_VER> # 镜像名称:版本
通过 Compose 使用 NFS 存储卷
在有上文命令行使用的基础上,Compose 使用就很简单了,这里以 Nginx 为例直接给出一个完整的 Compose 代码:
# compose_example.yaml
volumes:
v_data: # 编排内使用的存储卷名,与 service 的 volumes 里保持一致即可,在此编排内唯一
name: nfs_nginx # 实际存储卷名,全局唯一
driver_opts: # 等同于 --opt 参数
type: "nfs"
o: "addr=10.10.0.10,nolock,soft,rw" # NFS 挂载参数
device: ":/opt/nfs/nginx" # NFS导出路径
services:
core:
image: nginx:latest
ports:
- 80:80
- 443:443
restart: unless-stopped
volumes:
- v_data:/public # 使用存储卷
注意事项
对于 device 设置项
在使用 NFSv3 的情况下,你可以设置导出一个路径,此时你可以通过任意客户端挂载该路径下存在的任意子文件夹,而无需设置 NFS exports。即假设你的 NFS 导出路径设置的是 /opt/nfs
,你在其中创建了个子文件夹 data
,此时你可以在创建 Volume 时直接设置 device=:/opt/nfs/data
,是可以正常挂载的。
但是 NFSv4 最好直接挂载导出路径,虽然 NFSv4 也可以这样挂载,但是一旦这样挂多了很容易导致多个客户端之间的 TCP 拥堵,后果是大家都拿不到自己想要的数据服务就都死机了。对于 NFSv4 最好还是从系统层面挂载为本地路径再通过 bind-mount 给容器使用。
在 Compose 中使用 NFS 存储卷的注意事项
在容器编排里如果你写了存储卷的内容,那么在编排启动时 Docker 会检测如果对应的存储卷没有创建,就会自动创建一个新的存储卷拿来用。这在使用本地存储卷时不会引发任何问题,毕竟你可以使用 docker volume prune
来一次性删除所有未被使用的本地存储卷,但是像 NFS 这种存储卷就不可以了,Docker 的卷清理命令仅针对本地存储卷,你必须手动删除对应的 NFS 存储卷才行。