一、最小可用单元文件
保存为 /etc/systemd/system/myapp.service:
[Unit]
Description=My App
After=network.target
[Service]
Type=simple
WorkingDirectory=/opt/myapp
ExecStart=/usr/bin/python3 /opt/myapp/main.py
Restart=always
RestartSec=3
User=www-data
Environment=ENV=prod
[Install]
WantedBy=multi-user.target
三段的作用:
- [Unit]:描述与依赖顺序。
After=network.target表示等网络就绪再启动。 - [Service]:怎么跑。
ExecStart是启动命令,必须用绝对路径。 - [Install]:
systemctl enable时把服务挂到哪个 target 下。
二、Type 怎么选
| Type | 适用 |
|---|---|
simple(默认) | 前台常驻进程,最常用 |
forking | 传统守护进程,自己 fork 到后台 |
oneshot | 执行一次就退出(初始化脚本) |
notify | 程序主动 sd_notify 告诉 systemd 已就绪 |
用错 Type 的典型症状:前台进程写成 forking,systemd 以为它启动了但其实没有;一次性脚本写成 simple,会被反复判定「已退出」。
三、Restart 策略
Restart=always
RestartSec=3
StartLimitIntervalSec=60
StartLimitBurst=5
always:任何退出都重启(包括正常退出)。on-failure:只有非零退出码或信号杀死才重启,通常更合适。RestartSec:重启间隔,太短会在崩溃循环里疯狂刷日志。StartLimit*:60 秒内超过 5 次就停止尝试,避免无限重启拖垮机器。
四、enable ≠ start
这是最常混淆的一对:
systemctl daemon-reload # 改了 unit 文件必须先执行
systemctl start myapp # 现在启动
systemctl enable myapp # 开机自启(不启动)
systemctl enable --now myapp # 两者一起
systemctl status myapp # 看状态与最近日志
systemctl restart myapp
systemctl stop myapp
systemctl disable myapp
改完 unit 文件忘记 daemon-reload,是「配置明明改了却没生效」的头号原因。
五、看日志
journalctl -u myapp -n 100 # 最近 100 行
journalctl -u myapp -f # 实时跟踪(类似 tail -f)
journalctl -u myapp --since "10 min ago"
journalctl -u myapp -p err # 只看错误级别
journalctl 的日志默认存在内存/磁盘,长期保留要配置 Storage=persistent 或改用文件日志。程序自己写日志文件也没问题,两者不冲突。
六、启动失败怎么查
systemctl status myapp看Active:和最后几行输出。journalctl -u myapp -n 50 --no-pager看完整报错。- 常见原因:
ExecStart路径写错、没有执行权限。User指定的用户对WorkingDirectory没有读权限。- 环境变量缺失(systemd 不读
.bashrc,必须用Environment=或EnvironmentFile=)。 - 端口被占用、依赖的服务没起来。
Type与实际行为不符。
- 手动用同样的用户、同样的绝对路径跑一遍
ExecStart,能复现就能定位。
七、几个实用配置
# 环境变量文件
EnvironmentFile=/etc/myapp/env
# 进程挂掉时同时杀掉子进程
KillMode=control-group
# 标准输出重定向到文件(也可交给 journal)
StandardOutput=append:/var/log/myapp.log
StandardError=append:/var/log/myapp.err
# 限制资源
MemoryMax=512M
CPUQuota=80%
EnvironmentFile 的格式是 KEY=value,不能有 export,注释用 #。
小结
三段结构 + ExecStart 绝对路径 + Type=simple + Restart=on-failure;改配置先 daemon-reload;enable 是开机自启、start 是现在运行;排查先看 status 和 journalctl。