如何使用Docker Compose v3在容器中直接挂载NFS共享/卷。

54

我有一个使用v3的compose文件,其中有3个服务共享/使用同一个卷。在使用Swarm模式时,我们需要创建额外的容器和卷来管理集群中的服务。

我计划使用NFS服务器,以便单个NFS共享将直接挂载在集群内的所有主机上。

我已经找到了以下两种方法,但需要在docker主机上执行额外步骤:

是否有一种标准方式,可以通过在docker compose v3中执行少量/无需操作(我知道无论如何都需要“nfs-common”包)就能直接使用/挂载NFS共享?


将NFS共享挂载到Docker容器内部 - Trevor Boyd Smith
6个回答

93

发现这个过程文档化程度极低,以下是使用 stack 和 docker compose 挂载 NFS 卷的正确方法。

最重要的是您需要使用 version: "3.2" 或更高版本。如果不这样做,您将遇到奇怪且不明显的错误。

第二个问题是,当卷的定义更改时,它们不会自动更新。这可能会让您认为更改不正确,而实际上只是没有应用更改。确保在所有可能的地方使用 docker rm VOLUMENAME,因为如果卷存在,则不会被验证。

第三个问题更多是 NFS 的问题 - 如果服务器上不存在该文件夹,NFS 文件夹将不会被创建。这就是 NFS 的工作方式。在进行任何操作之前,请确保它存在。

(除非您确定自己知道在做什么,否则请勿删除“soft”和“nolock”- 这会防止 Docker 在 NFS 服务器消失时冻结)

下面是一个完整的示例:

[root@docker docker-mirror]# cat nfs-compose.yml
version: "3.2"

services:
  rsyslog:
    image: jumanjiman/rsyslog
    ports:
      - "514:514"
      - "514:514/udp"
    volumes:
      - type: volume
        source: example
        target: /nfs
        volume:
          nocopy: true
volumes:
  example:
    driver_opts:
      type: "nfs"
      o: "addr=10.40.0.199,nolock,soft,rw"
      device: ":/docker/example"



[root@docker docker-mirror]# docker stack deploy --with-registry-auth -c nfs-compose.yml rsyslog
Creating network rsyslog_default
Creating service rsyslog_rsyslog
[root@docker docker-mirror]# docker stack ps rsyslog
ID                  NAME                IMAGE                       NODE                DESIRED STATE       CURRENT STATE                     ERROR               PORTS
tb1dod43fe4c        rsyslog_rsyslog.1   jumanjiman/rsyslog:latest   swarm-4             Running             Starting less than a second ago
[root@docker docker-mirror]#

现在,关于swarm-4:

root@swarm-4:~# docker ps
CONTAINER ID        IMAGE                       COMMAND                  CREATED             STATUS              PORTS               NAMES
d883e0f14d3f        jumanjiman/rsyslog:latest   "rsyslogd -n -f /e..."   6 seconds ago       Up 5 seconds        514/tcp, 514/udp    rsyslog_rsyslog.1.tb1dod43fe4cy3j5vzsy7pgv5
root@swarm-4:~# docker exec -it d883e0f14d3f df -h /nfs
Filesystem                Size      Used Available Use% Mounted on
:/docker/example          7.2T      5.5T      1.7T  77% /nfs
root@swarm-4:~#

此卷将在运行堆栈的任何Swarm节点上创建(但不会被销毁)。

root@swarm-4:~# docker volume inspect rsyslog_example
[
    {
        "CreatedAt": "2017-09-29T13:53:59+10:00",
        "Driver": "local",
        "Labels": {
            "com.docker.stack.namespace": "rsyslog"
        },
        "Mountpoint": "/var/lib/docker/volumes/rsyslog_example/_data",
        "Name": "rsyslog_example",
        "Options": {
            "device": ":/docker/example",
            "o": "addr=10.40.0.199,nolock,soft,rw",
            "type": "nfs"
        },
        "Scope": "local"
    }
]
root@swarm-4:~#

1
我正在使用compose v3.1的NFS。你认为可能会出现什么问题? 由于使用了soft和nolock,可能会发生数据损坏的情况,这个问题在你的回答中应该得到解决,对吗? - herm
@herm,也许xrobau在谈论多个副本与同一nfs服务器通信的问题。 - Galigator
3
'soft' 允许中断IO操作。 'nolock' 允许flock()调用始终成功。如果您在NFS上使用flock()与多个用户进行交互,那么您将显着降低磁盘IO速度,应重新设计您的应用程序。 - xrobau
1
@xrobau,这正是我一直需要的,可以摆脱麻烦且难以处理的Docker卷Netshare(DVN)EFS驱动程序。最初尝试时,导致我使用DVN的遗漏部分是在“driver_opts:”下的“device:”规范。谢谢。 - chrisg
@GuySoft - 请记住,一旦创建了卷,它们就不会被更新。将NFS服务器的IP地址作为变量(无论如何我认为你都做不到)只意味着在不同的机器上NFS挂载点将是不同的。最好的方法是创建一个新的卷(例如,“example_nfs2”),指向另一个NFS服务器,然后更改服务定义中卷的源。但是-说真的-卷在创建后不会被更新。这对于不谨慎的人来说是一个容易陷入的陷阱。 - xrobau
显示剩余2条评论

50

根据我需要使用卷的方式,我有以下3个选项。

第一种方法是直接创建命名卷,并在compose中将其用作外部卷,或在docker rundocker service create命令中用作命名卷。

  # create a reusable volume
  $ docker volume create --driver local \
      --opt type=nfs \
      --opt o=nfsvers=4,addr=nfs.example.com,rw \
      --opt device=:/path/to/dir \
      foo

