首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >Systemd详解

Systemd详解

原创
作者头像
用户12795436
发布于 2026-09-30 16:19:17
发布于 2026-09-30 16:19:17
810
举报

Systemd 详解:如何编排 Linux 启动服务并实现开机自启动

前言

在 Linux 服务器或边缘设备中,我们经常需要让某个程序在系统启动后自动运行,例如:

  • 启动 Python Web 服务
  • 启动 MQTT 客户端
  • 启动算法服务
  • 按照依赖顺序启动多个程序
  • 程序异常退出后自动重启
  • 统一收集和查询运行日志

不少项目会使用 rc.local、桌面“启动应用程序”或者 Shell 脚本配合 gnome-terminal 实现自启动。这些方式虽然可以运行,但存在明显问题:

  • 依赖桌面环境,服务器没有图形界面时无法使用
  • 无法准确管理服务之间的依赖关系
  • 程序异常退出后不会自动恢复
  • 日志分散,不方便排查问题
  • 不容易判断程序当前是否正常运行

在采用 systemd 的 Linux 系统中,更推荐将程序编写成 systemd 服务。

本文将系统介绍 systemd 的基本概念、启动顺序、依赖编排、服务文件编写、开机自启动和常见故障排查,并以 Python MQTT 转发程序为例给出完整配置。


一、systemd 是什么

systemd 是目前主流 Linux 发行版使用的系统和服务管理器,通常作为系统启动后的第一个用户空间进程运行。

可以通过下面的命令确认:

代码语言:javascript
复制
ps -p 1 -o pid,comm,args

典型输出如下:

代码语言:javascript
复制
PID COMMAND         COMMAND
  1 systemd         /sbin/init

systemd 的主要职责包括:

  • 启动和关闭系统服务
  • 管理服务依赖关系
  • 实现服务并行启动
  • 监控服务运行状态
  • 在服务异常退出后自动重启
  • 管理系统日志
  • 管理挂载点、设备、定时任务和 Socket
  • 提供开机自启动能力

systemd 管理的基本对象称为 Unit,也就是“单元”。


二、systemd Unit 类型

不同后缀代表不同类型的 Unit。

Unit 后缀

作用

.service

管理后台服务或普通进程

.target

对多个 Unit 进行分组,类似启动阶段

.socket

管理 Socket,并支持按需启动服务

.timer

定时任务,可以替代部分 cron 场景

.mount

管理文件系统挂载

.automount

管理自动挂载

.path

监控文件或目录变化

.device

表示内核识别的设备

.slice

对进程进行资源分组

.scope

管理由外部创建的进程

部署普通 Python、Java、Go 或 Node.js 程序时,最常用的是 .service 文件。


三、Unit 文件存放位置及优先级

常见的 systemd 配置目录如下:

代码语言:javascript
复制
/etc/systemd/system/
/run/systemd/system/
/usr/lib/systemd/system/

部分 Debian、Ubuntu 系统也会使用:

代码语言:javascript
复制
/lib/systemd/system/

它们的用途和优先级如下:

路径

用途

/etc/systemd/system/

管理员创建或修改的服务,优先级最高

/run/systemd/system/

运行期间临时生成,重启后消失

/usr/lib/systemd/system/

软件包安装的默认服务文件

/lib/systemd/system/

部分发行版的软件包服务目录

自己编写的服务建议统一放在:

代码语言:javascript
复制
/etc/systemd/system/

不要直接修改 /usr/lib/systemd/system/ 或 /lib/systemd/system/ 中的软件包文件,因为软件升级时修改可能被覆盖。

查看 Unit 文件的实际来源:

代码语言:javascript
复制
systemctl cat ssh.service

查看 systemd 搜索 Unit 的目录:

代码语言:javascript
复制
systemd-analyze unit-paths

四、systemd 如何组织系统启动流程

传统启动方式通常按照脚本编号顺序串行执行。systemd 则通过依赖关系构建启动图,在满足依赖和顺序约束的情况下并行启动服务。

常见 target 包括:

Target

作用

basic.target

基础系统初始化完成

network.target

网络管理组件已经启动

network-online.target

网络被认为已经可用

multi-user.target

多用户命令行运行级别

graphical.target

图形界面运行级别

rescue.target

救援模式

reboot.target

