写 Python / Node 服务的 systemd 单元时,程序里各种相对路径都得基于”工作目录”来解读。systemd 提供了 WorkingDirectory 字段,比在 ExecStart 里 cd 一下再启动规范得多。
最简单的例子
[Unit]
Description=My App
After=network-online.target
[Service]
Type=simple
User=appuser
WorkingDirectory=/opt/myapp
ExecStart=/usr/bin/python3 main.py
Restart=always
RestartSec=3
[Install]
WantedBy=multi-user.target
WorkingDirectory=/opt/myapp 的效果:
- 进程启动时 cwd 就是这个目录
ExecStart里的相对路径按它算(比如python3 main.py就是/opt/myapp/main.py)- Python 里
os.getcwd()返回这个目录 - 写日志到
./logs/app.log就是/opt/myapp/logs/app.log
为什么不用 cd
见过这种写法:
ExecStart=/bin/bash -c 'cd /opt/myapp && python3 main.py'
能跑,但坏处一堆:
- 多一层 shell,进程树多了个 bash
- 语义没那么直观
- systemd 的
Restart/ 信号处理会作用在 bash 上,不是真进程 - 环境变量、User、日志跟你想的不一样
能用字段就用字段,cd 是最后的兜底。
常配套的字段
User / Group
以指定用户启动,不用 root 跑业务:
User=appuser
Group=appuser
注意 WorkingDirectory 里的目录必须让这个用户能读、能进:
sudo chown -R appuser:appuser /opt/myapp
EnvironmentFile
把环境变量放外面,方便修改:
EnvironmentFile=/etc/myapp/env
env 文件内容:
DATABASE_URL=postgres://user:pass@localhost/mydb
APP_PORT=8000
代码里 os.getenv("DATABASE_URL") 就能拿到。
Restart
崩溃后的行为:
Restart=always # 无条件重启
RestartSec=3 # 3 秒后重启
StartLimitInterval=60 # 60 秒内
StartLimitBurst=5 # 最多重启 5 次,再多就放弃
生产上都需要——不然崩了以后就得手动来。
日志
默认 stdout / stderr 会走 journald:
journalctl -u myapp -f
journalctl -u myapp --since "10 minutes ago"
想直接写文件:
StandardOutput=append:/var/log/myapp/out.log
StandardError=append:/var/log/myapp/err.log
append: 需要 systemd 240+,老版本用 file:。
一个”生产可用”的模板
[Unit]
Description=My FastAPI App
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=appuser
Group=appuser
WorkingDirectory=/opt/myapp
EnvironmentFile=/etc/myapp/env
ExecStart=/opt/myapp/.venv/bin/uvicorn main:app --host 0.0.0.0 --port 8000
Restart=always
RestartSec=3
StartLimitBurst=5
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
启用:
sudo systemctl daemon-reload
sudo systemctl enable --now myapp
sudo systemctl status myapp
一句话总结
WorkingDirectory=/path 就是 cwd,不要在 ExecStart 里套 bash -c 'cd ... && ...'。搭配 User、EnvironmentFile、Restart 一份 systemd 单元就是完整的服务定义。
