TrueNAS 维护实录:内存、自建服务和升级

最近在资料服务器(NAS)上开始了新一轮研究过程:部署更多内存、自建监测栈和升级系统等;现在尘埃落定,过程于此小记。

阅前注意:TrueNAS 系列文章涉及可能影响生产数据的危险操作,仅供测试参考,切勿生搬硬套。

一、内存升级

这几个月下来,我发现 TrueNAS Scale 简直是个大胃王:它会将一半内存划入 ZFS 动态缓存(ARC),并跟其余服务共享剩余另一半,而且很早(空闲内存剩余 10% 时)便起用硬盘缓冲区(Swap)。在现有内存仅为 8 GB(可用 7.6 GB)的情况下,可供给服务使用的约 3 GB 内存很快吃干抹净,Swap 占用多达 1.6 GB——已经严重溢出了。基于这点,我开始考虑扩充内存容量。

8GB 内存有些力不从心

淘来了两根 Crucial 品牌内存,作价 140 元。观察发现,尽管贴纸所示序列号为连号,但芯片特征截然不同(一边有二维码丝印,另一边则无),怀疑有冒牌贴标可能。不过,管用便可,谁管它哪儿拾掇来的。

新购内存条

更换内存模块后,既有系统和 12 个 Docker 容器可以完全承载,空闲内存约有 2 GB。不过在后半夜的一次全量复制过程中,这点空闲内存很快被耗尽,Swap 被再度起用。一不做二不休,刚卸下的内存又装了上去——现在有 24 GB 超大内存容量了。

二、NVMe 固态盘的散热改进

如上图所示,在未加强散热的情况下,三根 Optane 固态盘分别升温至 48、53 和 56 度。 上篇可见其安装位置,其中系统盘 nvme0n1 位于进风口附近;缓存盘 nvme1n1 nvme2n1 位于电源附近,此处空气不通(向后无开口),且持续被电源加热。有传闻称 Optane 报废多由主控过热导致,故考虑加强其散热。

准备 1mm、2mm 导热垫,和 20 x 70 mm 散热片若干。主控芯片和存储芯片之间存在约 0.5 mm 高差,故无法以 1mm 垫片完全覆盖;先以 2mm 垫片覆盖主控和电源模块,以 1 mm 垫片覆盖存储芯片,再以散热片按压较厚垫片,垫片充分压缩后,即可接触到存储芯片处的导热垫。贴纸旁有若干电容等小器件暴露,注意绝缘。

散热片敷设

装回去使用,结果是基本无效——气流过小,无法形成有效对流,依然被电源炙烤着。可能要考虑增强通风,例如在背板处安装涡轮扇一只;或者挪个位置安装。

三、Docker 备份和系统升级

这样升级散热后再开机,坏了,TrueNAS 自己的监测系统报销了……似乎是几天前我不慎在系统层面卸载了 Netdata 的缘故。这下不升级也得升级了。
4 月 22 日,TrueNAS Scale 24.04 版本正式登场,带来全新 Linux 6.6 内核和若干改进,包括不间断电源(UPS)状态监测、SMB/NFS 性能优化和连接状态监测、ARC 分配机制优化、沙盒等等。我本计划等到更完善的 24.04.1 版本再升级,但该版截至撰稿时尚无时间表,作罢。

升级前,依网上教程关闭 Docker 服务,备份目录 /var/lib/docker。退出终端,于 Web 管理界面执行升级。

升级后出现了以下问题:

  1. 原 23.10.1 版实例无法在 Web 界面中完全删除。
    方案: 通过命令行强制销毁该实例(或者说数据集),即 zfs destroy boot-pool/ROOT/23.10.1
  2. 自新版本起系统目录 /opt/usr 被设定为只读,且试图执行 apt 时会提示“包管理器已被禁用”字样,拒绝执行。
    方案: 据 TrueNAS 开发者透露apt 可经由一份内置 Python 脚本来激活,其位于 /usr/local/libexec/disable-rootfs-protection。该脚本是命令 install-dev-tools 的一部分,使用此命令进入开发者模式时,会安装若干用于系统本身开发的软件包,并通过该脚本解除系统目录的只读状态、开放 apt 权限;但我们一般无需此类软件包用以开发,故可单独运行该脚本。

注意

启用开发者模式不受 iXSystems 官方支持,一切后果自负!

  1. 如上篇所述,任何经命令行自行搭建的服务(包括 Docker)不会被新版系统继承。
    方案: 老老实实重新部署 Docker 和诸容器罢……我这系统注定一用至少用一年。能带 k8s 玩的话大约不会这么麻烦;也应当考虑编写 Docker Compose 配置文件以便日后部署。

