用 systemd 托管小服务:自动重启、限制内存和基础沙箱加固
在 2核2G 的云服务器上跑自己的小应用,一开始我是 nohup node app.js & 挂着,结果 SSH 一断、进程一崩就得手动拉起。后来全部改成 systemd 托管,顺手加了内存上限和一些沙箱选项。这篇记录我的 unit 文件写法和踩过的坑。
一、新建专用用户
不要用 root 跑应用。先建一个没有登录 shell 的用户:
sudo useradd --system --create-home --shell /usr/sbin/nologin appuser
sudo mkdir -p /opt/myapp && sudo chown -R appuser:appuser /opt/myapp
二、写 unit 文件
/etc/systemd/system/myapp.service:
[Unit]
Description=My App
After=network-online.target
Wants=network-online.target
[Service]
User=appuser
WorkingDirectory=/opt/myapp
ExecStart=/usr/bin/node /opt/myapp/app.js
Environment=NODE_ENV=production
Restart=on-failure
RestartSec=3
MemoryHigh=700M
MemoryMax=900M
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/opt/myapp/data
[Install]
WantedBy=multi-user.target
几个关键点:
- Restart=on-failure 只在异常退出时重启;如果用 always,你手动 stop 以外的正常退出也会被拉起,调试时容易困惑。
- MemoryHigh 是软限制,超过后内核会限速回收;MemoryMax 是硬限制,超过会被 OOM 杀掉。2G 机器上给应用设上限,可以避免它把 sshd 之类的挤死。
- ProtectSystem=strict 会把整个文件系统变成只读,只有 ReadWritePaths 列出的目录可写。应用要写日志或数据库,必须把目录加进去。
三、启用并查看
sudo systemctl daemon-reload
sudo systemctl enable --now myapp
systemctl status myapp
journalctl -u myapp -f
四、踩坑
坑 1:改了 unit 文件没生效。每次修改后必须 daemon-reload,否则 systemctl 还在用旧配置,systemctl status 会提示 unit 文件已变更。
坑 2:开了 ProtectSystem=strict 后服务起不来,日志里是 EROFS(只读文件系统)或 EACCES。原因是应用要往目录里写文件,而该目录不在 ReadWritePaths 里。另外 ReadWritePaths 里的目录必须事先存在,否则会报 namespace 相关的错误(状态码 226/NAMESPACE)。先 mkdir 再启动即可。
坑 3:服务一直在重启。默认 5 次/10 秒内连续失败,systemd 会放弃并进入 failed 状态。用 systemctl reset-failed myapp 清除后再看 journalctl 找真正原因,别只盯着重启本身。
坑 4:想绑 80/443 这类低端口,但用了普通用户。可以加:
AmbientCapabilities=CAP_NET_BIND_SERVICE
或者更常见的做法,让应用监听高位端口,前面用 nginx 反代。
五、检查安全评分
systemd 自带一个评估工具:
systemd-analyze security myapp
它会按各项选项给出一个"暴露程度"评分。不用追求满分,很多选项会影响应用正常运行,我的做法是逐项加、加一项重启验证一项。
六、小结
用 systemd 托管小服务的收益很实在:开机自启、崩溃自动重启、日志统一进 journald、还能顺便限制资源。我的经验是先让服务正常跑起来,再逐步加 MemoryMax 和 ProtectSystem 这类限制,出问题时看 journalctl 通常一眼就能定位。
- 点赞
- 收藏
- 关注作者
评论(0)