接下来,有一个--mount语法可用于docker rundocker service create。这是一个相当长的选项,当您在另一个逗号分隔的选项中嵌入逗号分隔的选项时,需要将一些引号(转义以使shell不会删除它们)传递给正在运行的命令。我倾向于将其用于需要访问NFS的一次性容器(例如,用于设置NFS目录的实用程序容器):

  # or from the docker run command
  $ docker run -it --rm \
    --mount type=volume,dst=/container/path,volume-driver=local,volume-opt=type=nfs,\"volume-opt=o=nfsvers=4,addr=nfs.example.com\",volume-opt=device=:/host/path \
    foo

  # or to create a service
  $ docker service create \
    --mount type=volume,dst=/container/path,volume-driver=local,volume-opt=type=nfs,\"volume-opt=o=nfsvers=4,addr=nfs.example.com\",volume-opt=device=:/host/path \
    foo

最后,您可以在compose文件中定义命名卷。在执行此操作时,需要注意一点:命名卷仅会在创建一次,并且不会因为任何更改而更新。因此,如果您需要修改命名卷,则需要给它一个新名称。

  # inside a docker-compose file
  ...
  services:
    example-app:
      volumes:
      - "nfs-data:/data"
  ...
  volumes:
    nfs-data:
      driver: local
      driver_opts:
        type: nfs
        o: nfsvers=4,addr=nfs.example.com,rw
        device: ":/path/to/dir"
  ...
在每个示例中:
  • 类型设置为nfs,而不是nfs4。这是因为Docker在addr字段上提供了一些不错的功能,但仅适用于nfs类型。
  • o是传递给挂载系统调用的选项。挂载系统调用和Linux中的挂载命令之间的一个区别是设备部分已移动到addr选项中的冒号之前。
  • nfsvers用于设置NFS版本。这样可以避免操作系统先尝试其他NFS版本而导致延迟。
  • addr可能是DNS名称,当您使用type = nfs时,而不仅仅是IP地址。如果您有多个具有不同NFS服务器且使用相同DNS名称的VPC,或者如果您想将来调整NFS服务器而不更新每个卷挂载,则此方法非常有用。
  • rw(读写)之类的其他选项可以传递给o选项。
  • device字段是远程NFS服务器上的路径。前导冒号是必需的。这是由于挂载命令如何将IP地址移动到系统调用的addr字段的产物。在将卷挂载到容器中之前,此目录必须存在于远程主机上。
  • --mount语法中,dst字段是容器内部的路径。对于命名卷,您可以在docker run -v命令中的卷挂载(短语法)右侧设置此路径。

如果访问远程NFS卷时出现权限问题,我遇到的常见原因是容器以root身份运行,并且NFS服务器设置为root squash(将所有root访问更改为nobody用户)。您需要将容器配置为以已知的非root UID身份运行,以便其具有访问NFS服务器上目录的权限,或者在NFS服务器上禁用root squash。


1
this should be the answer :'( - vee
3
@ValerioZhang,我有点惊讶另一个答案得到了更多的投票,因为它是在这篇文章之后发布的,而且重复了这里发布的大部分内容,但这就是 Stack Overflow 有些日子的运作方式。 - BMitch
6
你的写作是最好的,内容全面,最重要的是还支持即插即用。耸肩。再次感谢米奇的贡献! - vee
1
@RichaGarg 本地卷驱动程序将设置传递给Linux内核的挂载系统调用。如果您正在运行Windows容器,则无法使用Linux内核接受这些选项。 - BMitch
@BMitch感谢您的回复。您能否考虑一下这个问题:https://stackoverflow.com/questions/64430814/best-option-to-access-network-share-in-docker-windows-container-with-docker-comp? - Richa Garg
显示剩余3条评论

10

是的,你可以直接从compose文件中引用NFS:

volumes:
   db-data:
      driver: local
      driver_opts:
        type: nfs
        o: addr=$SOMEIP,rw
        device: ":$PathOnServer"

类似地,您可以在每个主机上创建一个nfs卷。

docker volume create --driver local --opt type=nfs --opt o=addr=$SomeIP,rw --opt device=:$DevicePath --name nfs-docker

5

我解决了AWS EFS的问题,以下是可行的方法:

  1. 创建EFS(不要忘记在安全组中开放NFS端口2049)
  2. 安装nfs-common软件包:

    sudo apt-get install -y nfs-common

  3. 检查您的EFS是否正常工作:

    mkdir efs-test-point
    sudo chmod go+rw efs-test-point
    sudo mount -t nfs -o nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,noresvport [YOUR_EFS_DNS]:/ efs-test-point
    touch efs-test-point/1.txt
    sudo umount efs-test-point/
    ls -la efs-test-point/

    目录必须为空

    sudo mount -t nfs -o nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,noresvport [YOUR_EFS_DNS]:/ efs-test-point

    ls -la efs-test-point/

    文件1.txt必须存在

  4. 配置docker-compose.yml文件:

    services:
      sidekiq:
        volumes:
          - uploads_tmp_efs:/home/application/public/uploads/tmp
      ...
    volumes:
      uploads_tmp_efs:
        driver: local
        driver_opts:
          type: nfs
          o: addr=[YOUR_EFS_DNS],nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2
          device: [YOUR_EFS_DNS]:/


3

将驱动选项类型更改为NFS4后,我的问题得到了解决。

volumes:
  my-nfs-share:
    driver: local
    driver_opts:
      type: "nfs4"
      o: "addr=172.24.0.107,rw"
      device: ":/mnt/sharedwordpress"

1
如果您也在使用AutoFS,在docker-compose中,您可以将:shared添加到所有路径中,如下所示:
volumes:
  - /some/nfs/mounted:/path:shared

我在编程中遇到了一个问题,感谢我的同事提供了更好的解决方案。我们的用户一直报错“符号链接太多”...

干杯!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接