部署 Docker 时,我顺便设置 Docker 主目录到缓存池,即原先备份 /var/lib/docker 的地方,这样 Docker 存储和系统存储隔离,不必再跟系统争空间了,也不会受系统更新牵连。配置文件 /etc/docker/daemon.json

1
2
3
4
5
6
{
"data-root": "/mnt/Cache/docker-bin",
"registry-mirrors": [
"https://mirror.ccs.tencentyun.com"
]
}

四、监测栈的自建

TrueNAS Scale 集成了监测系统 Netdata。该系统尽管可以满足基本的监测需求,但有许多不尽人意之处:例如观测范围仅有一小时、24 小时和七天等选项;观测范围仅对单个图表适用;诸硬盘指标完全分散,难以观察对比;控制台资源占用高;等等。那么我设想,像在群晖系统下一样引入一套自主的监测栈,是否会有更舒适的体验?

4.1 前期设想

先敲定监测栈方案。已知 TrueNAS Scale 可引出 Graphite 报告流;系统升级后会切换到新的 Linux 底层实例,以命令行部署的一切服务不会被新实例继承(见上篇),故裸机服务方式不适用;由于网络限制(我不考虑引入特殊上网手段),Kubernetes 应用获取有难度;考虑基于 Docker 建立服务,以便日后迁移。选项有:

  • 独立运作的 Netdata 容器
  • Telegraf + InfluxDB + Grafana——曾于群晖系统运用的方案
  • Graphite Exporter + Prometheus + Grafana

4.2 实践

Netdata 方案浅尝辄止:刚翻看图表,处理器占用随即上升至 10% 以上,远超背景占用率 5%。这是不可接受的:监测栈本身的资源用量(footprint)应尽量小,不得影响到其余业务的正常运行。这部服务器算力一般,经不起这般折腾。
在方案三中,我设想 Exporter 负责和宿主系统对接,将 Graphite 数据流转译为 Prometheus 可以接受的形式,后经 Grafana 展示。我为此断断续续探究许久,开始便被宿主目录访问权限拦住;群晖系统里远没有这么麻烦,管理员账户自动拥有一切目录的读写权限,以其身份访问宿主目录从来不成问题。然后转念一想,Exporter 可不是 Importer,它干不了 Importer 的活计……先扔一边罢。

Telegraf 在监测数据收集方面,有两条路线:

  1. 自行从系统诸元采集数据。 需将整个根目录暴露给 Telegraf 容器。不过,我怎么也采集不到所期望的数据:
    • 网友指引下,基本的处理器负载和温度、内存用量等数据可以采集到。
    • 试图采集硬盘/存储池相关数据时,由于 ZFS 文件系统的关系,诸数据集被视作相互独立的池,且获取根数据集的使用量时不会将子数据集一并计入,造成记录诸元非常稀疏。
    • 诸硬盘温度数据未能从被动途径取得。Telegraf 要求主动调用 smartctl nvme 等宿主命令,但此二者位于系统目录 /usr/sbin,不能以除 root 之外任何名义调用(TrueNAS 真是老严格了)。不可能给 root 权限的,这辈子都不可能。
    • 网络吞吐量未能从已获得数据中发现。
    • 当试图采集 ZFS 相关性能参数时,插件 inputs.zfs 不能适应含有空格的存储池名称(“Main Storage”和“Hot Backup”),从而无法采集。为此我在缺乏必要备份的情况下,变更存储池名称:

危险操作,请勿模仿

  1. 关闭和上述存储池有挂载关系的一切 Docker 容器服务;
  2. Web 界面中将上述两个存储池导出/卸载;
  3. 命令行中以 zpool import 'Hot Backup' Backup 的形式导入存储池,并进行更名;
  4. 命令行中再次导出/卸载存储池;
  5. Web 界面中再次导入存储池。
以原生接口采集数据
  1. 接收来自宿主的 Graphite 数据流。 这不仅免于暴露整个根目录,而且获得了尚且完整的记录,处理器、内存、硬盘吞吐、网络吞吐、ARC 命中率等皆有涉及;甚而在 Grafana 面板商城里,还有现成的面板可以解读存于 InfluxDB 的记录诸元。尽管该面板所展示的远远不够(例如诸存储池的使用率),硬盘 SMART 遥测数据几乎全部缺席(仅温度例外),但大约是相对省事的方案了。

4.3 成品

