这是什么
这是我在 Homelab 里对两次真实故障的完整复盘归档:2026-10-16 的整机卡死,和 2026-10-17 的服务器卡顿。两次事故都发生在那台跑着一堆服务的 .164 小主机上。它用 Celeron J1900,只有 3.8GB 内存,同时承载反向代理、全屋 DNS、相册、内网穿透、memos、filebrowser 等服务。
为什么写
故障发生时,用户只觉得网站打不开。真正的原因既不是断网,也不是硬盘坏,而是内存被没有上限的容器占满,整机在机械盘 swap 抖动。我想把「症状 → 取证 → 根因 → 修复 → 预防」这条链完整保留下来:load 79、iowait 90%、vmstat b=73、sda 90% busy 却只有 5.42MB/s,这些数字比结论更有说服力。
做了什么
给 PhotoPrism 加了内存、CPU、IO 三重限制和看门狗;给量化回测容器加了自动补内存上限的规则;把 swap 改成 zram → SSD → NAS 机械盘的分级;还把 AdGuard 健康检查改成系统卡顿时不再盲目重启容器。
现状
两次事故均已处置并验证,项目标记为已归档。唯一没做的根治方案是加一根 4GB DDR3L 内存条,主板还空着一个槽。
功能
- 完整记录两次事故的证据链:load、iowait、swap、阻塞进程数逐项留档
- PhotoPrism 容器限内存 1200m / 1.5 核 / IO 64MB/s,超限只杀自己不拖整机
- photoprism-guard 看门狗每 20 秒巡检,整机濒死时自动停相册保网站
- sim-memcap 给没有内存上限的回测容器自动补 memory limit
- swap 三级分层:zram > SSD > NAS 机械盘,换页不再抖动 HDD
- AdGuard 健康检查 v2:高负载时只记日志,连续两轮失败才重启
技术栈
使用 / 部署教程
复现这套防护的步骤
前提:Linux + Docker + cgroup v2(stat -fc %T /sys/fs/cgroup 返回 cgroup2fs)。
1. 先取证,别急着重启
卡顿时立刻采集现场,重启会毁掉证据:
uptime; vmstat 1 5; free -m; cat /proc/pressure/io
iostat -x 1 3
docker stats --no-stream --format '{{.Name}}\t{{.MemUsage}}'
重点看三样:load 与 iowait 是否高、vmstat 的 b 列(不可中断 IO 进程数)、以及磁盘 busy 高但吞吐极低——那是 swap 抖动的典型特征。
2. 给占用大的容器加硬限制
编辑它的 compose.yaml(先备份原文件):
services:
photoprism:
mem_limit: 1200m
memswap_limit: 1600m
cpus: 1.5
cpu_shares: 128
pids_limit: 512
logging:
driver: json-file
options: { max-size: 10m, max-file: "3" }
docker compose up -d <服务> 重建,只影响它自己。
3. 用 cgroup v2 限磁盘 IO
echo '8:0 rbps=67108864 wbps=33554432 riops=80 wiops=60' > /sys/fs/cgroup/system.slice/docker-<ID>.scope/io.max
echo 'default 20' > /sys/fs/cgroup/system.slice/docker-<ID>.scope/io.weight
echo 1000M > /sys/fs/cgroup/system.slice/docker-<ID>.scope/memory.high
容器重建后 cgroup 会重置,所以要写个脚本配合 systemd timer 每 20 秒重新施加一次。
4. 加看门狗
同一个 timer 里判断:/proc/pressure/io 的 full avg60 超过 50% 且 1 分钟负载超过 25,连续两次就 docker stop 掉那个最吃资源的容器,状态写进 /run/ 下的文件。日志用 journalctl -t <tag> 查。
5. swap 分级,别落在机械盘上
fallocate -l 4G /swapfile && chmod 600 /swapfile
mkswap /swapfile && swapon -p 50 /swapfile
echo '/swapfile none swap pri=50 0 0' >> /etc/fstab
systemctl daemon-reload
优先级要保证 zram 最高、SSD 居中、机械盘只做兜底。
6. 抬升关键容器的 CPU 权重
docker update --cpu-shares 2048 npm adguardhome natfrp-service
规则保存在容器配置里,重启仍然生效。
7. 验证
确认 docker inspect 里的限制值、io.max 是否写入、容器内写文件速度是否真的被压下来,最后逐个 curl 关键服务确认返回码正常。
每次改配置都留一份带日期后缀的备份,回滚时直接覆盖回去再重建即可。
注意
- 所有配置改动都留了 .bak 备份,回滚前先核对文件名和日期后缀
- 看门狗会主动停掉相册容器,这是“宁可相册暂时不可用,也不让整机卡死”的取舍,阈值可按自己的服务组合调整
- 内存上限只是缓解,这台机器只有 3.8GB 内存却跑着一堆服务,根治方案还是加内存条