< 返回博客

完善 NAS 方案


硬件

目前使用的是天钡 WTR R1,配置如下:

  • CPU: Intel N100

  • 内存:16G

  • 硬盘:256G 固态做系统盘,4T+16T 机械做数据盘

系统

PVE

PVE是一个可以运行在裸机上的开源虚拟机系统,基于debian和qemu/kvm,同时提供了方便的web和cli界面。官网:https://pve.proxmox.com/

虚拟机配置

目前我的主力nas系统是一个叫做OpenMediaVault的系统,相比于命令行,它提供了一个简单的管理nas的图形界面,方便快速配置一些服务。

这里我贴上在pve里面的虚拟机配置,就不一一细讲了:

其中配置比较麻烦的是最下面的PCI设备,这是将显卡直通到了虚拟机里,需要启用IOMMU等功能,我也是一知半解地搞好了,有需要的可以搜索关键字“PVE显卡直通”查详细资料。

官网的资料:PCI(e)_PassthroughPCI_Passthrough

磁盘

物理磁盘上,目前我有两块机械硬盘(4T+16T),一块固态(256G),没有组raid,纯手工管理。

固态上主要用于放置虚拟系统的虚拟盘,因此其中跑的各种服务的数据库也在固态上放着,能加快服务的访问。

机械盘里面主要存数据,比如下载的电影电视、备份的照片与文件等等。

通常,使用pve时每个系统会挂在各自的硬盘,但是对我来说,各个系统之间数据共享是很重要的,例如我在群晖(单独的系统)上备份的照片,需要立即在samba、nfs、nextcloud等地方(nas系统)能看到,这就需要它们之间能够共享同一个目录

所以最终我选择的方案是:宿主机挂载物理硬盘,然后通过9pfs/virtiofs等文件系统分享给虚拟系统。 这两个文件系统都可以实现虚拟机内外共享目录,其原理是转发虚拟机内的文件系统操作到宿主机,不过不需要走网络请求,本地传输理论上会快一些。

不过pve自身并不支持这两个方案,所以需要在conf文件中手动修改qemu的命令行参数,会有一点麻烦。

这两个使用和配置起来有些差异,最终我选择了virtiofs。

9pfs(弃用)

之前看韦易笑大佬的文章配置的,所以一开始我使用的9pfs,但是用过一段时间之后,发现有一些比较影响使用的问题:

  1. 性能问题

经过一段时间的使用,发现通过9pfs挂载的目录性能很成问题,7zip解压、rsync同步等都很慢,就连nextcloud的速度也会变得很慢,可能是随机读写慢?这点有点无法接受。

  1. 不支持nfs

之前预计是在虚拟机的nas系统中启用nfs和samba,但是发现9pfs不支持nfs。

  1. sqlite无法读取,影响jellyfin的运行,原因是在默认的参数下,mmap无法在9pfs中调用:

通过给挂载参数中添加cache=mmap解决。

virtio-fs(选这个)

virtiofs配置稍微麻烦一点,但性能要比9pfs好很多(同样一个zip文件,肉眼可见大概比9pfs快2-3倍左右),同时限制也更少(直接支持nfs、mmap)。

virtiofs并不是qemu内置支持的,而是需要运行一个后台进程virtiofsd。这个后台进程需要跟着虚拟机启动时开启,虚拟机关闭时关闭,所以可以通过pve的hookscript来实现:

  1. /var/lib/vz/snippets目录下新建文件nas-hookscript.sh,添加以下内容:
#!/bin/sh

vmid=$1
phase=$2

case $phase in
    pre-start)
      echo pre-start vmid=$vmid phase=$phase
      systemd-run -G --unit=virtiofsd /usr/libexec/virtiofsd --shared-dir /mnt --socket-path /run/virtiofs-sea.sock --announce-submounts --inode-file-handles=mandatory --syslog
      ;;
esac

其中,—shared-dir参数需要替换成需要的目录。

然后通过qm set 102 --hookscript local:snippets/nas-hookscript.sh命令,将该脚本设置为虚拟机的hookscript,这里的102是虚拟机id。

这里使用systemd-run是因为hookscript需要立即退出,所以里面运行的daemon进程必须得是double-fork之后的,不能阻塞hookscript,而systemd-run默认会让进程以服务的形式运行,同时也支持将日志重定向到journald(这里通过—unit=virtiofsd选项将这条命令作为一个具名的临时服务运行了)。

virtiofsd命令会等待第一个客户端连接(也就是虚拟机启动后),第一个客户端断开连接后virtiofsd会结束进程。

  1. 打开pve的conf文件,目录一般在/etc/pve/qemu-server中,文件名是$虚拟机ID.conf,在里面添加如下一行:
args: -chardev socket,id=char0,path=/run/virtiofs-sea.sock -device vhost-user-fs-pci,queue-size=1024,chardev=char0,tag=sea -object memory-backend-file,id=mem,size=6G,mem-path=/dev/shm,share=on -numa node,memdev=mem -m 6G

其中,两个6G按照需要设置,它们的大小其实就是你要给虚拟机的内存大小(-m会覆盖pve的设置)。

/run/virtiofs-sea.sock是上面virtiofs监听的sock文件。

还有一点是这里要用/dev/shm做共享内存,/dev/shm默认是物理内存的一半,可能会不够用,可以在fstab中添加一行tmpfs /dev/shm tmpfs defaults,size=8G 0 0适当增加shm的大小。

至于为什么要配置这一大堆内存相关的东西(numa、shm之类的)我目前不太清楚,官方文档暂时是这样推荐的,后面在慢慢了解吧。

  1. 添加完之后重启虚拟机即可。不出意外的话,通过systemctl status virtiofsd可以看到daemon正常启动了,日志里面也会有Client connected, servicing requests的提示。这时候就可以进入虚拟机系统挂载文件系统了,在fstab中添加:
sea /mnt virtiofs rw 0 0 # 这里的sea是在qemu命令中设置的tag名

执行mount -a进行挂载即可。

这里要注意tag不能是mnt,会导致各种报错,不确定什么原因……

OpenMediaVault

OpenMediaVault是一个开源的nas系统,同样是基于debian。使用一段时间下来发现它提供的主要功能就是一个图形化的web界面,在里面可以快速配置邮箱、定时任务、nfs、samba等服务,也能查看监控等,更多其余的服务还是得自己配置docker。

(下面会把OpenMediaVault简写为omv。)

问题

这里记录一下使用时遇到的几个问题。

  1. /dev/dri权限问题

omv中默认的dri目录内的权限是660,非root非render组的用户无法访问,但是由于我的很多服务都没有直接使用root账户跑,在docker里面也不方便加组,所以就需要改下udev的规则,将/dev/dri/的权限改为666。

修改文件/lib/udev/rules.d/50-udev-default.rules,在其中找renderD*字样,将那一行的MODE改为"0666",然后重启即可:

  1. (使用9pfs时遇到,virtiofs不会遇到)omv里面的qbittorrent/nextcloud等服务提示打开文件过多,一开始以为是omv里面的ulimit设置的太小,只有1024,但是后面排查下来,发现只有通过9pfs挂载的目录会出现这个问题,所以去查了pve的ulimit,也只有1024,同时查看当前虚拟机的kvm进程的打开文件数量,刚好到了1024,所以实际的原因是9pfs要使用的文件描述符不够了。

需要在pve宿主机上面增加ulimit数量:

  1. 打开/etc/security/limits.conf,加两行内容:
```text
root - nofile 128000
* - nofile 128000
```
  1. 打开/etc/systemd/system.conf,添加一行内容:
```纯文本
DefaultLimitNOFILE=128000:524288
```

然后重启pve即可。

软件

接下来简单介绍下系统中部署的软件与服务。

关于数据存放

目前大部分服务都是在docker中部署的,docker容器需要保存的数据一般不会太大,目前跑的8个容器,需要保存和备份的数据总共也就4G,其中大头是nextcloud和jellyfin的数据库,所以将这些数据都放到固态上,不会有多少存储压力,还能显著提高这些服务的访问速度。

至于媒体文件、网盘内容文件等,则放到机械盘上面,速度通常不会影响太多。

nginx和域名

  • 跑在docker中,代理omv的主页。 omv默认有自己的nginx,不过是在omv宿主机上,不方便管理,因此我把omv的nginx监听到了其他端口上,然后单独跑了个nginx容器,用容器里面的nginx做主服务器,nextcloud、jellyfin以及omv等服务跑在这个服务后面。

certbot/ddns

  • 自己用python造的轮子,支持阿里的dns服务,定时检查来实现ddns。
  • 证书更新则使用certbot-cli的hook完成dns-challenge,运行一遍之后,certbot可以自己记下命令,特别方便。
root@nas ~/d/ddns cat certbot-cert.sh | tail -n 10
sudo certbot -d $DOMAIN --manual \
    --preferred-challenges dns \
    --renew-by-default \
    certonly \
    --manual-auth-hook "$PWD/certbot-hook.py -c $ALI_CONFIG" \
    --manual-cleanup-hook "$PWD/certbot-hook.py -c $ALI_CONFIG" \
    --cert-name $DOMAIN \
    --non-interactive \
    --agree-tos --email xxx

root@nas ~/d/ddns cat certbot-hook.py | tail -n 15 | head -n 11
    init_ali_client(config['access_key'], config['access_key_secret'])

    is_cleanup = os.environ.get('CERTBOT_AUTH_OUTPUT', None) is not None
    domain = '_acme-challenge.' + os.environ['CERTBOT_DOMAIN']
    value = os.environ['CERTBOT_VALIDATION']
    if not is_cleanup:
        update_dns_record(domain, value, allow_add=True, type_='TXT')
        print(f'updated record for domain {domain}')
    else:
        delete_dns_record(domain=domain, type_='TXT')
        print(f'deleted record for domain {domain}')

PT服务:qbittorrent

媒体库:Jellyfin Plex

文件管理与共享:samba + nfs + nextcloud

文件同步

  • Syncthing

Email

  • 阿里云邮箱+自定义域名

监控:graphite采集数据,grafana展示数据

  • 部署到另外一台虚拟机上,避免nas系统挂掉的问题

备份

定时备份

  • rsync备份命令:~~~~rsync -r --links /root/docker /mnt/sea/docker-backup/ --exclude 'docker/qbittorrent/data/config/qBittorrent/ipc-socket'~~~~,将命令添加到crontab中即可。备份所需要的时间比较长,通常得四五分钟的样子。备份期间,CPU占用大概在10%-20%,主要应该是rsync在校验数据。考虑到目前设置的每周执行一次,这个速度与CPU使用率还可以接受。
  • zip备份命令:zip -ryq /mnt/sea/docker-backup.zip /root/docker/ -x 'docker/qbittorrent/data/config/qBittorrent/ipc-socket'。切换到zip的原因是方便上传到对象存储、网盘等地方,同时也能压缩一次,减小一些体积。而且使用时发现zip的-f选项增量更新时,速度要比rsync快不少,大概30s左右就可以结束运行,不过运行时的CPU使用率就比较高,大概在50%左右。理论上,如果使用-0选项,也就是不压缩的话,速度应该可以更快一些。

需要备份的数据

  • docker数据

网络

  • openwrt