对上述面板稍加改造,便有:

监测栈完成后 Grafana 界面

改造细节:

  1. 原面板含有二级动态缓存(L2ARC)的展示,但本系统未部署任何 L2ARC,故显示为空。已移除。
  2. 拆分了内存和 Swap 使用曲线。
  3. 处理器使用率改以时间—曲线展示。
  4. 移除了开机时间(Uptime)背景的直线——在数据库指令中加入 last() 以限定最后一项记录。
  5. 移除了内存使用饼状图,加入 UPS 剩余电量指示。

未来考虑将面板汉化并上传到 Gitee。
更新:已上传,然后详细过程都记录在“什么值得买”社区——那里的读者似乎喜欢所谓“保姆式教程”,我便投其所好了。

五、撰稿为止启用的服务

近来,基于 Alpine Linux 封装 Docker 镜像的做法正蔚然成风;其最大优势在于成品体积,封装后的存储量相较传统途径减少了数十至百余 MB。这里有条件时也尽量引入 Alpine 封装版本,以节约存储空间。不过,其中有的反而精简过度(如 nextcloud:fpm-alpine 要求额外部署反向代理系统),遂略过。
一切镜像均取自 Docker Hub。

服务类别Docker 镜像镜像标签
Portainer容器管理portainer/portainer-cealpine🌟
Watchtower容器管理containrrr/watchtowerlatest
InfluxDB基础设施influxdbalpine🌟
MariaDB基础设施mariadblts
Alist网盘/基础设施xhofe/alistlatest
Tailscale内网穿透tailscale/tailscalelatest
Gitea开发/备份gitea/gitealatest
Nextcloud备份linuxserver/nextcloudlatest
Telegraf监测telegrafalpine🌟
Grafana监测grafana/grafana-osslatest
File Browser文件管理hurlenko/filebrowserlatest
FreshRSS订阅管理freshrss/freshrssalpine🌟
Qbittorrent下载superng6/qbittorrenteelatest
Jellyfin影音管理nyanmisaka/jellyfinlatest
新增:One API人工智能/基础设施justsong/one-apilatest
新增:ChatGPT Web人工智能chenzhaoyu94/chatgpt-weblatest
恢复:Plex影音管理linuxserver/plexlatest
恢复:Syncthing备份/文件同步syncthing/syncthinglatest

总结一下:本次改造和升级过程如同钢丝跳舞,非常惊心动魄;如同盲人摸象,相当消耗耐心;如同十面埋伏,处处意想不到。去年末,我曾在生产环境中调试 GitHub Actions 脚本,未事先做好功课的情况下,十几个 commit 均未修复问题,成了 commit 历史上的一道伤疤……凡事要做好,有备无患是前提。再次告诫:好孩子千万不要学😇


六、后记:人工智能伙伴的引入

本篇发表后不久,我开始考虑引进人工智能伙伴。作为阿斯伯格人士,我一向不愿有求于人,而人工智能多少能让我从对社交的恐惧中解脱出来,为我答疑解惑,似乎亦可打破传统搜索引擎结果鱼龙混杂、广告铺天盖地造成的信息获取障碍。

首先考虑采用何种方案:

  • 在自有硬件上搭载人工智能模型。 这里对 GPU 要求非常苛刻:VRAM 足够大(8 GB 以上),半高尺寸(服务器空间有限),无需外接电源(即功率 70W 以下;服务器供电能力有限),至少支持半精度浮点(F16)计算,有 INT8、INT4 支持更佳。GPU 选择非常有限:
    • Tesla P4。所有选项中最廉价者,但不支持半精度浮点,价格业已水涨船高。
    • Tesla T4NVIDIA A2。以上要求均可满足,但作为较新近的产品,加上绿厂金字招牌,价格自然不大好看。
    • Intel Arc Pro A50。满足以上要求,价格也低于绿厂,但依旧不好看。
    • 此外,多数模型支持通过处理器演算,但对处理器算力和指令集都有要求,例如需要 AVX2——这当然不是 Pentium 处理器所能给予的。
  • 租用服务器。 这也不甚划算,毕竟我缺乏训练模型之需,仅仅需要跟训练好的模型交流而已。
  • 接入云上模型 API 接口。 阿里云“灵积”服务提供了“通义千问”“ChatGLM”等诸多模型,开箱即用,部分模型限时免费,对其它模型也有免费体验的百万字口令(Token)额度。尽管自主性上不及自托管模型,但大约是现有条件下最优选择了。

