在 Linux 服务器或边缘设备中,我们经常需要让某个程序在系统启动后自动运行,例如:
不少项目会使用 rc.local、桌面“启动应用程序”或者 Shell 脚本配合 gnome-terminal 实现自启动。这些方式虽然可以运行,但存在明显问题:
在采用 systemd 的 Linux 系统中,更推荐将程序编写成 systemd 服务。
本文将系统介绍 systemd 的基本概念、启动顺序、依赖编排、服务文件编写、开机自启动和常见故障排查,并以 Python MQTT 转发程序为例给出完整配置。
systemd 是目前主流 Linux 发行版使用的系统和服务管理器,通常作为系统启动后的第一个用户空间进程运行。
可以通过下面的命令确认:
ps -p 1 -o pid,comm,args典型输出如下:
PID COMMAND COMMAND
1 systemd /sbin/initsystemd 的主要职责包括:
systemd 管理的基本对象称为 Unit,也就是“单元”。
不同后缀代表不同类型的 Unit。
Unit 后缀 | 作用 |
|---|---|
.service | 管理后台服务或普通进程 |
.target | 对多个 Unit 进行分组,类似启动阶段 |
.socket | 管理 Socket,并支持按需启动服务 |
.timer | 定时任务,可以替代部分 cron 场景 |
.mount | 管理文件系统挂载 |
.automount | 管理自动挂载 |
.path | 监控文件或目录变化 |
.device | 表示内核识别的设备 |
.slice | 对进程进行资源分组 |
.scope | 管理由外部创建的进程 |
部署普通 Python、Java、Go 或 Node.js 程序时,最常用的是 .service 文件。
常见的 systemd 配置目录如下:
/etc/systemd/system/
/run/systemd/system/
/usr/lib/systemd/system/部分 Debian、Ubuntu 系统也会使用:
/lib/systemd/system/它们的用途和优先级如下:
路径 | 用途 |
|---|---|
/etc/systemd/system/ | 管理员创建或修改的服务,优先级最高 |
/run/systemd/system/ | 运行期间临时生成,重启后消失 |
/usr/lib/systemd/system/ | 软件包安装的默认服务文件 |
/lib/systemd/system/ | 部分发行版的软件包服务目录 |
自己编写的服务建议统一放在:
/etc/systemd/system/不要直接修改 /usr/lib/systemd/system/ 或 /lib/systemd/system/ 中的软件包文件,因为软件升级时修改可能被覆盖。
查看 Unit 文件的实际来源:
systemctl cat ssh.service查看 systemd 搜索 Unit 的目录:
systemd-analyze unit-paths传统启动方式通常按照脚本编号顺序串行执行。systemd 则通过依赖关系构建启动图,在满足依赖和顺序约束的情况下并行启动服务。
常见 target 包括:
Target | 作用 |
|---|---|
basic.target | 基础系统初始化完成 |
network.target | 网络管理组件已经启动 |
network-online.target | 网络被认为已经可用 |
multi-user.target | 多用户命令行运行级别 |
graphical.target | 图形界面运行级别 |
rescue.target | 救援模式 |
reboot.target | 重启系统 |
poweroff.target | 关闭系统 |
查看系统默认启动目标:
systemctl get-default服务器通常是:
multi-user.target桌面系统通常是:
graphical.target查看某个 target 会启动哪些 Unit:
systemctl list-dependencies multi-user.target反向查看哪些 Unit 依赖它:
systemctl list-dependencies --reverse network-online.target这是 systemd 配置中最容易混淆的地方。
After 和 Before它们只控制启动顺序,不会主动拉起另一个服务。
After=network-online.target表示当前服务要在 network-online.target 之后启动,但不代表 systemd 一定会启动 network-online.target。
Wants表示弱依赖。
Wants=network-online.target
After=network-online.targetsystemd 会尝试启动 network-online.target。即使该 target 启动失败,当前服务仍可能继续启动。
Requires表示强依赖。
Requires=algorithm-api.service
After=algorithm-api.service如果依赖服务无法启动,当前服务通常也不会成功启动。依赖服务被显式停止时,当前服务也会受到关联影响。
BindsTo比 Requires 绑定得更紧。当被绑定的 Unit 消失或停止时,当前 Unit 也会停止。
只要求启动顺序:
After=algorithm-api.service需要尽量启动依赖,但允许依赖失败:
Wants=algorithm-api.service
After=algorithm-api.service必须依赖另一个服务:
Requires=algorithm-api.service
After=algorithm-api.service需要注意,After 只表示前一个服务完成了 systemd 所定义的“启动过程”,不一定代表应用已经可以对外提供业务服务。
如果服务必须等到端口真正可用,可以采用以下方案:
Type=notify 主动通知 systemdExecStartPre 执行健康检查对于 MQTT、数据库和网络 API,应用内部重试通常比固定执行 sleep 10 更可靠。
一个典型的 .service 文件由三部分组成:
[Unit]
Description=示例服务
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=cat
WorkingDirectory=/home/cat/app
ExecStart=/usr/bin/python3 /home/cat/app/main.py
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target[Unit]描述服务本身以及与其他 Unit 的关系。
常用字段:
Description=服务说明
Documentation=https://example.com/docs
After=network-online.target
Before=other.service
Wants=network-online.target
Requires=other.service
ConditionPathExists=/home/cat/app/main.py[Service]描述服务如何启动、停止、重启以及以什么身份运行。
常用字段:
Type=simple
User=cat
Group=cat
WorkingDirectory=/home/cat/app
ExecStart=/usr/bin/python3 /home/cat/app/main.py
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=5
TimeoutStopSec=30[Install]描述执行 systemctl enable 时,服务应该挂到哪个 target。
[Install]
WantedBy=multi-user.target如果没有 [Install],服务仍然可以手动启动,但通常不能通过普通的 systemctl enable 设置开机启动。
Type=simple默认类型。执行 ExecStart 后,systemd 就认为服务已经启动。
Type=simple
ExecStart=/usr/bin/python3 /opt/app/main.py适合大多数前台运行的 Python、Java、Go 和 Node.js 程序。
Type=exec与 simple 类似,但 systemd 会等到目标程序成功执行后,才认为启动操作成功。
Type=exec较新的 systemd 版本可以优先考虑该类型。
Type=forking适用于启动后主动 fork 到后台的传统程序。
Type=forking
PIDFile=/run/example.pid现代程序在 systemd 下通常应该以前台模式运行,不建议自行转入后台。
Type=oneshot适合执行一次后退出的初始化任务。
Type=oneshot
ExecStart=/usr/local/bin/init-device.sh
RemainAfterExit=yesType=notify程序完成初始化后,通过 sd_notify 通知 systemd。
Type=notify适合需要准确表达“服务已经准备完成”的程序,但应用本身必须支持 systemd 通知协议。
假设程序路径为:
/home/cat/websocket/edge_mqtt_forwarder.pyPython 虚拟环境为:
/home/cat/miniconda3/envs/edge/bin/python创建配置文件:
sudo vim /etc/default/edge-mqtt-forwarder内容如下:
DEVICE_ID=camera_021
MQTT_PASSWORD=12345678
BROKER_HOST=192.168.1.100
BROKER_PORT=1883
MQTT_KEEPALIVE=60
ALGORITHM_API_HOST=127.0.0.1
ALGORITHM_API_PORT=8889
ALGORITHM_API_PATH=/config
HTTP_TIMEOUT_SECONDS=10
STATE_PATH=/var/lib/edge-mqtt-forwarder/forwarder_state.json
LOG_LEVEL=INFO限制配置文件权限:
sudo chmod 600 /etc/default/edge-mqtt-forwarder密码等敏感信息不建议直接写在 .service 文件中,因为 systemctl cat 等命令可以直接查看 Unit 内容。
创建文件:
sudo vim /etc/systemd/system/edge-mqtt-forwarder.service写入以下内容:
[Unit]
Description=Edge MQTT Configuration Forwarder
Documentation=man:systemd.service(5)
Wants=network-online.target
After=network-online.target
ConditionPathExists=/home/cat/websocket/edge_mqtt_forwarder.py
[Service]
Type=simple
User=cat
Group=cat
WorkingDirectory=/home/cat/websocket
EnvironmentFile=/etc/default/edge-mqtt-forwarder
ExecStart=/home/cat/miniconda3/envs/edge/bin/python /home/cat/websocket/edge_mqtt_forwarder.py
Restart=on-failure
RestartSec=5
StartLimitIntervalSec=60
StartLimitBurst=10
TimeoutStopSec=30
KillSignal=SIGTERM
StateDirectory=edge-mqtt-forwarder
StateDirectoryMode=0750
StandardOutput=journal
StandardError=journal
SyslogIdentifier=edge-mqtt-forwarder
[Install]
WantedBy=multi-user.target如果程序实际使用 Conda 的 base 环境,则需要将 ExecStart 修改为真实的 Python 路径,例如:
ExecStart=/home/cat/miniconda3/bin/python /home/cat/websocket/edge_mqtt_forwarder.py可以使用下面的命令确认 Python 路径:
which python或者:
conda run -n edge which python在 systemd 中通常不需要执行:
source conda.sh
conda activate edge直接调用目标环境里的 Python 解释器更加稳定。
每次新增或修改 Unit 文件后,都要重新加载 systemd 配置:
sudo systemctl daemon-reload启动服务:
sudo systemctl start edge-mqtt-forwarder.service查看状态:
sudo systemctl status edge-mqtt-forwarder.service设置开机自启动:
sudo systemctl enable edge-mqtt-forwarder.service也可以使用一条命令同时设置开机启动并立即启动:
sudo systemctl enable --now edge-mqtt-forwarder.service检查是否已经设置为开机启动:
systemctl is-enabled edge-mqtt-forwarder.service检查当前是否正在运行:
systemctl is-active edge-mqtt-forwarder.servicestart 和 enable 的区别这是初次使用 systemd 时最常见的问题。
sudo systemctl start example.service表示立即启动服务,但重启系统后不一定自动运行。
sudo systemctl enable example.service表示建立开机启动关系,但不会保证服务当前立即启动。
因此,通常使用:
sudo systemctl enable --now example.service执行 enable 后,systemd 通常会建立类似下面的符号链接:
/etc/systemd/system/multi-user.target.wants/example.service
-> /etc/systemd/system/example.service这意味着启动进入 multi-user.target 时,会将该服务一起拉起。
查看启用关系:
ls -l /etc/systemd/system/multi-user.target.wants/取消开机启动:
sudo systemctl disable example.service取消开机启动并立即停止:
sudo systemctl disable --now example.service假设系统包含三个程序:
MQTT Broker
↓
本地算法 API
↓
MQTT 配置转发程序其中算法 API 的服务名为:
algorithm-api.serviceMQTT 转发程序必须在算法 API 启动后运行,可以这样配置:
[Unit]
Description=Edge MQTT Configuration Forwarder
Wants=network-online.target
After=network-online.target
Requires=algorithm-api.service
After=algorithm-api.service
[Service]
Type=simple
User=cat
WorkingDirectory=/home/cat/websocket
EnvironmentFile=/etc/default/edge-mqtt-forwarder
ExecStart=/home/cat/miniconda3/envs/edge/bin/python /home/cat/websocket/edge_mqtt_forwarder.py
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target也可以把多个业务服务组合成一个自定义 target。
创建:
sudo vim /etc/systemd/system/edge-platform.target内容如下:
[Unit]
Description=Edge Computing Platform
Wants=algorithm-api.service edge-mqtt-forwarder.service
After=network-online.target
AllowIsolate=no
[Install]
WantedBy=multi-user.target启用整个业务服务组:
sudo systemctl daemon-reload
sudo systemctl enable --now edge-platform.target查看依赖关系:
systemctl list-dependencies edge-platform.target这样可以使用统一入口管理一组服务:
sudo systemctl start edge-platform.target
sudo systemctl stop edge-platform.target
sudo systemctl restart edge-platform.target需要注意,停止 target 时是否同时停止所有成员,还取决于成员服务的依赖和归属配置。如果要求服务生命周期严格跟随 target,可以在服务的 [Unit] 中增加:
PartOf=edge-platform.target例如:
[Unit]
Description=Edge MQTT Configuration Forwarder
PartOf=edge-platform.target
After=algorithm-api.service
Requires=algorithm-api.service下面的配置很常见:
Wants=network-online.target
After=network-online.target但 network-online.target 是否真正等待网络可用,还取决于系统使用的网络管理器。
如果使用 NetworkManager:
sudo systemctl enable NetworkManager-wait-online.service如果使用 systemd-networkd:
sudo systemctl enable systemd-networkd-wait-online.service即便如此,外部 MQTT Broker、DNS 或互联网仍可能暂时不可用。因此,联网程序最好自身实现断线重连,不能只依赖 systemd 的启动顺序。
常用配置如下:
Restart=on-failure
RestartSec=5Restart 可选值:
配置 | 含义 |
|---|---|
no | 不自动重启,默认值 |
on-success | 正常退出时重启 |
on-failure | 异常退出、信号终止或超时时重启 |
on-abnormal | 由信号、超时等异常原因退出时重启 |
on-abort | 因未捕获信号退出时重启 |
always | 无论退出原因都重启 |
对于普通后台服务,通常推荐:
Restart=on-failure
RestartSec=5为了避免程序不断崩溃形成无限重启循环,还可以设置启动频率限制:
StartLimitIntervalSec=60
StartLimitBurst=10表示 60 秒内最多尝试启动 10 次。
修复问题后,如果服务因为触发频率限制而无法重新启动,可以执行:
sudo systemctl reset-failed edge-mqtt-forwarder.service
sudo systemctl restart edge-mqtt-forwarder.servicesystemd 服务默认可以将标准输出和标准错误交给 journald。
实时查看服务日志:
journalctl -u edge-mqtt-forwarder.service -f查看最近 100 行:
journalctl -u edge-mqtt-forwarder.service -n 100查看本次启动以来的日志:
journalctl -u edge-mqtt-forwarder.service -b查看上一次启动的日志:
journalctl -u edge-mqtt-forwarder.service -b -1查看指定时间之后的日志:
journalctl -u edge-mqtt-forwarder.service --since "2026-09-21 08:00:00"只查看错误级别:
journalctl -u edge-mqtt-forwarder.service -p err查看日志占用空间:
journalctl --disk-usage有些程序原来通过下面的脚本启动:
#!/bin/bash
sleep 5
source /home/cat/miniconda3/etc/profile.d/conda.sh
conda activate base
gnome-terminal --wait -- bash -c "
cd /home/cat/websocket/
python3 /home/cat/websocket/websocket.py
exec bash
"这种方式不适合作为系统后台服务,原因包括:
gnome-terminal 依赖图形桌面和用户会话exec bash 会让终端一直存在,但并不等于可靠的服务管理sleep 5 只是固定等待,不能证明依赖服务已经就绪可以直接转换为:
[Unit]
Description=WebSocket Python Service
Wants=network-online.target
After=network-online.target
ConditionPathExists=/home/cat/websocket/websocket.py
[Service]
Type=simple
User=cat
Group=cat
WorkingDirectory=/home/cat/websocket
ExecStart=/home/cat/miniconda3/bin/python /home/cat/websocket/websocket.py
Restart=on-failure
RestartSec=5
TimeoutStopSec=30
StandardOutput=journal
StandardError=journal
SyslogIdentifier=websocket-service
[Install]
WantedBy=multi-user.target保存为:
/etc/systemd/system/websocket.service然后执行:
sudo systemctl daemon-reload
sudo systemctl enable --now websocket.service
sudo systemctl status websocket.service查看日志:
journalctl -u websocket.service -f这样就不再需要 autoExec.sh、gnome-terminal 和固定的 sleep 5。
ExecStart 默认不是由 Bash 解释执行的。
下面的写法通常不能按预期工作:
ExecStart=cd /home/cat/app && python3 main.py应该写成:
WorkingDirectory=/home/cat/app
ExecStart=/usr/bin/python3 /home/cat/app/main.py下面的 Shell 语法也不能直接使用:
ExecStart=source env.sh
ExecStart=python3 main.py | tee app.log
ExecStart=echo $HOME
ExecStart=~/venv/bin/python main.py如果确实必须执行复杂 Shell 命令,应显式调用 Shell:
ExecStart=/bin/bash -lc 'source /opt/app/env.sh && exec python3 /opt/app/main.py'不过,更推荐使用:
WorkingDirectory 设置工作目录Environment 设置单个环境变量EnvironmentFile 加载环境变量文件另外,一个普通服务只能有一个非空的 ExecStart。如果要连续执行准备命令,可以使用:
ExecStartPre=/usr/local/bin/check-config
ExecStartPre=/usr/local/bin/wait-for-api
ExecStart=/usr/bin/python3 /opt/app/main.py
ExecStartPost=/usr/local/bin/report-started停止后需要执行清理操作时,可以使用:
ExecStopPost=/usr/local/bin/cleanup不要为了修改软件包自带服务而直接编辑原文件。
执行:
sudo systemctl edit example.servicesystemd 会创建类似下面的覆盖文件:
/etc/systemd/system/example.service.d/override.conf例如修改重启策略:
[Service]
Restart=always
RestartSec=10如果要覆盖原来的 ExecStart,必须先将其清空:
[Service]
ExecStart=
ExecStart=/opt/new-env/bin/python /opt/app/main.py保存后执行:
sudo systemctl daemon-reload
sudo systemctl restart example.service查看最终合并后的配置:
systemctl cat example.service# 启动服务
sudo systemctl start example.service
# 停止服务
sudo systemctl stop example.service
# 重启服务
sudo systemctl restart example.service
# 配置支持时重新加载,不中断进程
sudo systemctl reload example.service
# 查看状态
systemctl status example.service
# 设置开机启动
sudo systemctl enable example.service
# 取消开机启动
sudo systemctl disable example.service
# 设置开机启动并立即运行
sudo systemctl enable --now example.service
# 取消开机启动并立即停止
sudo systemctl disable --now example.service
# 判断是否正在运行
systemctl is-active example.service
# 判断是否已经启用
systemctl is-enabled example.service
# 查看失败的服务
systemctl --failed
# 查看所有正在运行的服务
systemctl list-units --type=service --state=running
# 查看所有已安装的服务文件
systemctl list-unit-files --type=service
# 修改 Unit 后重新加载
sudo systemctl daemon-reload
# 查看完整 Unit 内容
systemctl cat example.service
# 查看依赖树
systemctl list-dependencies example.service原因通常是没有重新加载配置。
sudo systemctl daemon-reload
sudo systemctl restart example.service常见原因:
排查:
systemctl status example.service
journalctl -u example.service -n 100 --no-pager
systemctl show example.servicestatus=203/EXEC一般表示 ExecStart 指定的文件无法执行。
检查:
ls -l /实际/程序/路径
file /实际/程序/路径重点确认:
systemd 应管理一个持续运行的前台进程。如果程序自行转入后台,systemd 可能认为主进程已经退出。
优先让程序以前台模式运行,并使用:
Type=simplesystemd 默认不会读取用户的 .bashrc 或 .profile。
应该显式配置:
Environment="APP_ENV=production"
EnvironmentFile=/etc/default/example例如 Python 中使用:
Path("forwarder_state.json")它会相对于 WorkingDirectory 解析。
更稳妥的方式是通过环境变量传入绝对路径:
Environment="STATE_PATH=/var/lib/example/forwarder_state.json"可以添加:
Wants=network-online.target
After=network-online.target同时确保对应的 wait-online 服务已经启用,并让应用自身具备断线重连能力。
查看状态:
systemctl status example.service清除失败计数:
sudo systemctl reset-failed example.service
sudo systemctl restart example.servicesystemd 提供了 Unit 文件检查工具:
systemd-analyze verify /etc/systemd/system/edge-mqtt-forwarder.service查看系统启动耗时:
systemd-analyze查看启动最慢的服务:
systemd-analyze blame查看关键启动链:
systemd-analyze critical-chain查看指定服务的启动链:
systemd-analyze critical-chain edge-mqtt-forwarder.service这些命令对于排查“为什么程序开机后很久才启动”非常有用。
编写 systemd 服务时,建议遵循以下原则:
WorkingDirectory 明确工作目录。conda activate。gnome-terminal 等图形界面程序。EnvironmentFile 管理运行参数。After 配置顺序,使用 Wants 或 Requires 配置依赖。sleep 代替服务就绪检查。systemctl daemon-reload。systemd-analyze verify。systemctl enable --now 同时完成启用和启动。systemd 不只是一个“开机启动工具”,它还是 Linux 中完整的服务编排和进程管理框架。
实现一个可靠的开机自启动服务,核心流程只有四步:
# 1. 创建服务文件
sudo vim /etc/systemd/system/example.service
# 2. 重新加载配置
sudo systemctl daemon-reload
# 3. 设置开机启动并立即运行
sudo systemctl enable --now example.service
# 4. 查看状态和日志
systemctl status example.service
journalctl -u example.service -f真正需要重点理解的是:
After/Before:控制启动顺序
Wants/Requires:控制依赖关系
WantedBy:决定 enable 时挂载到哪个 target
Restart:决定程序退出后的恢复策略
journalctl:负责统一查看服务日志掌握这些概念后,就可以使用 systemd 管理 Python 服务、MQTT 客户端、WebSocket 服务、算法程序以及多个相互依赖的边缘计算组件,获得比启动脚本更稳定、更透明、更容易维护的运行方式。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。