8. 共享存储
即使使用 qcow2 文件作为硬盘存储格式,按需分配实际存储空间,也无法避免 Proxmox VE 本地存储耗尽的问题,以我的一个 dev 开发机器,就是由于长期编译程序,导致根分区对应的 qcow2 文件大小已接近 300G,而实际物理设备只有 512GB,为了压缩 qcow2 文件回收存储空间,不得不先迁移到一个大量容量 HDD,整个过程比较折腾人。
计算和存储分离是常见手法,为了更好地利用现有存储,过去我尝试了 NFS、Samba、WebDAV、S3 等存储方案,这里做一个记录。
这里使用一个 Debian 12 虚拟机部署存储服务端,内网 IP 地址为 192.168.123.2,一块 HDD 和一块 SSD 分别挂载到 /mnt/external-hdd 与 /mnt/external-ssd,客户端也是一个 Debian 12 虚拟机,测试时会将远端存储挂载到本地的 /mnt/external-hdd 与 /mnt/external-ssd。
由于 HDD 是通过 USB 接入 Proxmox VE 主机然后透传给虚拟机的,为了避免使用 /etc/fstab 挂载可能导致的系统卡死问题,这里改用 systemd 执行挂载,配置如下:
➜ ~ lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
sda 8:0 0 64G 0 disk
├─sda1 8:1 0 512M 0 part /boot/efi
└─sda2 8:2 0 63.5G 0 part /
sdb 8:16 0 223.6G 0 disk
└─sdb1 8:17 0 223.6G 0 part /mnt/external-ssd
sdc 8:32 0 1.8T 0 disk /mnt/external-hdd
sr0 11:0 1 4M 0 rom
➜ ~ blkid /dev/sdb
/dev/sdb: PTUUID="285361c4-8e79-4773-a4da-f1e3af654b0c" PTTYPE="gpt"
➜ ~ blkid /dev/sdc
/dev/sdc: UUID="73246c75-2ce4-48ca-8bfd-cb95fdcc8afc" BLOCK_SIZE="4096" TYPE="ext4"
➜ ~ cat /lib/systemd/system/mnt-external\\x2dhdd.mount
[Unit]
Description=Mount data partition
[Mount]
What=/dev/disk/by-uuid/73246c75-2ce4-48ca-8bfd-cb95fdcc8afc
Where=/mnt/external-hdd
Type=ext4
Options=defaults
[Install]
WantedBy=multi-user.target
➜ ~ cat /lib/systemd/system/mnt-external\\x2dssd.mount
[Unit]
Description=Mount data partition
[Mount]
What=/dev/disk/by-uuid/9abbe539-05c4-456c-ab0d-bdf2a75e92ab
Where=/mnt/external-ssd
Type=btrfs
Options=defaults
[Install]
WantedBy=multi-user.target
➜ ~ systemctl enable --now mnt-external\\x2dssd.mount
➜ ~ systemctl enable --now mnt-external\\x2dhdd.mount
➜ ~ df -Th |grep /mnt/external
/dev/sdb1 btrfs 224G 12G 211G 6% /mnt/external-ssd
/dev/sdc ext4 1.8T 1.3T 428G 76% /mnt/external-hdd
- HDD 对应的设备为 /dev/sdc,未分区,直接格式化为 ext4,使用 blkid 挂载设备,避免重新拔插设备、添加新设备后导致对应的 /dev/sdx 设备变化影响挂载
- SSD 对应的设备为 /dev/sdb,创建一个主分区,格式化为 btrfs,从使用体验看,除了需要 raid 的场景外,最好创建一个主分区再格式化为 btrfs
- mount 文件的命名需要与设备挂载路径一致,挂载路径中含有特殊符号时需要转义,比如将
-改为\\x2d - 使用 systemd 挂载磁盘的另一个好处是可以配置 service 依赖关系,比如让 docker 等待两个设备挂载成功后再启动
Debian 12 虚拟机的内网 IP 为 192.168.123.2,为了方便以后迁移数据,我给他在内网解析了一个域名,若以后有专用的 NAS 存储数据,只需要做服务端的数据迁移就行。
NFS 是 Linux 上最简单的共享文件存储方案,安装与配置都不复杂。
安装NFS服务端
apt install -y nfs-kernel-server
配置共享目录
/etc/exports 文件内容如下,这里添加了 insecure 参数允许通过客户端使用 1024 以上端口连接服务器,兼容 macOS 的使用场景。
➜ ~ cat /etc/exports
# /etc/exports: the access control list for filesystems which may be exported
# to NFS clients. See exports(5).
#
# Example for NFSv2 and NFSv3:
# /srv/homes hostname1(rw,sync,no_subtree_check) hostname2(ro,sync,no_subtree_check)
#
# Example for NFSv4:
# /srv/nfs4 gss/krb5i(rw,sync,fsid=0,crossmnt,no_subtree_check)
# /srv/nfs4/homes gss/krb5i(rw,sync,no_subtree_check)
#
/mnt/external-ssd *(rw,sync,insecure,no_root_squash,no_subtree_check)
/mnt/external-hdd *(rw,sync,insecure,no_root_squash,no_subtree_check)
重载配置
systemctl restart nfs-kernel-server
Linux 客户端上可以使用命令行或者 systemd 挂载 NFS 存储,我们需要先安装 NFS 客户端:apt install -y nfs-common。
命令行
➜ ~ mktemp -d
/tmp/tmp.3OCVVuHu9o
➜ ~ mount -t nfs 192.168.123.2:/mnt/external-ssd /tmp/tmp.3OCVVuHu9o
➜ ~ df -Th /tmp/tmp.3OCVVuHu9o
Filesystem Type Size Used Avail Use% Mounted on
192.168.123.2:/mnt/external-ssd nfs4 224G 12G 211G 6% /tmp/tmp.3OCVVuHu9o
➜ ~ umount /tmp/tmp.3OCVVuHu9o
systemd
mount 文件的写法是通用的,这里修改 What 为远端 NFS 存储,type 为 nfs,如下:
➜ ~ cat /lib/systemd/system/mnt-external\\x2dssd.mount
[Unit]
Description=Mount NFS share
After=network-online.target
[Mount]
What=192.168.123.2:/mnt/external-ssd
Where=/mnt/external-ssd
Type=nfs
Options=defaults
[Install]
WantedBy=multi-user.target
➜ ~ systemctl enable --now mnt-external\\x2dssd.mount
➜ ~ df -Th /mnt/external-ssd
Filesystem Type Size Used Avail Use% Mounted on
192.168.123.2:/mnt/external-ssd nfs4 224G 12G 211G 6% /mnt/external-ssd
macOS 与 Windows 也支持挂载 NFS,默认只读,以 macOS 为例,在 Finder 中使用 Cmd + k 连接服务器即可:


NFS 只读带来的一个好处是 macOS 无法在远端存储上写入 .DS_Store 文件了(能减少垃圾文件产生),需要写入文件时可以通过网页操作,比如 filebrowser。
Docker 支持创建 NFS 类型的 Volume 用于挂载到容器内,如下:
docker volume create \
--driver local \
--opt type=nfs \
--opt o=addr=192.168.123.2,rw,nfsvers=4 \
--opt device=:/mnt/external-ssd \
external-ssd
创建完成后可以检查 volume 参数:
➜ ~ docker volume inspect external-ssd
[
{
"CreatedAt": "2024-09-18T11:25:37+08:00",
"Driver": "local",
"Labels": null,
"Mountpoint": "/var/lib/docker/volumes/external-ssd/_data",
"Name": "external-ssd",
"Options": {
"device": ":/mnt/external-ssd",
"o": "addr=192.168.123.2,rw,nfsvers=4",
"type": "nfs"
},
"Scope": "local"
}
]
使用 alpine:3.18 容器镜像测试挂载,如下:
➜ ~ docker run --rm -v external-ssd:/mnt/external-ssd alpine:3.18 sh -c 'mount | grep nfs'
:/mnt/external-ssd on /mnt/external-ssd type nfs4 (rw,relatime,vers=4.0,rsize=524288,wsize=524288,namlen=255,hard,proto=tcp,timeo=600,retrans=2,sec=sys,clientaddr=192.168.123.3,local_lock=none,addr=192.168.123.2)
Samba 相比 NFS 有更好的兼容性和易用性,无论是 Windows、macOS 还是 Linux,都可以轻松连接 Samba 服务器获取文件共享的能力,两者的对比如下:
| 特性 | NFS | Samba |
|---|---|---|
| 协议类型 | 主用于 Unix/Linux 系统 | 主用于 Windows 系统 |
| 操作系统支持 | Linux、Unix 等 | Windows、Linux、Unix |
| 文件系统访问 | 直接通过文件系统调用 | 通过 SMB/CIFS 协议 |
| 性能 | 较高,特别是在 Unix/Linux 系统上 | 较低于 NFS,尤其在大型文件传输时 |
| 安全性 | 支持基本的权限控制,依赖于用户和组 | 提供更细粒度的安全设置,包括用户验证 |
| 配置复杂性 | 配置相对简单,依赖于 /etc/exports 文件 | 配置稍复杂,需要设置 smb.conf |
| 跨平台支持 | 主要为 Unix/Linux,Windows 支持较差 | 跨平台,Windows 和 Linux 之间兼容性好 |
| 文件锁定 | 支持文件锁定 | 支持文件锁定 |
| 性能调优 | 可以通过调整参数进行优化 | 也支持性能优化,但相对复杂 |
| 文件共享 | 适合大规模文件共享 | 适合小型文件共享和家庭网络 |
| 使用场景 | 数据库、虚拟机存储等 | 文件共享、打印服务等 |
对于家庭用户来说,Samba 的配置也不复杂,具体操作如下:
安装服务端
apt install -y samba
配置共享目录
编辑 /etc/samba/smb.conf,使用 ; 注释掉 [homes]、[printers]、[printe$] 等配置,然后在末尾添加以下内容:
[external-hdd]
comment = external-hdd
path = /mnt/external-hdd
public = yes
guest ok = yes
browseable = yes
read only = no
force user = root
force group = root
[external-ssd]
comment = external-ssd
path = /mnt/external-ssd
public = yes
guest ok = yes
browseable = yes
read only = no
force user = root
force group = root
重载配置
systemctl restart smbd
上述配置中,设置了 external-ssd 与 external-hdd 两个共享目录,并允许公开访问和读写,以 root 用户的权限操作文件。
Linux 客户端上可以使用命令行或者 systemd 挂载 NFS 存储,我们需要先安装 Samba 客户端:apt install -y smbclient。
命令行
挂载路径与 NFS 有一些差异,远端存储的格式为 //服务端地址/共享目录
➜ ~ mount -t cifs -o username=none,password=none //192.168.123.2/external-ssd /tmp/tmp.yDPru6ot52
➜ ~ df -Th /tmp/tmp.yDPru6ot52
Filesystem Type Size Used Avail Use% Mounted on
//192.168.123.2/external-ssd cifs 224G 14G 211G 6% /tmp/tmp.yDPru6ot52
➜ ~ umount /tmp/tmp.yDPru6ot52
systemd
这里修改 What 为远端 Samba 存储,type 为 cifs,Options 中可以配置挂载参数,并设置了用户名密码为 none。
➜ ~ cat /lib/systemd/system/mnt-external\\x2dssd.mount
[Unit]
Description=Mount Samba share
After=network-online.target
[Mount]
What=//192.168.123.2/external-ssd
Where=/mnt/external-ssd
Type=cifs
Options=username=none,password=none
[Install]
WantedBy=multi-user.target
➜ ~ systemctl enable --now mnt-external\\x2dssd.mount
➜ ~ df -Th /mnt/external-ssd
Filesystem Type Size Used Avail Use% Mounted on
//192.168.123.2/external-ssd cifs 224G 14G 211G 6% /mnt/external-ssd
启动 Samba 服务端后,macOS 和 Windows 客户端就可以自动发现局域网内网的 Samba 存储:

