systemd 托管应用
一个好的部署,不只是"代码放上去了",而是"服务能被稳定拉起、停止、重启、观察"。
生产托管结构
graph TD
A[代码目录 /opt/myapp/current] --> B[systemd .service 文件]
B --> C[应用进程 ExecStart]
B --> D[journal 日志 StandardOutput=journal]
B --> E[重启策略 Restart=on-failure]
B --> F[依赖关系 After=postgresql.service]
完整生产级 .service 文件
将以下内容保存为 /etc/systemd/system/myapp.service:
[Unit]
Description=MyApp Production Service
Documentation=https://github.com/example/myapp
After=network.target postgresql.service
Requires=postgresql.service
[Service]
Type=simple
User=app
Group=app
WorkingDirectory=/opt/myapp/current
# 环境变量文件(不要把密钥写进 service 文件)
EnvironmentFile=/etc/myapp/myapp.env
# 启动前检查(可选)
ExecStartPre=/opt/myapp/current/scripts/pre-start.sh
# 主进程
ExecStart=/usr/bin/node /opt/myapp/current/server.js
# 优雅停止(给应用 30 秒处理在途请求)
ExecStop=/bin/kill -SIGTERM $MAINPID
TimeoutStopSec=30
KillMode=mixed
# 重启策略:进程异常退出时自动重启,不重启 code=0 的正常退出
Restart=on-failure
RestartSec=5
StartLimitIntervalSec=60
StartLimitBurst=3
# 日志
StandardOutput=journal
StandardError=journal
SyslogIdentifier=myapp
# 资源限制
LimitNOFILE=65536
MemoryMax=512M
[Install]
WantedBy=multi-user.target
安装和启动:
sudo systemctl daemon-reload # 每次修改 .service 文件后必须执行
sudo systemctl enable myapp # 开机自启
sudo systemctl start myapp # 启动服务
sudo systemctl status myapp # 查看状态
字段对照说明
| 字段 | 作用 | 典型值 |
|---|---|---|
After | 声明依赖启动顺序(但不强制) | network.target postgresql.service |
Requires | 强制依赖(依赖失败则本服务也失败) | postgresql.service |
EnvironmentFile | 注入环境变量(不放到代码里) | /etc/myapp/myapp.env |
Restart=on-failure | 只在异常退出时重启,不重启 exit 0 | 生产推荐 |
RestartSec | 重启间隔(防止崩溃循环) | 5(秒) |
LimitNOFILE | 最大文件描述符(Node/Python 高并发需要) | 65536 |
SyslogIdentifier | journalctl 过滤关键词 | myapp |
常见故障调试表
| 症状 | 可能原因 | 排查命令 |
|---|---|---|
Active: failed | ExecStart 路径错误 / 程序崩溃 | systemctl status myapp |
| 服务启动但立即退出 | 应用未捕获异常 / 端口冲突 | journalctl -u myapp -n 100 |
Start request repeated too quickly | 崩溃循环触发了 StartLimitBurst | systemctl reset-failed myapp && systemctl start myapp |
| 开机没有自启 | 忘记 systemctl enable | systemctl is-enabled myapp |
| 修改 .service 无效 | 没有执行 daemon-reload | sudo systemctl daemon-reload |
实时日志查看:
journalctl -u myapp -f # 追踪实时日志
journalctl -u myapp --since "1 hour ago" # 最近 1 小时
journalctl -u myapp -n 50 --no-pager # 最后 50 行
常见误区
| 误区 | 正确做法 |
|---|---|
Restart=always — 包括正常 exit 0 也会重启 | 用 Restart=on-failure 避免正常停机被重新拉起 |
把密钥直接写进 Environment=SECRET=xxx | 用 EnvironmentFile 指向权限为 600 的文件 |
修改 .service 后直接 systemctl restart | 先 daemon-reload 再 restart,否则用的是旧定义 |
本节执行清单
- [ ] 为你的应用写完整 .service 文件(含
[Unit]、[Service]、[Install]三段) - [ ] 验证
enable和restart都有效 - [ ] 故意让进程退出一次,检查是否自动恢复
- [ ] 用
journalctl -u myapp -f确认日志正常输出
下一节:蓝绿、滚动与回滚策略——开始考虑发布失败怎么办。