Linux上服务(Systemd)自动重启

前言

有时因为各种原因,比如程序崩溃、内存不足等导致服务 down 掉,我们可以使用 systemd 让服务自动重启。

自动重启

在 Linux 上,让 Go 服务“崩溃后自动重启”最省心的做法就是:

不要把守护逻辑写进 Go 程序里,而是交给 systemd(Ubuntu/CentOS/Debian 均自带)。

systemd 会在进程退出码非 0 时立即帮你拉起,还能限制重启频率、记录日志、设置环境变量、开机自启,一条命令搞定。

服务

准备一个最小单元文件。

假设你的可执行文件放在 /data/wwwjarapi/8931mcws/xh-control-ws,示例中用 root 用户(生产环境建议创建专用系统用户运行)。

创建文件

1
vi /etc/systemd/system/xh-control-ws.service

内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[Unit]
Description=xh-control-ws
After=network.target

[Service]
Type=simple
User=root
Group=root
WorkingDirectory=/data/wwwjarapi/8931mcws
ExecStart=/data/wwwjarapi/8931mcws/xh-control-ws
Restart=always
RestartSec=5s
StartLimitInterval=60s
StartLimitBurst=10
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target

[Unit]Description 为服务描述,After=network.target 表示等网络就绪后再启动。

[Service] 配置说明:

Type=simple → 拉起进程就算启动完成
User=root → 用 root 身份跑(示例用,生产建议专用用户)
Group=root → 用户组 root
WorkingDirectory=… → 先 cd 到该目录再启动
ExecStart=… → 要执行的命令(必须绝对路径)
Restart=always → 一挂就重启
RestartSec=5s → 等待5秒再重启
StartLimitInterval=60s + StartLimitBurst=10 → 60 秒内超 10 次重启就放弃
StandardOutput=journal → 把 stdout 打进 systemd 日志
StandardError=journal → 把 stderr 也打进 systemd 日志

启用并立即启动

1
2
sudo systemctl daemon-reload
sudo systemctl enable --now xh-control-ws

之后若修改过 .service 文件,需要先执行 daemon-reloadstartrestart

验证

手动 kill 进程:

1
2
ps -ef | grep xh-control-ws
sudo kill -9 <pid>

几秒后执行 systemctl status xh-control-ws,应看到 Active: active (running),说明已被自动拉起。

查看状态

1
systemctl status xh-control-ws

查看实时日志:

1
journalctl -u xh-control-ws -f

服务启动/停止

正常停止/启动服务

1
2
3
4
5
6
7
8
# 只停掉当前运行实例(下次开机若 enable 了仍会自启)
sudo systemctl stop xh-control-ws

# 需要时再启动
sudo systemctl start xh-control-ws

# 重启(先 stop 再 start)
sudo systemctl restart xh-control-ws

彻底禁用自启(stop + disable)

1
sudo systemctl disable --now xh-control-ws   # --now 顺便帮你 stop

环境变量

在 Linux 系统中使用 systemd 管理服务时,若需为服务进程注入环境变量(如 JAVA_HOMEPATH、自定义配置等),不能依赖用户 shell 配置文件(如 ~/.bashrc/etc/profile),因为 systemd 服务默认运行在一个“干净”的隔离环境中,不会加载这些交互式 shell 的环境变量 。

以下是 systemd 中设置环境变量的主流方式,按推荐程度和适用场景分类说明:

Environment

直接在 service 文件中使用 Environment=(适用于少量变量)

这是最简单直接的方式,适合变量数量较少、值固定的场景。

示例:

1
2
3
4
5
[Service]
Environment="JAVA_HOME=/usr/lib/jvm/java-17-openjdk"
Environment="NODE_ENV=production"
Environment="PATH=/opt/node/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin"
ExecStart=/opt/myapp/server.js

特点:

  • 变量写死在服务文件中,便于审计。
  • 支持带空格或特殊字符的值(用双引号包裹)。
  • 修改后需执行 systemctl daemon-reload 生效 。

推荐用于:关键路径(如 PATHJAVA_HOME)或简单配置。


EnvironmentFile

通过 EnvironmentFile= 加载外部配置文件(适用于多变量或敏感信息)

当环境变量较多、需要区分开发/测试/生产环境,或包含敏感信息(如 API 密钥、数据库密码)时,推荐使用此方式。

步骤:

创建环境变量文件(如 /etc/myapp/env.conf):

1
2
3
4
5
# /etc/myapp/env.conf
APP_PORT=8080
DB_HOST=localhost
DB_PASSWORD='my$ecureP@ss' # 特殊字符建议用引号包裹
LOG_LEVEL=info

在 service 文件中引用

1
2
3
4
5
[Service]
EnvironmentFile=/etc/myapp/env.conf
# 或使用可选文件(不存在不报错)
EnvironmentFile=-/etc/myapp/optional.env
ExecStart=/usr/bin/myapp --port ${APP_PORT}

安全建议:

对含敏感信息的文件设置严格权限:

1
2
chmod 600 /etc/myapp/env.conf
chown myappuser:myappgroup /etc/myapp/env.conf

可组合多个 EnvironmentFile 实现分层配置(如 base + secrets + network)。

推荐用于

生产环境、多变量管理、配置与代码分离。

使用 drop-in 覆盖配置

使用 drop-in 覆盖配置(override.conf

无需修改原始 service 文件,适合运维人员动态调整环境变量。

操作:

1
sudo systemctl edit myapp.service

系统会自动创建 /etc/systemd/system/myapp.service.d/override.conf,内容如下:

1
2
3
[Service]
Environment="DEBUG=true"
EnvironmentFile=/etc/myapp/prod.env

优点

避免直接编辑主 service 文件,便于版本控制和回滚 。

通过包装脚本设置环境

通过包装脚本设置环境(兼容旧脚本,但不推荐长期使用)

若启动脚本严重依赖 .bashrc/etc/profile,可临时使用此方法:

示例脚本

/opt/myapp/start.sh

1
2
3
4
#!/bin/bash
source /etc/profile # 加载全局 profile
source ~/.bashrc # 加载用户配置(需指定用户)
exec /opt/myapp/main.py # 使用 exec 替换进程

service 文件

1
2
3
[Service]
User=myuser
ExecStart=/opt/myapp/start.sh

注意事项

  • 性能略低(额外启动 shell 进程)。
  • 可能引入不可控变量,破坏服务的确定性。
  • 仅建议用于迁移过渡期

全局设置(谨慎使用)

可通过以下方式为所有 systemd 服务设置默认环境变量:

  • /etc/environment:纯键值对,无 shell 语法,适用于 LANGPATH 等静态变量 。
  • /etc/systemd/system.conf 中的 DefaultEnvironment=:影响所有 system 级服务(需重启生效)。

注意

不推荐:除非确实需要全局统一配置,否则会降低服务隔离性和可移植性。

调试与验证技巧

查看服务最终加载的环境变量:

1
systemctl show myapp.service --property=Environment

检查环境文件是否被正确读取:

1
journalctl -u myapp.service

ExecStart 中引用变量时,必须使用 ${VAR} 语法,而非 $VAR

总结

最佳实践建议

场景 推荐方式
少量固定变量 Environment= 直接声明
多变量、敏感信息、多环境 EnvironmentFile= + 权限控制
运维动态调整 systemctl edit 创建 override.conf
兼容旧脚本(临时) 包装脚本
全局 PATH/LANG /etc/environment

核心原则

不要依赖 shell 配置文件显式声明或通过受控文件注入环境变量,以确保服务的可重复性、安全性和可维护性 。