写一个可靠的自启服务单元
把一个自己写的程序做成开机自启的服务,最省事的做法是写一个 unit 文件。 下面是能直接用的最小模板:
[Unit]
Description=My Service
After=network.target
[Service]
Type=simple
User=root
WorkingDirectory=/opt/myapp
ExecStart=/usr/bin/node /opt/myapp/dist/main.js
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
安装与启用:
sudo cp myapp.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now myapp
systemctl status myapp --no-pager
Type 怎么选
绝大多数常驻进程用 Type=simple:systemd 执行命令后立即认为服务已启动。
如果程序会自己 fork 到后台(典型的老式守护进程),用 Type=forking。
一次性的初始化任务(比如导入数据、清理缓存)用 Type=oneshot,
配合 RemainAfterExit=yes 让它在执行完后仍显示 active。
Restart 策略
| 取值 | 行为 |
|---|---|
no |
不重启(默认) |
on-failure |
非零退出、被信号杀死、超时时重启 |
always |
无论如何都重启,包括正常退出 |
常驻服务写 on-failure 通常就够。但要注意:配置错误导致的
启动失败也会被当作 failure 并不断重试,这就是下一个坑的来源。
一个把 CPU 和日志一起打满的坑
曾经有两个前端开发服务器的 unit 文件留在机器上,WorkingDirectory
指向的目录后来被删除了。结果是这样:
systemctl status playlink-admin
# Active: activating (auto-restart) (Result: exit-code)
# Main process exited, code=exited, status=200/CHDIR
journalctl -u playlink-admin -n 3
# Changing to the requested working directory failed: No such file or directory
# Failed with result 'exit-code'
status=200/CHDIR 表示切换工作目录失败。因为配了
Restart=always 和 RestartSec=5,
它每 5 秒重启一次,重启计数累积到了 700 次,机器负载一直挂着。
systemctl --failed 里看不到。
它的状态是 activating (auto-restart),不属于 failed。
要发现它,得查 --state=activating 或直接看负载来源。
排查命令:
# 列出反复重启的服务
systemctl list-units --type=service --state=activating --no-legend
# 看重启次数
systemctl show myapp -p NRestarts -p Result -p ExecMainStatus
处理方式:
sudo systemctl disable --now myapp
sudo rm /etc/systemd/system/myapp.service
sudo systemctl daemon-reload
sudo systemctl reset-failed myapp
最后一步 reset-failed 常被忘掉。删了 unit 文件之后,
systemctl is-active myapp 可能还会返回 failed,
因为失败状态被记在内存里。
几个实用的补充项
环境变量
不要把密钥直接写在 unit 里(/etc/systemd/system 通常全局可读),
用独立的环境文件并收紧权限:
[Service]
EnvironmentFile=/opt/myapp/.env
# 同时
chmod 600 /opt/myapp/.env
日志
默认输出会进 journal,用 journalctl -u myapp -f 查看。
如果程序自己有日志文件,注意目录要在 WorkingDirectory 存在的前提下才可写——
这又回到上面那个坑:目录没了,程序连日志都写不出来,只能靠 journal 看到 CHDIR 错误。
启动顺序依赖
After=network.target 只表示顺序,不表示依赖。
如果服务必须等网络真正可用,用:
After=network-online.target
Wants=network-online.target
资源限制
内存泄漏的程序可以加个上限,超过后由 systemd 重启:
[Service]
MemoryMax=512M
MemoryHigh=384M
检查清单
- unit 文件放在
/etc/systemd/system/,改完必做daemon-reload WorkingDirectory和ExecStart里的路径都要用绝对路径- 用
systemctl cat myapp确认 systemd 实际读到的内容 - 用
systemctl show myapp -p NRestarts检查有没有在偷偷重启 - 删除服务时按 disable → rm → daemon-reload → reset-failed 的顺序做