Docker容器使用NFS¶
备注
Kubernetes Atlas 在Docker基础上采用了 Kubernetes持久化存储卷 实现更为抽象化的持久化存储。
简单来说,Docker使用NFS存储有两种形式:
方法一:在容器内部安装
nfs-utils
,就如同常规到NFS客户端一样,在容器内部直接通过rpcbind方式挂载NFS共享输出,这种方式需要每个容器独立运行rpcbind服务,并且要使用 Docker systemd进程管理器 ,复杂且消耗较多资源。不过,优点是完全在容器内部控制,符合传统SA运维方式。方法二:Host主机上创建NFS类型的Docker Volume,然后将docker volume映射到容器内部,这样容器就可以直接使用Docker共享卷,这种方式最为简洁优雅。
方法三:使用Docker volume plugin,例如 ContainX/docker-volume-netshare 可以直接将NFS共享卷作为容器卷挂载(就不需要在host主机上去执行挂载NFS命令了)
方法一:在容器内部使用NFS¶
要知道,容器并不是完整的虚拟机,天然就是瘦客户端并且有意削减了部分操作系统功能。
为了能够在容器中挂载NFS,我们需要同时运行多个服务,这样我们就需要有一个容器内部运行的 Docker init进程管理器 以便启动多个服务来支持NFS挂载。例如,我们可以选择 Docker systemd进程管理器 。
这里的案例假设我们已经构建了一个使用 Docker systemd进程管理器 的容器 centos8-ssh
,然后我们来实践NFS挂载部署。
备注
如果使用 NFSv4 ,就不需要安装 nfs-utils
,也不需要设置 systemd
NFS服务器¶
我的测试NFS服务部署在运行Docker服务的物理主机上,该物理服务器是 Ubuntu Linux ,已完成 Ubuntu NFS部署 ,共享目录设置如下:
/etc/exportfs
/data *(rw,sync,no_root_squash,no_subtree_check)
详细部署方法和解释见 Ubuntu NFS部署
NFS客户端¶
在容器内部安装
nfs-utils
dnf install nfs-utils
在容器内部配置
/etc/fstab
如下:172.17.0.1:/data /data nfs rw,soft,intr,vers=3,proto=tcp,rsize=32768,wsize=32768 0 0
在容器内部执行挂载:
mkdir /data mount /data
报错:
mount.nfs: Operation not permitted
这个问题和 Docker systemd进程管理器 相同,需要赋予运行容器 CAP_SYS_ADMIN
能力,所以需要重新创建运行容器,并传递参数 --cap-add sys_admin
mount.nfs: access denied¶
重新增加了
CAP_SYS_ADMIN
能力创建容器后,再次挂载NFS卷,提示错误:mount.nfs: access denied by server while mounting 172.17.0.1:/data
但是,服务器端命名已经设置了 /data <world>
输出给所有客户端,为何会拒绝?
使用 -v
参数挂载:
mount -v /data
显示输出:
mount.nfs: timeout set for Fri Jan 22 08:16:08 2021
mount.nfs: trying text-based options 'rw,soft,intr,vers=3,proto=tcp,rsize=32768,wsize=32768,addr=172.17.0.1'
mount.nfs: prog 100003, trying vers=3, prot=6
mount.nfs: trying 172.17.0.1 prog 100003 vers 3 prot TCP port 2049
mount.nfs: prog 100005, trying vers=3, prot=6
mount.nfs: trying 172.17.0.1 prog 100005 vers 3 prot TCP port 43999
mount.nfs: mount(2): Permission denied
mount.nfs: access denied by server while mounting 172.17.0.1:/data
检查服务器的rpc输出:
rpcinfo -p 172.17.0.1
显示输出:
program vers proto port service
100000 4 tcp 111 portmapper
100000 3 tcp 111 portmapper
100000 2 tcp 111 portmapper
100000 4 udp 111 portmapper
100000 3 udp 111 portmapper
100000 2 udp 111 portmapper
100005 1 udp 43059 mountd
100005 1 tcp 46359 mountd
100005 2 udp 37724 mountd
100005 2 tcp 39809 mountd
100005 3 udp 41232 mountd
100005 3 tcp 43999 mountd
100003 3 tcp 2049 nfs
100003 4 tcp 2049 nfs
100227 3 tcp 2049 nfs_acl
100003 3 udp 2049 nfs
100227 3 udp 2049 nfs_acl
100021 1 udp 34464 nlockmgr
100021 3 udp 34464 nlockmgr
100021 4 udp 34464 nlockmgr
100021 1 tcp 35037 nlockmgr
100021 3 tcp 35037 nlockmgr
100021 4 tcp 35037 nlockmgr
在容器内部检查服务器端输出的NFS共享:
showmount -e 172.17.0.1
显示输出:
Export list for 172.17.0.1:
/data *
通过tcpdump抓包:
tcpdump -s0 -i eth0 host 172.17.0.1 -w /tmp/client.pcap
然后通过wireshark分析:
tshark -tad -n -r /tmp/client.pcap -Y 'frame.number == 500' -O rpc | sed '/^Re/,$ !d'
输出显示:
Running as user "root" and group "root". This could be dangerous.
tcpdump的分析 nfs.status!=0
tshark -tad -nr /tmp/client.pcap -Y nfs.status!=0
同样显示输出:
Running as user "root" and group "root". This could be dangerous.
在NFS服务器端检查输出:
cat /var/lib/nfs/etab
显示如下:
/data *(rw,sync,wdelay,hide,nocrossmnt,secure,no_root_squash,no_all_squash,no_subtree_check,secure_locks,acl,no_pnfs,anonuid=65534,anongid=65534,sec=sys,rw,secure,no_root_squash,no_all_squash)
警告
暂时还没有解决容器内部直接mount NFS问题,待进一步排查
方法二:Docker NFS volume(推荐)¶
采用Docker NFS volume的方式更为简单明了,实际上也就是先在host主机上挂载NFS卷,然后通过卷映射方式映射到容器内部。这种方式不需要给容器特殊的权限,也不需要运行systemd这样沉重的进程管理器,特别适合轻量级运行容器。当然,你也可以构建 Docker tini进程管理器 来运行一个轻量级的进程管理器,甚至使用 Docker systemd进程管理器 来构建复杂的 “富容器” 。
在Host主机上挂载NFS卷,即编辑host主机
/etc/fstab
添加如下配置:172.17.0.1:/data /container-data nfs rw,soft,intr,vers=3,proto=tcp,rsize=32768,wsize=32768 0 0
然后在host主机上挂载NFS卷:
mkdir /container-data mount /container-data
挂载以后在host主机上检查
df -h
可以看到NFS已经挂载:172.17.0.1:/data 117G 12G 101G 11% /container-data
现在我们启动一个容器并且映射这个挂载的NFS卷:
docker run -itd -p 1222:22 --hostname centos8-nfs --name centos8-nfs \ -v /container-data:/container-data centos:8
使用
docker ps | grep centos8-nfs
检查可以看到:7a9e6663a988 centos:8 "/bin/bash" 9 seconds ago Up 7 seconds 0.0.0.0:1222->22/tcp centos8-nfs
然后我们进入这个
centos8-nfs
容器:docker exec -it centos8-nfs /bin/bash
在容器内部检查磁盘
df -h
可以看到:Filesystem Size Used Avail Use% Mounted on ... 172.17.0.1:/data 117G 12G 101G 11% /container-data
方法三:使用Docker volume plugin¶
ContainX/docker-volume-netshare 是一个Docker plugin,可以用来挂载NFS v3/4, AWS EFS 或者 CIFS到容器内部。
备注
使用netshare插件部署安装比较麻烦,没有集成到发行版中,并且随着Kubernetes发展,较少采用Docker volume plugin方式。所以我没有具体实践这个方法,仅供参考。后续如果有实践需求,例如需要在简单的Docker环境挂载不同的NFS/CIFS等卷情况,我再实践。
安装netshare插件:
go get github.com/ContainX/docker-volume-netshare go build
也可以通过二进制安装,提供Ubuntu/Debian安装包:
wget https://github.com/ContainX/docker-volume-netshare/releases/download/v0.36/docker-volume-netshare_0.36_amd64.deb
sudo dpkg -i docker-volume-netshare_0.36_amd64.deb
修改
/etc/default/docker-volume-netshare
启动参数启动服务:
service docker-volume-netshare start
使用¶
运行插件 - 可以添加到systemd或者在后台运行:
sudo docker-volume-netshare nfs
启动容器:
docker run -i -t --volume-driver=nfs -v nfshost/path:/mount ubuntu /bin/bash
参考¶
TECH::Using NFS with Docker – Where does it fit in? - 原作者另外写了两篇有关使用NetApp FlexGroup volumes和加密NFS mount文章,可以进一步参考
Mount failed with mount: mount.nfs: access denied by server while mounting error Red Hat提供了非常好的debug方法,建议参考
“mount.nfs: access denied by server while mounting” – how to resolve
Mounting nfs shares inside docker container 提供了docker挂载NFS的多种方法概述