私有相册的可用性保障:一次 Immich 服务中断的分层排查与恢复
自建 Immich 已经稳定跑了几个月,直到某天晚上手机 App 突然提示「无法连接服务器」。这类故障最麻烦的地方在于:表面看是应用挂了,实际根因往往在几层之外。
本文记录完整的排查与恢复过程,重点不是某一条命令,而是分层的思路和恢复的顺序。
一、故障现象
三个现象同时出现:
- 手机端和网页端都连不上,请求直接超时
- 服务器上
curl接口无响应,端口没有监听 - 但数据库、缓存、机器学习容器看起来都还在运行
这说明问题不在「整个主机挂了」,而是某个环节断了。
二、分层排查
私有相册的依赖链通常是这样:
手机 App
↓ HTTP
应用容器(Immich Server)
↓ 读写
媒体存储(SMB/CIFS 挂载)
↓ 网络
存储服务器(NAS 虚拟机)
↓
物理磁盘
任何一层出问题,表现都是「应用连不上」。所以要从下往上验证,而不是从上往下猜。
第 1 层:应用容器
docker ps -a --format '{{.Names}}\t{{.Status}}'
结果很明确:immich_server 状态是 Exited (1),而数据库和缓存容器正常。
再看退出前的日志,出现两类错误:
EHOSTDOWN: host is down, open '/data/library/.../IMG_xxxx.jpg'
TypeError: Cannot read properties of undefined (reading 'handleMessage')
第一行是关键线索:应用在读媒体文件时,底层报「主机不可达」。这不是应用配置问题,是存储链路断了。第二行是应用在错误处理路径上又崩了一次,属于次要现象。
第 2 层:存储挂载
mount | grep cifs
ls /mnt/photos
结果:
//nas/photo on /mnt/photos type cifs (...)
ls: cannot access '/mnt/photos': Host is down
挂载条目还在,但访问报 Host is down。这是典型的 CIFS 悬挂挂载:连接断了,但挂载点没有被自动清理。
注意:mount 显示挂着,不代表它可用。判断存储是否真的可用,必须实际读一次。
第 3 层:存储服务器
ping -c 2 <nas-ip>
100% 丢包,ARP 表里的状态是 FAILED。说明 NAS 本身已经不在线。
到虚拟化平台查看:
qm list
qm status 101
结果:
status: io-error
找到根因了。 虚拟机因为磁盘 I/O 错误被 QEMU 自动暂停(-no-shutdown 模式下进程还在,但 CPU 停止执行)。虚拟机一停,SMB 服务就没了,CIFS 挂载随之失效,应用读文件全部失败然后崩溃。
第 4 层:物理磁盘
内核日志里能看到完整证据链:
usb 2-3.2: reset SuperSpeed USB device number 4 using xhci_hcd ← USB 链路反复重置
I/O error, dev sdb, sector 16785280 op 0x1:(WRITE) ← 写 I/O 错误
sd 2:0:0:0: [sdb] Attached SCSI disk ← 重新枚举
外接 USB 硬盘在几小时内重置了二十多次,最终出现 I/O 错误。
三、恢复顺序(关键)
知道原因后,恢复必须按依赖链从下往上,顺序错了会反复失败:
| 步骤 | 动作 | 为什么这个顺序 |
|---|---|---|
| 1 | 备份虚拟机的配置文件 | 改配置前留退路 |
| 2 | 停止故障虚拟机 | 解除 io-error 暂停态 |
| 3 | 移除故障磁盘的直通配置 | 让虚拟机不再依赖那块盘 |
| 4 | 启动虚拟机 | 此时只挂正常磁盘 |
| 5 | 验证 NAS 网络与共享 | ping + SMB 端口 + 实际读文件 |
| 6 | 验证挂载点可读写 | 读一个文件 + touch 测试写入 |
| 7 | 才启动应用容器 | 存储没就绪就启动,只会再崩一次 |
第 7 步特别重要:存储不可用时不要急着拉起应用。它会在启动阶段读不到文件,然后进入崩溃循环,日志被刷满,反而掩盖真实问题。
恢复后验证清单:
应用接口返回 pong
容器 healthy,重启次数为 0
资产总数与故障前一致
缩略图可访问(随机抽 3 张)
原图可下载(随机抽 3 张)
只看到「容器 running」不算恢复完成,要能真正取到数据。
四、四条加固措施
1. 画清楚依赖链,监控分层
原来的监控只检查「应用接口是否存活」。这次故障中,接口挂了只是表象,真正的信号在更下层。
改进后的巡检同时检查三层:
应用层:HTTP 接口返回 pong
容器层:四个容器 Up 且 healthy
主机层:根分区使用率 < 85%
异常时只输出问题项,正常时完全静默——监控的价值在于出事时能被看见,而不是每天报一次「一切正常」。
2. 给容器加资源上限和日志轮转
应用崩溃时日志会短时间暴涨。不加限制的话,容器日志能把宿主磁盘写满,把小故障放大成磁盘告警。
services:
immich-server:
mem_limit: 1100m
logging:
driver: json-file
options:
max-size: 10m
max-file: "3"
数据库容器不要设内存上限——它需要页缓存来维持性能,限制了反而拖慢查询。
3. 下调后台任务并发
媒体库挂的是网络存储,并发太高会让每个任务都在等 I/O:
缩略图生成:8 → 4
元数据提取:7 → 3
后台任务: 5 → 4
在 3 核 + 网络存储的环境下,这个配置比默认值稳得多。
4. 清理失效挂载,别让它看起来正常
CIFS 卸载后,挂载点的目录项会损坏,表现为:
drwxr-xr-x ... download
ls: cannot access '/mnt/download': No such file or directory
此时 umount 会报“not mounted”,因为路径本身解析不了。唯一的干净做法是重启引用该挂载点的容器,让挂载表重新建立。
五、小结
这次故障最值得记住的三点:
- 「应用连不上」不等于「应用有问题」。依存链越深,越要从底层往上验证。
mount显示挂着不代表可用,docker ps显示 running 不代表正常,判断依据必须是「能不能真的读到数据」。- 恢复顺序比恢复速度重要。存储没就绪就拉应用,只会把一次故障变成一次崩溃循环。
私有化部署的便利是自己掌控数据,代价是每一条依赖链都要自己负责。把依赖关系画出来、把监控建在正确的层级上,比事后救火有效得多。
- 点赞
- 收藏
- 关注作者
评论(0)