我们需要一台大容量的镜像与冷数据存储,给内网机器放系统镜像、备份和一些不常改动的大文件。预算有限,买不起商业存储,手里有的是一台塞满盘的服务器:36 块 16T SATA、两块 NVMe、两个 25G 带 RDMA 的网口。于是我们决定自己拿 FreeBSD + ZFS + Samba 组一套,把钱花在盘上,而不是机头上。
这篇文章记录我们当时的选型、踩过的点和最终落地的配置。核心判断很简单:对这种容量优先、以顺序大文件为主的场景,ZFS 的 raidz2 加上一块 SLOG、一块 L2ARC,性价比高得离谱,而且数据完整性有保障。
为什么是 FreeBSD + ZFS
ZFS 最早由 Matthew Ahrens 和 Jeff Bonwick 在 2001 年于 Sun 开发,2008 年移植到 FreeBSD。它和 Linux 内核因为 CDDL 与 GPL 许可证不兼容一直是“分家”状态,所以在 FreeBSD 上用 ZFS 是一等公民,省去了 Linux 上 DKMS 那套折腾。甲骨文收购 Sun 后 OpenSolaris 闭源,大批核心开发者出走,于 2013 年成立了 OpenZFS,现在 FreeBSD 用的就是这一支。
对我们这台存储真正重要的是 ZFS 的几个特性:存储池把所有盘抽象成一个池、写时拷贝保证掉电不会写坏已有数据、端到端校验和能发现并自动修复静默损坏、RAID-Z 提供软件级冗余、再加上透明压缩。这些加起来意味着:我们不需要硬件 RAID 卡,盘坏了换一块就能重建,而且每一个字节读出来都被校验过。
从 Linux 过来的工程师,日常命令大致这样对应:
| Linux (Red Hat/Debian) | FreeBSD | 用途 |
|---|---|---|
yum install / apt-get install | pkg install | 从远程仓库安装软件包 |
rpm -ivh / dpkg -i | pkg add | 安装本地包 |
rpm -qa / dpkg -l | pkg info | 列出已安装软件包 |
lspci | pciconf | 列出 PCI 设备 |
lsmod | kldstat | 列出已载入的内核模块 |
modprobe | kldload / kldunload | 载入/卸载内核模块 |
strace | truss | 跟踪系统调用 |
ip / ifconfig | ifconfig | 配置网络接口 |
iptables | ipfw | 防火墙 |
硬件布局
这台机器的配置:
网络: 25G * 2 with rdma
磁盘:
36 * 16T sata # 数据盘
1 * 1T nvme # zfs log(SLOG,写缓存),其实应该上 2 块做镜像
1 * 3.8T nvme # zfs cache(L2ARC,读缓存)
这里有两个要说清楚的取舍。
SLOG(1T NVMe)不是通常理解的“写缓存”。它只承接同步写(ZIL),把需要落盘确认的同步请求先写到低延迟的 NVMe 上,立刻返回,再在后台批量刷到 raidz2。对 Samba 这种会发同步写的场景能明显降低延迟。代价是:SLOG 一旦在掉电瞬间损坏,有可能丢掉最后几秒还没落盘的同步写。所以严格来说它应该做成两块 NVMe 的镜像——我们当时只有一块,这是明知的妥协,写进文档提醒自己后面补上。
L2ARC(3.8T NVMe)是二级读缓存,兜住内存 ARC 装不下的热数据。它只对读有帮助,而且会占用一部分内存来维护索引,所以不是越大越好。对我们这种镜像反复被拉取的读多写少场景,它是划算的。
组 zpool
动手前先看盘:
geom disk list
然后把 36 块盘组成一个 raidz2:
zpool create datasets raidz2 da0 da1 da2 da3 da4 da5 da6 da7 da8 da9 da10 da11 da12 da13 da14 da15 da16 da17 da18 da19 da20 da21 da22 da23 da24 da25 da26 da27 da28 da29 da30 da31 da32 da33 da34 da35
为什么是 raidz2 而不是 raidz1?raidz2 相当于 RAID6 的冗余级别,允许同时坏两块盘。36 块盘的池里,重建一块 16T 盘要跑很久,而重建期间整个池的盘都在高负载读取,这正是第二块盘最容易跟着挂掉的时候。raidz1 在这个规模下几乎等于在赌命。raidz2 用两块盘的容量换来重建窗口内的安全,在可靠性、性能、容量之间取得了我们能接受的平衡。
一个诚实的提醒:把 36 块盘放进单个 vdev 不是教科书推荐做法。vdev 越宽,重建越慢、随机 IOPS 越差。更稳妥的做法是拆成几个较窄的 raidz2 vdev 条带起来。我们这台是容量优先、以顺序大文件为主,才选了单 vdev 把容量吃满——如果你的负载是随机小文件,别照抄。
加 SLOG(写日志):
zpool add datasets log nvd0
如果有两块 NVMe,SLOG 应该做镜像(强烈建议这么做):
zpool add datasets log mirror xxx xxx
加 L2ARC(读缓存):
zpool add datasets cache nvd1
组完看一眼状态:
zpool list
NAME SIZE ALLOC FREE CKPOINT EXPANDSZ FRAG CAP DEDUP HEALTH ALTROOT
datasets 524T 95.6T 428T - - 0% 18% 1.00x ONLINE -
zpool status datasets
pool: datasets
state: ONLINE
scan: none requested
config:
NAME STATE READ WRITE CKSUM
datasets ONLINE 0 0 0
raidz2-0 ONLINE 0 0 0
da0 ONLINE 0 0 0
da1 ONLINE 0 0 0
da2 ONLINE 0 0 0
da3 ONLINE 0 0 0
da4 ONLINE 0 0 0
da5 ONLINE 0 0 0
da6 ONLINE 0 0 0
da7 ONLINE 0 0 0
da8 ONLINE 0 0 0
da9 ONLINE 0 0 0
da10 ONLINE 0 0 0
da11 ONLINE 0 0 0
da12 ONLINE 0 0 0
da13 ONLINE 0 0 0
da14 ONLINE 0 0 0
da15 ONLINE 0 0 0
da16 ONLINE 0 0 0
da17 ONLINE 0 0 0
da18 ONLINE 0 0 0
da19 ONLINE 0 0 0
da20 ONLINE 0 0 0
da21 ONLINE 0 0 0
da22 ONLINE 0 0 0
da23 ONLINE 0 0 0
da24 ONLINE 0 0 0
da25 ONLINE 0 0 0
da26 ONLINE 0 0 0
da27 ONLINE 0 0 0
da28 ONLINE 0 0 0
da29 ONLINE 0 0 0
da30 ONLINE 0 0 0
da31 ONLINE 0 0 0
da32 ONLINE 0 0 0
da33 ONLINE 0 0 0
da34 ONLINE 0 0 0
da35 ONLINE 0 0 0
logs
nvd0 ONLINE 0 0 0
cache
nvd1 ONLINE 0 0 0
errors: No known data errors
524T 裸容量,去掉 raidz2 的两块校验盘和 ZFS 自己的开销,实际可用约 451T。
挂载
zfs set mountpoint=/datasets datasets
zfs mount datasets
确认挂上了:
df -h
Filesystem Size Used Avail Capacity Mounted on
/dev/ada0p2 429G 11G 384G 3% /
devfs 1.0K 1.0K 0B 100% /dev
datasets 451T 9.5G 451T 0% /datasets
装 Samba 对外提供服务
内网大量是 Windows 机器,所以走 SMB 最省事:
pkg install samba413-4.13.17_1
开机自启,两种写法任选其一(sysrc 本质上就是帮你改 /etc/rc.conf):
sysrc samba_server_enable=YES
# 等价于在 /etc/rc.conf 里加一行:
# samba_server_enable="YES"
然后是 smb.conf。几个值是有意调过的,不是默认:
[global]
workgroup = SAMBA
security = user
server string = ZFS SAMBA Version %v
passdb backend = tdbsam
hosts allow = 127. 10.20.193. 192.168.23. 192.168.1. 10.20.191. 10.88.88.
load printers = no
use sendfile = yes
socket options = TCP_NODELAY SO_RCVBUF=16384 SO_SNDBUF=16384
write raw = yes
read raw = yes
max xmit = 65535
unix charset = UTF-8
dos charset = GB2312
server multi channel support = yes
max protocol = SMB3
[zfs]
comment = ZFS SAMBA Version %v Image Storage
path = /datasets/
browseable = yes
writable = yes
public = no
guest ok = no
create mask = 0755
directory mode = 0755
security = share
几个关键项的理由:use sendfile = yes 让内核直接把文件从 ZFS 送到 socket,省去用户态来回拷贝,对这种大文件传输很有用;server multi channel support = yes 配合 SMB3 和两个 25G 口,能让单个客户端同时用上多条链路;hosts allow 把访问限制在我们自己的几个内网段里,这是第一道访问控制;dos charset = GB2312 是为了兼容老 Windows 客户端的中文文件名。
最后 smbpasswd -a 建用户,重启 Samba 服务,客户端就能挂载了。
之后我们做了什么
这套跑起来之后,有几件事是我们按计划补上或持续在做的:给 SLOG 补第二块 NVMe 做镜像,消掉那个已知的单点;配置定期 zpool scrub,让 ZFS 主动巡检并修复静默损坏,而不是等读到坏数据才发现;监控 L2ARC 命中率,确认那块 3.8T 读缓存确实在干活。如果你也在用一堆便宜盘攒存储,这套组合值得抄——但 SLOG 一定要做镜像,别留着那个坑。