重启系统

poweroff.target

关闭系统

查看系统默认启动目标:

代码语言:javascript
复制
systemctl get-default

服务器通常是:

代码语言:javascript
复制
multi-user.target

桌面系统通常是:

代码语言:javascript
复制
graphical.target

查看某个 target 会启动哪些 Unit:

代码语言:javascript
复制
systemctl list-dependencies multi-user.target

反向查看哪些 Unit 依赖它:

代码语言:javascript
复制
systemctl list-dependencies --reverse network-online.target

五、启动顺序和依赖关系不是一回事

这是 systemd 配置中最容易混淆的地方。

1. After 和 Before

它们只控制启动顺序,不会主动拉起另一个服务。

代码语言:javascript
复制
After=network-online.target

表示当前服务要在 network-online.target 之后启动,但不代表 systemd 一定会启动 network-online.target。

2. Wants

表示弱依赖。

代码语言:javascript
复制
Wants=network-online.target
After=network-online.target

systemd 会尝试启动 network-online.target。即使该 target 启动失败,当前服务仍可能继续启动。

3. Requires

表示强依赖。

代码语言:javascript
复制
Requires=algorithm-api.service
After=algorithm-api.service

如果依赖服务无法启动,当前服务通常也不会成功启动。依赖服务被显式停止时,当前服务也会受到关联影响。

4. BindsTo

比 Requires 绑定得更紧。当被绑定的 Unit 消失或停止时,当前 Unit 也会停止。

5. 常见组合

只要求启动顺序:

代码语言:javascript
复制
After=algorithm-api.service

需要尽量启动依赖,但允许依赖失败:

代码语言:javascript
复制
Wants=algorithm-api.service
After=algorithm-api.service

必须依赖另一个服务:

代码语言:javascript
复制
Requires=algorithm-api.service
After=algorithm-api.service

需要注意,After 只表示前一个服务完成了 systemd 所定义的“启动过程”,不一定代表应用已经可以对外提供业务服务。

如果服务必须等到端口真正可用,可以采用以下方案:

  • 被依赖程序使用 Type=notify 主动通知 systemd
  • 在应用程序内部实现连接重试
  • 使用 ExecStartPre 执行健康检查
  • 由独立脚本等待端口或 HTTP 接口就绪

对于 MQTT、数据库和网络 API,应用内部重试通常比固定执行 sleep 10 更可靠。


六、Service 文件的基本结构

一个典型的 .service 文件由三部分组成:

代码语言:javascript
复制
[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 的关系。

常用字段:

代码语言:javascript
复制
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]

描述服务如何启动、停止、重启以及以什么身份运行。

常用字段:

代码语言:javascript
复制
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。

代码语言:javascript
复制
[Install]
WantedBy=multi-user.target

如果没有 [Install],服务仍然可以手动启动,但通常不能通过普通的 systemctl enable 设置开机启动。


七、Service 的 Type 类型

Type=simple

默认类型。执行 ExecStart 后,systemd 就认为服务已经启动。

代码语言:javascript
复制
Type=simple
ExecStart=/usr/bin/python3 /opt/app/main.py

适合大多数前台运行的 Python、Java、Go 和 Node.js 程序。

Type=exec

与 simple 类似,但 systemd 会等到目标程序成功执行后,才认为启动操作成功。

代码语言:javascript
复制
Type=exec

较新的 systemd 版本可以优先考虑该类型。

Type=forking

适用于启动后主动 fork 到后台的传统程序。

代码语言:javascript
复制
Type=forking
PIDFile=/run/example.pid

现代程序在 systemd 下通常应该以前台模式运行,不建议自行转入后台。

Type=oneshot

适合执行一次后退出的初始化任务。

代码语言:javascript
复制
Type=oneshot
ExecStart=/usr/local/bin/init-device.sh
RemainAfterExit=yes

Type=notify

程序完成初始化后,通过 sd_notify 通知 systemd。

代码语言:javascript
复制
Type=notify

适合需要准确表达“服务已经准备完成”的程序,但应用本身必须支持 systemd 通知协议。


八、实战:将 Python MQTT 程序设置为开机自启动

假设程序路径为:

代码语言:javascript
复制
/home/cat/websocket/edge_mqtt_forwarder.py

Python 虚拟环境为:

代码语言:javascript
复制
/home/cat/miniconda3/envs/edge/bin/python

1. 创建环境变量文件

创建配置文件:

代码语言:javascript
复制
sudo vim /etc/default/edge-mqtt-forwarder

内容如下:

代码语言:javascript
复制
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

限制配置文件权限:

代码语言:javascript
复制
sudo chmod 600 /etc/default/edge-mqtt-forwarder

密码等敏感信息不建议直接写在 .service 文件中,因为 systemctl cat 等命令可以直接查看 Unit 内容。

2. 创建 systemd 服务

创建文件:

代码语言:javascript
复制
sudo vim /etc/systemd/system/edge-mqtt-forwarder.service

写入以下内容:

代码语言:javascript
复制
[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 路径,例如:

代码语言:javascript
复制
ExecStart=/home/cat/miniconda3/bin/python /home/cat/websocket/edge_mqtt_forwarder.py

可以使用下面的命令确认 Python 路径:

代码语言:javascript
复制
which python

或者:

代码语言:javascript
复制
conda run -n edge which python

在 systemd 中通常不需要执行:

代码语言:javascript
复制
source conda.sh
conda activate edge

直接调用目标环境里的 Python 解释器更加稳定。


九、加载服务并设置开机自启动

每次新增或修改 Unit 文件后,都要重新加载 systemd 配置:

代码语言:javascript
复制
sudo systemctl daemon-reload

启动服务:

代码语言:javascript
复制
sudo systemctl start edge-mqtt-forwarder.service

查看状态:

代码语言:javascript
复制
sudo systemctl status edge-mqtt-forwarder.service

设置开机自启动:

代码语言:javascript
复制
sudo systemctl enable edge-mqtt-forwarder.service

也可以使用一条命令同时设置开机启动并立即启动:

代码语言:javascript
复制
sudo systemctl enable --now edge-mqtt-forwarder.service

检查是否已经设置为开机启动:

代码语言:javascript
复制
systemctl is-enabled edge-mqtt-forwarder.service

检查当前是否正在运行:

代码语言:javascript
复制
systemctl is-active edge-mqtt-forwarder.service

十、start 和 enable 的区别

这是初次使用 systemd 时最常见的问题。

代码语言:javascript
复制
sudo systemctl start example.service

表示立即启动服务,但重启系统后不一定自动运行。

代码语言:javascript
复制
sudo systemctl enable example.service

表示建立开机启动关系,但不会保证服务当前立即启动。

因此,通常使用:

代码语言:javascript
复制
sudo systemctl enable --now example.service

执行 enable 后,systemd 通常会建立类似下面的符号链接:

代码语言:javascript
复制
/etc/systemd/system/multi-user.target.wants/example.service
    -> /etc/systemd/system/example.service

这意味着启动进入 multi-user.target 时,会将该服务一起拉起。

查看启用关系:

代码语言:javascript
复制
ls -l /etc/systemd/system/multi-user.target.wants/

取消开机启动:

代码语言:javascript
复制
sudo systemctl disable example.service

取消开机启动并立即停止:

代码语言:javascript
复制
sudo systemctl disable --now example.service

十一、使用 systemd 编排多个服务

假设系统包含三个程序:

代码语言:javascript
复制
MQTT Broker
    ↓
本地算法 API
    ↓
MQTT 配置转发程序

其中算法 API 的服务名为:

代码语言:javascript
复制
algorithm-api.service

MQTT 转发程序必须在算法 API 启动后运行,可以这样配置:

代码语言:javascript
复制
[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。

1. 创建自定义 target

创建:

代码语言:javascript
复制
sudo vim /etc/systemd/system/edge-platform.target

内容如下:

代码语言:javascript
复制
[Unit]
Description=Edge Computing Platform
Wants=algorithm-api.service edge-mqtt-forwarder.service
After=network-online.target
AllowIsolate=no

[Install]
WantedBy=multi-user.target

启用整个业务服务组:

代码语言:javascript
复制
sudo systemctl daemon-reload
sudo systemctl enable --now edge-platform.target

查看依赖关系:

代码语言:javascript
复制
systemctl list-dependencies edge-platform.target

这样可以使用统一入口管理一组服务:

代码语言:javascript
复制
sudo systemctl start edge-platform.target
sudo systemctl stop edge-platform.target
sudo systemctl restart edge-platform.target

需要注意,停止 target 时是否同时停止所有成员,还取决于成员服务的依赖和归属配置。如果要求服务生命周期严格跟随 target,可以在服务的 [Unit] 中增加:

代码语言:javascript
复制
PartOf=edge-platform.target

例如:

代码语言:javascript
复制
[Unit]
Description=Edge MQTT Configuration Forwarder
PartOf=edge-platform.target
After=algorithm-api.service
Requires=algorithm-api.service

十二、等待网络真正可用

下面的配置很常见:

代码语言:javascript
复制
Wants=network-online.target
After=network-online.target

但 network-online.target 是否真正等待网络可用,还取决于系统使用的网络管理器。

如果使用 NetworkManager:

代码语言:javascript
复制
sudo systemctl enable NetworkManager-wait-online.service

如果使用 systemd-networkd:

代码语言:javascript
复制
sudo systemctl enable systemd-networkd-wait-online.service

即便如此,外部 MQTT Broker、DNS 或互联网仍可能暂时不可用。因此,联网程序最好自身实现断线重连,不能只依赖 systemd 的启动顺序。


十三、程序异常退出后自动重启

常用配置如下:

代码语言:javascript
复制
Restart=on-failure
RestartSec=5

Restart 可选值:

配置

含义

no

不自动重启,默认值

on-success

正常退出时重启

on-failure

异常退出、信号终止或超时时重启

on-abnormal

由信号、超时等异常原因退出时重启

on-abort

因未捕获信号退出时重启

always

无论退出原因都重启

对于普通后台服务,通常推荐:

代码语言:javascript
复制
Restart=on-failure
RestartSec=5

为了避免程序不断崩溃形成无限重启循环,还可以设置启动频率限制:

代码语言:javascript
复制
StartLimitIntervalSec=60
StartLimitBurst=10

表示 60 秒内最多尝试启动 10 次。

修复问题后,如果服务因为触发频率限制而无法重新启动,可以执行:

代码语言:javascript
复制
sudo systemctl reset-failed edge-mqtt-forwarder.service
sudo systemctl restart edge-mqtt-forwarder.service

十四、使用 journalctl 查看日志

systemd 服务默认可以将标准输出和标准错误交给 journald。

实时查看服务日志:

代码语言:javascript
复制
journalctl -u edge-mqtt-forwarder.service -f

查看最近 100 行:

代码语言:javascript
复制
journalctl -u edge-mqtt-forwarder.service -n 100

查看本次启动以来的日志:

代码语言:javascript
复制
journalctl -u edge-mqtt-forwarder.service -b

查看上一次启动的日志:

代码语言:javascript
复制
journalctl -u edge-mqtt-forwarder.service -b -1

查看指定时间之后的日志:

代码语言:javascript
复制
journalctl -u edge-mqtt-forwarder.service --since "2026-09-21 08:00:00"

只查看错误级别:

代码语言:javascript
复制
journalctl -u edge-mqtt-forwarder.service -p err

查看日志占用空间:

代码语言:javascript
复制
journalctl --disk-usage

十五、将 Shell 启动脚本改造成 systemd 服务

有些程序原来通过下面的脚本启动:

代码语言:javascript
复制
#!/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 只是固定等待,不能证明依赖服务已经就绪
  • systemd 无法直接追踪真正的 Python 主进程
  • 日志需要打开终端才能查看
  • 程序退出后不能可靠地自动恢复

可以直接转换为:

代码语言:javascript
复制
[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

保存为:

代码语言:javascript
复制
/etc/systemd/system/websocket.service

然后执行:

代码语言:javascript
复制
sudo systemctl daemon-reload
sudo systemctl enable --now websocket.service
sudo systemctl status websocket.service

查看日志:

代码语言:javascript
复制
journalctl -u websocket.service -f

这样就不再需要 autoExec.sh、gnome-terminal 和固定的 sleep 5。


十六、ExecStart 的常见限制

ExecStart 默认不是由 Bash 解释执行的。

下面的写法通常不能按预期工作:

代码语言:javascript
复制
ExecStart=cd /home/cat/app && python3 main.py

应该写成:

代码语言:javascript
复制
WorkingDirectory=/home/cat/app
ExecStart=/usr/bin/python3 /home/cat/app/main.py

下面的 Shell 语法也不能直接使用:

代码语言:javascript
复制
ExecStart=source env.sh
ExecStart=python3 main.py | tee app.log
ExecStart=echo $HOME
ExecStart=~/venv/bin/python main.py

如果确实必须执行复杂 Shell 命令,应显式调用 Shell:

代码语言:javascript
复制
ExecStart=/bin/bash -lc 'source /opt/app/env.sh && exec python3 /opt/app/main.py'

不过,更推荐使用:

  • WorkingDirectory 设置工作目录
  • Environment 设置单个环境变量
  • EnvironmentFile 加载环境变量文件
  • 直接调用虚拟环境中的解释器
  • 使用 journald 收集日志

另外,一个普通服务只能有一个非空的 ExecStart。如果要连续执行准备命令,可以使用:

代码语言:javascript
复制
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

停止后需要执行清理操作时,可以使用:

代码语言:javascript
复制
ExecStopPost=/usr/local/bin/cleanup

十七、使用 drop-in 覆盖配置

不要为了修改软件包自带服务而直接编辑原文件。

执行:

代码语言:javascript
复制
sudo systemctl edit example.service

systemd 会创建类似下面的覆盖文件:

代码语言:javascript
复制
/etc/systemd/system/example.service.d/override.conf

例如修改重启策略:

代码语言:javascript
复制
[Service]
Restart=always
RestartSec=10

如果要覆盖原来的 ExecStart,必须先将其清空:

代码语言:javascript
复制
[Service]
ExecStart=
ExecStart=/opt/new-env/bin/python /opt/app/main.py

保存后执行:

代码语言:javascript
复制
sudo systemctl daemon-reload
sudo systemctl restart example.service

查看最终合并后的配置:

代码语言:javascript
复制
systemctl cat example.service

十八、常用 systemctl 命令

代码语言:javascript
复制
# 启动服务
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

十九、常见故障及排查方法

1. 修改服务文件后没有生效

原因通常是没有重新加载配置。

代码语言:javascript
复制
sudo systemctl daemon-reload
sudo systemctl restart example.service

2. 手动运行正常,systemd 启动失败

常见原因:

  • systemd 的环境变量与终端不同
  • 使用了相对路径
  • 工作目录不正确
  • systemd 运行用户没有文件权限
  • Python 虚拟环境路径错误
  • 程序依赖当前用户的 Shell 初始化文件

排查:

代码语言:javascript
复制
systemctl status example.service
journalctl -u example.service -n 100 --no-pager
systemctl show example.service

3. 出现 status=203/EXEC

一般表示 ExecStart 指定的文件无法执行。

检查:

代码语言:javascript
复制
ls -l /实际/程序/路径
file /实际/程序/路径

重点确认:

  • 路径是否存在
  • 是否有执行权限
  • 脚本第一行解释器是否正确
  • 是否使用了 Windows 的 CRLF 换行符
  • Python 解释器路径是否真实存在

4. 服务启动后立即退出

systemd 应管理一个持续运行的前台进程。如果程序自行转入后台,systemd 可能认为主进程已经退出。

优先让程序以前台模式运行,并使用:

代码语言:javascript
复制
Type=simple

5. 找不到环境变量

systemd 默认不会读取用户的 .bashrc 或 .profile。

应该显式配置:

代码语言:javascript
复制
Environment="APP_ENV=production"
EnvironmentFile=/etc/default/example

6. 相对路径导致文件写错位置

例如 Python 中使用:

代码语言:javascript
复制
Path("forwarder_state.json")

它会相对于 WorkingDirectory 解析。

更稳妥的方式是通过环境变量传入绝对路径:

代码语言:javascript
复制
Environment="STATE_PATH=/var/lib/example/forwarder_state.json"

7. 网络服务启动太早

可以添加:

代码语言:javascript
复制
Wants=network-online.target
After=network-online.target

同时确保对应的 wait-online 服务已经启用,并让应用自身具备断线重连能力。

8. 服务频繁失败后不再重启

查看状态:

代码语言:javascript
复制
systemctl status example.service

清除失败计数:

代码语言:javascript
复制
sudo systemctl reset-failed example.service
sudo systemctl restart example.service

二十、验证 Unit 文件是否正确

systemd 提供了 Unit 文件检查工具:

代码语言:javascript
复制
systemd-analyze verify /etc/systemd/system/edge-mqtt-forwarder.service

查看系统启动耗时:

代码语言:javascript
复制
systemd-analyze

查看启动最慢的服务:

代码语言:javascript
复制
systemd-analyze blame

查看关键启动链:

代码语言:javascript
复制
systemd-analyze critical-chain

查看指定服务的启动链:

代码语言:javascript
复制
systemd-analyze critical-chain edge-mqtt-forwarder.service

这些命令对于排查“为什么程序开机后很久才启动”非常有用。


二十一、推荐的生产环境配置原则

编写 systemd 服务时,建议遵循以下原则:

  1. 使用绝对路径,不依赖当前目录。
  2. 使用 WorkingDirectory 明确工作目录。
  3. 直接调用虚拟环境中的 Python,不依赖 conda activate。
  4. 不要依赖 gnome-terminal 等图形界面程序。
  5. 使用非 root 用户运行普通业务服务。
  6. 通过 EnvironmentFile 管理运行参数。
  7. 为长期运行的服务设置合理的重启策略。
  8. 使用 journald 统一管理日志。
  9. 使用 After 配置顺序,使用 Wants 或 Requires 配置依赖。
  10. 不要用固定 sleep 代替服务就绪检查。
  11. 联网程序必须具备超时、重试和断线重连机制。
  12. 修改 Unit 后执行 systemctl daemon-reload。
  13. 上线前执行 systemd-analyze verify。
  14. 使用 systemctl enable --now 同时完成启用和启动。

总结

systemd 不只是一个“开机启动工具”,它还是 Linux 中完整的服务编排和进程管理框架。

实现一个可靠的开机自启动服务,核心流程只有四步:

代码语言:javascript
复制
# 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

真正需要重点理解的是:

代码语言:javascript
复制
After/Before:控制启动顺序
Wants/Requires:控制依赖关系
WantedBy:决定 enable 时挂载到哪个 target
Restart:决定程序退出后的恢复策略
journalctl:负责统一查看服务日志

掌握这些概念后,就可以使用 systemd 管理 Python 服务、MQTT 客户端、WebSocket 服务、算法程序以及多个相互依赖的边缘计算组件,获得比启动脚本更稳定、更透明、更容易维护的运行方式。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • Systemd 详解:如何编排 Linux 启动服务并实现开机自启动
    • 前言
    • 一、systemd 是什么
    • 二、systemd Unit 类型
    • 三、Unit 文件存放位置及优先级
    • 四、systemd 如何组织系统启动流程
    • 五、启动顺序和依赖关系不是一回事
      • 1. After 和 Before
      • 2. Wants
      • 3. Requires
      • 4. BindsTo
      • 5. 常见组合
    • 六、Service 文件的基本结构
      • [Unit]
      • [Service]
      • [Install]
    • 七、Service 的 Type 类型
      • Type=simple
      • Type=exec
      • Type=forking
      • Type=oneshot
      • Type=notify
    • 八、实战:将 Python MQTT 程序设置为开机自启动
      • 1. 创建环境变量文件
      • 2. 创建 systemd 服务
    • 九、加载服务并设置开机自启动
    • 十、start 和 enable 的区别
    • 十一、使用 systemd 编排多个服务
      • 1. 创建自定义 target
    • 十二、等待网络真正可用
    • 十三、程序异常退出后自动重启
    • 十四、使用 journalctl 查看日志
    • 十五、将 Shell 启动脚本改造成 systemd 服务
    • 十六、ExecStart 的常见限制
    • 十七、使用 drop-in 覆盖配置
    • 十八、常用 systemctl 命令
    • 十九、常见故障及排查方法
      • 1. 修改服务文件后没有生效
      • 2. 手动运行正常,systemd 启动失败
      • 3. 出现 status=203/EXEC
      • 4. 服务启动后立即退出
      • 5. 找不到环境变量
      • 6. 相对路径导致文件写错位置
      • 7. 网络服务启动太早
      • 8. 服务频繁失败后不再重启
    • 二十、验证 Unit 文件是否正确
    • 二十一、推荐的生产环境配置原则
    • 总结
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档