然后,依“什么值得买”社区文章搭建服务:

  1. 阿里云上开通“灵积”服务,创建“通义千问”API 访问令牌;
  2. 取得 OpenAI ChatGPT 3.5 API 访问令牌(完全免费的,不用白不用);
  3. 于 MariaDB 实例准备好供 One API 使用的用户和数据库(这一步可以省略,我只是希望将访问令牌之类的设置永久化、安全保存而已);
  4. 启动 One API 容器,其应和 MariaDB 处于同一内网,令其调用 MariaDB;
  5. 于 One API 设置两个渠道(channel),类型分别为“OpenAI”和“通义千问”,模型分别为 gpt-3.5-turboqwen1.5-32b-chat,再填入相应的访问令牌;
  6. 服务器上启用 ChatGPT Web 容器作为客户端;手机上安装 BotGem 作为客户端;
  7. 在 One API 生成访问令牌,指定可用模型,随后分配给客户端。

这样,用于对接外部模型的令牌被封装并保护,对一般用户仅需提供面向内网的令牌,泄漏风险极低;“通义千问”API 被重新封装成 OpenAI API,其余原设计对接 ChatGPT 的客户端均可享用。不谙技术的家人经过指导亦可在家中接入,享受科技的乐趣。

One API 渠道设置
ChatGPT Web 体验“通义千问”

当然,这其中小插曲是少不了的。例如,我试验 yidadaa/chatgpt-next-web 镜像时就发现它根本没产生任何网络请求(One API 日志里无请求记录),总是返回“空响应”;再如,我试图通过阿里云的 OpenAI 兼容接口引进模型供客户端使用,结果这个兼容接口限制颇多,既不能调用“通义千问”以外的其它模型,也不支持通常被 ChatGPT 所用的 top_p presence_penalty 等参数(top_p 可能支持,但取值范围不及 ChatGPT),让客户端和我都手足无措。

不管怎样,我往初级运维的目标又近了一步。现在处理器远比 GPU 廉价,我大约会考虑更换处理器和大容量内存,来试验大模型的本机演算(“通义千问”说这样可行);但这部主机最多支持七代 Core,提升潜力有限,更换平台又会产生一大笔开销……当然这是后话了。

更新: 5 月 28 日起,“通义千问”70 亿参数以上模型转为收费。有必要密切关注阿里云相关公告和文档更新,并预留一定款项,以备不时之需。

七、后记二:新版系统的 ARC 分配机制

这是本文发表后第二次补充,必须就 TrueNAS 系统中 ARC 分配的方式作一次澄清。在早前版本,ARC 的确仅仅用去系统内存的一半;但到了 24.04 版本,ARC 被调整为尽可能利用未被系统服务所占据的一切闲置内存空间。尽管理论上 ARC 会为系统服务让步,但这不包括自行部署的 Docker 及诸容器。于是,纵有 24 GB 超大内存,ARC 也独吞超过 19 GB,留给 Docker 的只剩少许渣滓和 Swap……

考虑到 ARC 对单个用户和非并发连接的作用非常有限,我认为有必要控制 ARC 分配量的上限,例如仅分配 8 GB。首先尝试给内核注入参数:

  1. 命令行中进入 TrueNAS CLI 管理界面:sudo cli
  2. 添加内核参数:
    system advanced update kernel_extra_options="zfs_arc_max=8589934592"
    其中,数字 8589934592 = 8 ⨉ 230
  3. 重启服务器。

结果无效。重启后尝试于 Qbittorrent 添加下载任务,此后 ARC 逐渐上升,五小时后恢复至 11 GB,一天后升至 12 GB。那便采用旁的办法:

  1. 系统高级设置中,设置一个初始化后(PostInit)脚本:
    echo 8589934592 >> /sys/module/zfs/parameters/zfs_arc_max
  2. 重启服务器。

成功了。现在空余内存接近 2 GB,短期内无需担忧内存耗尽的问题了。——观察数日后发现,Swap 的占用量仍有缓慢上升之势;下面是至六月 5 日止近一周内内存使用情况,可见在 1 日凌晨,因触发数据清理任务,系统服务对内存的需求骤然增大,甚至一度影响到 ARC,此时 Swap 占有量从零开始上升;两日后又快速上升,至 5 日维持于 760 MB 上下。这究竟是我的配置问题还是 TrueNAS 本身的调度问题所致?兴许要到后续文章里解答了。

限制 ARC 后内存使用情况

9 月 23 日更新: 自 24.10.1 版本起,Swap 被自动关闭,免去了这项后顾之忧。