这是什么
PhotoPrism 是我家里的照片管理服务,跑在 .164(Celeron J1900 / 3.8G 内存 / 1.5G zram)上,负责照片索引、浏览和视频转码,通过 photo.xiejun-server.top 访问。相册本身没问题,问题在于它身边还挤着 nginx-proxy-manager(80/443,所有网站的入口)、AdGuard(全屋 DNS)、natfrp、memos、filebrowser、reader——一台小机器承担了太多职责。
为什么要做限制
10-16 和 10-17 两次故障指向同一个模式:跑东西的容器没有内存上限。10-17 那次,PhotoPrism 峰值 2.2GB(RSS 1.64G + 换出 1.74G),叠加每日 SQLite 备份和两个 libx264 转码,把机器推进了机械盘 swap 抖动——load 峰值 79.15、iowait 84%–90%、73 个进程阻塞在不可中断 IO,最后 AdGuard 健康检查连续超时被重启,全屋 DNS 断了 2–3 分钟。体感是「服务器卡了、网站打不开」,实际是本机 IO 饱和。
现在怎么跑
我给 PhotoPrism 加了三层限制:容器硬限制(1200m 内存 / 1600m memswap / 1.5 核 / 512 pids),cgroup v2 精细限制(io.max 读 64MB/s、写 32MB/s,memory.high 1000M),以及一个 20 秒跑一次的看门狗——整机濒死时自动 docker stop photoprism,宁可相册暂时不可用,也不让反向代理和网站陪葬。顺手把换页从机械盘挪到 SSD,关键容器 CPU 权重提到 8 倍。当前状态:运行中,常驻内存约 171MB,PSI io full avg60 约 2.5%,限速实测写 24MB/s。
功能
- 容器硬限制:1200m 内存、1600m memswap、1.5 核、512 pids,超限只杀自己
- cgroup v2 精细限制:机械盘读写限 64/32MB/s、IOPS 80/60,memory.high 1000M 提前回收
- 整机濒死自动保护:PSI io full avg60 > 50% 且负载 > 25 连续两次,自动停掉相册服务
- 换页分级:zram(100) > SSD swapfile(50) > NAS 4G(10),不再打满机械盘
- 关键容器 cpu_shares=2048、相册 128,CPU 争抢时关键服务约 8 倍优先
- 转码降规格:FFMPEG_SIZE 1280、BITRATE 20Mbit/s,压低 CPU 与内存峰值
技术栈
使用 / 部署教程
1. 写容器硬限制
编辑 /opt/photoprism/compose.yaml(先 cp compose.yaml compose.yaml.bak 备份):
services:
photoprism:
mem_limit: 1200m
memswap_limit: 1600m # 最多 400M 换页
cpus: 1.5
cpu_shares: 128 # 关键容器设 2048
pids_limit: 512
environment:
PHOTOPRISM_FFMPEG_SIZE: "1280"
PHOTOPRISM_FFMPEG_BITRATE: "20"
logging:
driver: json-file
options: { max-size: "10m", max-file: "3" }
docker compose up -d photoprism 重建,只重启它自己,反向代理和网站无感。
2. 用 cgroup v2 钉 IO 和内存软限
先确认块设备号:lsblk -o NAME,MAJ:MIN(资料里机械盘是 8:0)。写一个 photoprism-guard.sh,由 photoprism-guard.timer 每 20 秒重新施加一次(容器重建后也能自动补上):
echo "8:0 rbps=67108864 wbps=33554432 riops=80 wiops=60" > /sys/fs/cgroup/.../io.max
echo "default 20" > /sys/fs/cgroup/.../io.weight # 默认 100
echo 1000M > /sys/fs/cgroup/.../memory.high # 硬限 1200M 兜底
3. 加整机濒死看门狗
同一个 timer 里读 /proc/pressure/io 的 full avg60,大于 50% 且 1 分钟负载大于 25,连续两次(约 40 秒)就 docker stop photoprism。计数写 /run/photoprism-guard.bad,触发时间写 /run/photoprism-guard.stopped,日志用 journalctl -t photoprism-guard 看。恢复手动 docker start photoprism。
4. 让换页优先走 SSD
在 SSD 上建 4G swapfile,/etc/fstab 加一行(先备份 fstab),写成三级:zram(pri=100) > SSD(pri=50) > NAS 4G(pri=10)。systemctl daemon-reload 生效。
5. 提升关键容器优先级
docker update --cpu-shares 2048 npm adguardhome natfrp-service home memos reader filebrowser
规则存在容器配置里,重启后保持。
6. 验证
docker inspect photoprism看 Memory / NanoCpus / CpuShares / PidsLimitcat io.max、cat memory.high确认 cgroup 生效- 容器内写 128MB 测速,应明显低于机械盘原始写速
- 逐个 curl 各服务端口,确认全绿;再看
load、PSI、可用内存
7. 回滚
cp compose.yaml.bak-严格限制-20261017-2143 compose.yaml && docker compose up -d photoprism;看门狗用 systemctl disable --now photoprism-guard.timer(脚本保留但不生效)。
注意
- 内存上限 1200m 意味着大相册批量索引或高码率转码时容器可能被 OOM 重启,属预期行为——保整机优先。
- 看门狗触发后会真的把相册停掉,需要手动
docker start photoprism恢复,建议配告警而不是靠体感发现。 - 所有改动都留了
.bak备份(compose.yaml、fstab、健康检查脚本),改之前先备份,出问题按回滚步骤还原。