Docker 也支持创建 CIFS 类型的 Volume 用于挂载到容器内,如下:
docker volume create \
--driver local \
--opt type=cifs \
--opt device=//192.168.123.2/external-ssd \
--opt o=addr=192.168.123.2,username=none,password=none,file_mode=0777,dir_mode=0777 \
--name external-ssd
创建完成后可以检查 volume 参数:
➜ ~ docker volume inspect external-ssd
[
{
"CreatedAt": "2024-09-18T11:37:40+08:00",
"Driver": "local",
"Labels": null,
"Mountpoint": "/var/lib/docker/volumes/external-ssd/_data",
"Name": "external-ssd",
"Options": {
"device": "//192.168.123.2/external-ssd",
"o": "addr=192.168.123.2,username=none,password=none,file_mode=0777,dir_mode=0777",
"type": "cifs"
},
"Scope": "local"
}
]
使用 alpine:3.18 容器镜像测试挂载,如下:
➜ ~ docker run --rm -v external-ssd:/mnt/external-ssd alpine:3.18 sh -c 'mount | grep cifs'
//192.168.123.2/external-ssd on /mnt/external-ssd type cifs (rw,relatime,vers=3.1.1,cache=strict,username=none,uid=0,noforceuid,gid=0,noforcegid,addr=192.168.123.2,file_mode=0777,dir_mode=0777,soft,nounix,serverino,mapposix,rsize=4194304,wsize=4194304,bsize=1048576,retrans=1,echo_interval=60,actimeo=1,closetimeo=1)
若需要用户认证,最简单的办法是添加一个 Linux 用户,然后为共享目录绑定用户,下面创建一个 debian 用户,密码为 debian,用于访问 /debian 共享目录,如下:
# 创建用户
➜ ~ useradd debian
# 为用户配置samba密码
➜ ~ smbpasswd -a debian
New SMB password:
Retype new SMB password:
Added user debian.
# 创建目录并配置权限为debian用户
➜ ~ mkdir /debian
➜ ~ chown debian:debian /debian
然后编辑 /etc/samba/smb.conf,添加以下配置:
[debian]
comment = Debian
path = /debian
public = no
guest ok = no
browseable = yes
read only = no
write list = debian
valid users = debian
重启 samba 服务后,在客户端配置用户密码连接 Samba,macOS 上使用如下,在服务器连接中添加用户名:

点击连接会提示输入账号密码:

挂载成功后可以右键中可看到新建文件夹的选项,说明具备写入权限:

如果需要删除用户,操作如下:
➜ ~ smbpasswd -d debian
Disabled user debian.
➜ ~ smbpasswd -x debian
Deleted user debian.
➜ ~ userdel debian
WebDAV 与 S3 都是基于 HTTP 协议的,更适合应用层,而挂载到本地使用时需要 FUSE(Filesystem in Userspace)提供用户空间的文件系统支持,性能相比 NFS 和 Samba 会有比较大的折扣,这里有几个工具可以支持 WebDAV 和 S3 存储。
参考 CNCF笔记 / 使用K3s部署容器化应用 / 2. 基础配置 / 2.3 公共服务。
Minio 是一个开源的 S3 存储服务,支持多用户和高可用部署,复杂的应用场景可以考虑使用 MinIO。
集成 github.com/mholt/caddy-webdav 模块后,可以来为 caddy 添加 WebDAV 支持。
macOS 和 Windows 上可以直接挂载 WebDAV 存储,Linux 需要安装 davfs2 来支持挂载 WebDAV 存储,如下:
➜ ~ apt install -y davfs2
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
davfs2 is already the newest version (1.6.1-1).
0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.
➜ ~ mount -t davfs http://192.168.123.2:8080/mnt/external-ssd /tmp/tmp.WoamaAg85F
Please enter the username to authenticate with server
http://192.168.123.2:8080/mnt/external-ssd or hit enter for none.
Username:
Please enter the password to authenticate user with server
http://192.168.123.2:8080/mnt/external-ssd or hit enter for none.
Password:
➜ ~ df -Th /tmp/tmp.WoamaAg85F
Filesystem Type Size Used Avail Use% Mounted on
http://192.168.123.2:8080/mnt/external-ssd fuse 1.3T 763G 509G 61% /tmp/tmp.WoamaAg85F
Rclone 是一个用于管理云存储上文件的命令行程序,它既可以作为客户端使用,也可以提供简单的服务端功能,Debian 12 上可以直接安装:
apt install -y rclone
官网上可以下载最新版本:Downdloads
WebDAV
服务端使用命令行启动
rclone serve webdav /mnt/external-ssd --addr 192.168.123.2:8080 --baseurl /mnt/external-ssd
客户端创建配置文件后可以使用命令行浏览远端存储:
➜ ~ cat .config/rclone/rclone.conf
[webdav]
type = webdav
url = http://192.168.123.2:8080
vendor = other
➜ ~ rclone ls webdav:/mnt/external-ssd/
420872192 libvirt/iso/Fedora-Cloud-Base-Generic.aarch64-40-1.14.qcow2
222572544 libvirt/iso/alpine-standard-3.20.2-arm64.iso
72740864 libvirt/iso/alpine-virt-3.20.2-arm64.iso
551858176 libvirt/iso/debian-12.6.0-arm64-netinst.iso
222429184 libvirt/iso/generic_alpine-3.20.2-aarch64-uefi-cloudinit-r0.qcow2
626851840 libvirt/iso/ubuntu-22.04-arm64-tpl.img
2490609664 libvirt/iso/ubuntu-24.04-live-server-arm64-64k.iso
7048935424 libvirt/iso/uos-server-20-1070e-arm64.iso
136445952 libvirt/images/fedora.qcow2
62914560 libvirt/images/ubuntu22.04-2024-8-2.qcow2
或者挂载到本地目录:
➜ ~ mktemp -d
/tmp/tmp.YD9zbVQK2v
➜ ~ rclone mount webdav:/mnt/external-ssd/ /tmp/tmp.YD9zbVQK2v
2024/09/18 12:55:00 NOTICE: webdav root 'mnt/external-ssd': --vfs-cache-mode writes or full is recommended for this remote as it can't stream
挂载操作会前台运行,我们需要新开一个终端浏览文件:
➜ ~ df -Th /tmp/tmp.YD9zbVQK2v/
Filesystem Type Size Used Avail Use% Mounted on
webdav:mnt/external-ssd fuse.rclone 1.0P 0 1.0P 0% /tmp/tmp.YD9zbVQK2v
➜ ~ tree /tmp/tmp.YD9zbVQK2v
/tmp/tmp.YD9zbVQK2v
└── libvirt
├── images
│ ├── fedora.qcow2
│ └── ubuntu22.04-2024-8-2.qcow2
└── iso
├── alpine-standard-3.20.2-arm64.iso
├── alpine-virt-3.20.2-arm64.iso
├── debian-12.6.0-arm64-netinst.iso
├── Fedora-Cloud-Base-Generic.aarch64-40-1.14.qcow2
├── generic_alpine-3.20.2-aarch64-uefi-cloudinit-r0.qcow2
├── ubuntu-22.04-arm64-tpl.img
├── ubuntu-24.04-live-server-arm64-64k.iso
└── uos-server-20-1070e-arm64.iso
4 directories, 10 files
S3
服务端使用命令行启动:
rclone serve s3 /mnt/external-ssd --auth-key ACCESS_KEY_ID,SECRET_ACCESS_KEY --addr :8080
客户端创建配置文件后可以使用命令行浏览远端存储:
➜ ~ cat .config/rclone/rclone.conf
[s3]
type = s3
provider = Rclone
endpoint = http://192.168.123.2:8080/
access_key_id = ACCESS_KEY_ID
secret_access_key = SECRET_ACCESS_KEY
use_multipart_uploads = false
➜ ~ rclone ls s3:/
626851840 libvirt/iso/ubuntu-22.04-arm64-tpl.img
62914560 libvirt/images/ubuntu22.04-2024-8-2.qcow2
222572544 libvirt/iso/alpine-standard-3.20.2-arm64.iso
551858176 libvirt/iso/debian-12.6.0-arm64-netinst.iso
7048935424 libvirt/iso/uos-server-20-1070e-arm64.iso
2490609664 libvirt/iso/ubuntu-24.04-live-server-arm64-64k.iso
222429184 libvirt/iso/generic_alpine-3.20.2-aarch64-uefi-cloudinit-r0.qcow2
72740864 libvirt/iso/alpine-virt-3.20.2-arm64.iso
420872192 libvirt/iso/Fedora-Cloud-Base-Generic.aarch64-40-1.14.qcow2
136445952 libvirt/images/fedora.qcow2
也可以挂载到本地,每个子目录就是一个 bucket:
➜ ~ mktemp -d
/tmp/tmp.rSJm1joU1g
➜ ~ rclone mount s3:/libvirt /tmp/tmp.rSJm1joU1g
新开一个终端浏览文件:
➜ ~ df -Th /tmp/tmp.rSJm1joU1g
Filesystem Type Size Used Avail Use% Mounted on
s3:libvirt fuse.rclone 1.0P 0 1.0P 0% /tmp/tmp.rSJm1joU1g
➜ ~ tree /tmp/tmp.rSJm1joU1g
/tmp/tmp.rSJm1joU1g
├── images
│ ├── fedora.qcow2
│ └── ubuntu22.04-2024-8-2.qcow2
└── iso
├── alpine-standard-3.20.2-arm64.iso
├── alpine-virt-3.20.2-arm64.iso
├── debian-12.6.0-arm64-netinst.iso
├── Fedora-Cloud-Base-Generic.aarch64-40-1.14.qcow2
├── generic_alpine-3.20.2-aarch64-uefi-cloudinit-r0.qcow2
├── ubuntu-22.04-arm64-tpl.img
├── ubuntu-24.04-live-server-arm64-64k.iso
└── uos-server-20-1070e-arm64.iso
3 directories, 10 files