systemd 托管应用
High Contrast
Dark Mode
Light Mode
Sepia
Forest
2 min read419 words

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,否则用的是旧定义

本节执行清单

下一节蓝绿、滚动与回滚策略——开始考虑发布失败怎么办。