Systemd 服务管理深度解析:从核心概念到网络依赖服务实战

前言

在现代 Linux 系统中,Systemd 已成为事实上的 init 系统和服务管理器,取代了传统的 System V init。无论是桌面系统还是服务器环境,理解 Systemd 的工作原理和服务编写方法,都是 Linux 运维人员的必备技能。本文将从 Systemd 的核心概念讲起,深入解析服务单元文件的编写规范,并重点介绍如何创建一个依赖网络就绪的服务------以 iSCSI 磁盘的发现与挂载为例。

一、Systemd 核心概念

1.1 什么是 Systemd

Systemd 是 Linux 操作系统的系统和服务管理器。当它作为启动时的第一个进程(PID 1)运行时,充当 init 系统,负责启动和维护用户空间服务。

Systemd 的核心设计理念包括:

  • 并行化启动:尽可能并行启动服务,显著缩短系统启动时间
  • 依赖关系管理:通过声明式配置确保服务以正确的顺序启动和停止
  • 统一日志:通过 journald 提供集中式日志记录
  • CGroup 跟踪:使用 Linux 控制组精确管理进程生命周期和资源

1.2 Unit(单元)的概念

Systemd 将系统资源抽象为"Unit"(单元)。Unit 是 Systemd 知道如何操作和管理的资源对象,通过配置文件(称为单元文件)来定义。

Systemd 支持多种 Unit 类型:

Unit 类型 后缀 用途
Service .service 封装守护进程的启动、停止、重启------最常用
Target .target 将多个 Unit 分组,提供同步点(如 multi-user.target
Socket .socket 管理网络套接字,支持按需激活
Mount .mount 处理文件系统挂载点
Timer .timer 替代 cron 的定时任务
Device .device 暴露内核设备,支持基于设备的激活
Path .path 监控目录或文件变化并触发其他 Unit

1.3 Target(目标)与启动级别

Systemd 使用 Target 替代了 SysV init 的运行级别(runlevel)概念。Target 是一组 Unit 的逻辑集合,代表系统的某个状态。常见的 Target 包括:

  • multi-user.target:多用户模式(对应 runlevel 3)
  • graphical.target:图形界面模式(对应 runlevel 5)
  • default.target:系统默认启动目标

二、Service Unit 文件编写详解

2.1 文件结构与存放位置

一个 Service Unit 文件由三个段落组成:

  • [Unit]:描述与依赖关系
  • [Service]:进程启动方式、运行用户、工作目录、资源限制
  • [Install]:控制开机自启时挂载到哪个 Target

Unit 文件的存放位置:

  • /etc/systemd/system/自定义配置(优先级最高)
  • /lib/systemd/system/:软件包提供的默认配置

文件权限 :确保文件属主为 root:root,权限为 644

2.2 Unit 段:描述与依赖

ini 复制代码
[Unit]
Description=My Custom Service
After=network.target
Requires=mysql.service
Wants=network-online.target

关键指令说明:

指令 含义
Description 服务的描述信息
After= 排序约束:本服务 指定服务之后启动
Before= 排序约束:本服务 指定服务之前启动
Requires= 强依赖:依赖目标失败则本服务也失败
Wants= 弱依赖:依赖目标失败本服务仍启动

重要原则After/Before 仅控制启动顺序 ,不建立依赖关系。如果需要依赖关系,必须配合 RequiresWants 使用。

2.3 Service 段:运行配置

ini 复制代码
[Service]
Type=simple
User=appuser
Group=appgroup
WorkingDirectory=/opt/myapp
ExecStart=/usr/bin/my_daemon --config /etc/my.conf
ExecStop=/bin/kill -TERM $MAINPID
Restart=on-failure
RestartSec=5s

Type(服务类型)

Type 说明 适用场景
simple 默认值,ExecStart 启动的进程本身就是服务主进程 大多数前台运行的程序
forking 程序会 fork 子进程后父进程退出 传统守护进程(需配合 PIDFile=
oneshot 一次性任务,执行完即退出 初始化脚本
notify 通过 sd_notify() 通知 systemd 已就绪 Nginx、PostgreSQL 等
idle 在所有其他任务完成后才启动 避免 CPU/IO 竞争

Restart(重启策略)

策略 含义
no 不自动重启(默认)
on-failure 非零退出码时重启------生产环境推荐
always 无条件重启
on-success 零退出码时重启
on-abnormal 信号/超时/看门狗异常时重启

资源限制

ini 复制代码
MemoryMax=512M
CPUQuota=80%
LimitNOFILE=65535

2.4 Install 段:开机自启

ini 复制代码
[Install]
WantedBy=multi-user.target

WantedBy 指定服务应挂载到哪个 Target 下。当执行 systemctl enable 时,systemd 会在对应 Target 的 .wants/ 目录下创建符号链接。

三、网络依赖:network.target vs network-online.target

这是编写网络相关服务时最容易混淆的地方,值得单独详细讲解。

3.1 network.target

network.target 表示网络管理堆栈已经启动 。但到达此目标并不意味着任何网络接口已配置完成

主要用途:在关机时确保网络在服务停止后才断开。

正确用法

ini 复制代码
[Unit]
After=network.target

不要 添加 Wants=network.targetRequires=network.target

3.2 network-online.target

network-online.target 是一个主动等待的目标,直到网络真正"在线"(通常指已配置可路由的 IP 地址)。

主要用途延迟服务启动,直到网络完全就绪。

正确用法(两者缺一不可):

ini 复制代码
[Unit]
After=network-online.target
Wants=network-online.target

为什么不建议滥用 network-online.target? 官方文档强烈建议不要过于随意地使用它。网络服务端软件(如 Web 服务器)通常不应依赖它,因为服务器软件即使在外网 IP 未就绪时也能接受本地连接。它主要适用于没有网络就无法工作的客户端软件

3.3 两者对比

特性 network.target network-online.target
含义 网络栈已启动 网络已在线(有 IP)
启动时 不等待网络配置 等待网络配置完成
关机时 反向顺序,确保网络最后关 同左
适用场景 通用依赖,关机顺序控制 必须网络就绪才能工作的服务
推荐使用 大多数服务 网络客户端、远程存储挂载

四、实战:依赖网络就绪的 iSCSI 磁盘挂载服务

4.1 场景需求

某服务器需要通过 iSCSI 连接远程存储,执行以下操作:

  1. 发现 iSCSI 目标:iscsiadm -m discovery -t sendtargets -p <IP>
  2. 登录 iSCSI 目标
  3. 挂载发现的磁盘到指定目录

要求:必须在网络完全就绪后才能执行这些操作。

4.2 iSCSI 相关命令速览

发现目标

bash 复制代码
iscsiadm -m discovery -t sendtargets -p 192.168.1.100

登录目标

bash 复制代码
iscsiadm -m node -T <target_name> -p <IP> -l

挂载磁盘

bash 复制代码
mount /dev/sdb1 /mnt/iscsi_data

4.3 方案一:编写自定义 Service(推荐)

创建服务文件 /etc/systemd/system/iscsi-mount.service

ini 复制代码
[Unit]
Description=iSCSI Target Discovery and Mount
# 关键:依赖网络在线
After=network-online.target
Wants=network-online.target
# 确保在远程文件系统目标之前完成
Before=remote-fs.target

[Service]
Type=oneshot
RemainAfterExit=yes
# 使用 bash 执行多个命令
ExecStart=/bin/bash -c '\
    iscsiadm -m discovery -t sendtargets -p 192.168.1.100 && \
    iscsiadm -m node -T iqn.2024-01.com.example:storage -p 192.168.1.100 -l && \
    sleep 2 && \
    mount /dev/sdb1 /mnt/iscsi_data \
'
ExecStop=/bin/bash -c '\
    umount /mnt/iscsi_data && \
    iscsiadm -m node -T iqn.2024-01.com.example:storage -p 192.168.1.100 -u \
'
Restart=no

[Install]
WantedBy=multi-user.target

关键点解析

  • Type=oneshot:一次性任务,适合执行命令序列
  • RemainAfterExit=yes:退出后仍视为 active 状态
  • After=network-online.target + Wants=network-online.target:确保网络真正就绪
  • Before=remote-fs.target:在远程文件系统目标之前完成

4.4 方案二:拆分多个 Service(更优雅)

将发现、登录、挂载拆分为多个服务,便于管理和调试:

1. 发现服务 /etc/systemd/system/iscsi-discovery.service

ini 复制代码
[Unit]
Description=iSCSI Target Discovery
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot
ExecStart=/usr/bin/iscsiadm -m discovery -t sendtargets -p 192.168.1.100

[Install]
WantedBy=multi-user.target

2. 登录服务 /etc/systemd/system/iscsi-login.service

ini 复制代码
[Unit]
Description=iSCSI Target Login
After=iscsi-discovery.service
Requires=iscsi-discovery.service

[Service]
Type=oneshot
ExecStart=/usr/bin/iscsiadm -m node -T iqn.2024-01.com.example:storage -p 192.168.1.100 -l

[Install]
WantedBy=multi-user.target

3. 挂载服务 /etc/systemd/system/iscsi-mount.service

ini 复制代码
[Unit]
Description=iSCSI Mount
After=iscsi-login.service
Requires=iscsi-login.service

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/bin/mount /dev/sdb1 /mnt/iscsi_data
ExecStop=/usr/bin/umount /mnt/iscsi_data

[Install]
WantedBy=multi-user.target

4.5 方案三:利用 /etc/fstab + _netdev 选项

对于简单的挂载需求,可以直接在 /etc/fstab 中使用 _netdev 选项:

复制代码
/dev/sdb1  /mnt/iscsi_data  ext4  defaults,_netdev  0  0

_netdev 选项告诉 systemd 这是一个网络设备,系统会自动为其添加对 network-online.target 的依赖。但这种方法无法处理发现和登录的复杂逻辑。

4.6 部署与验证

bash 复制代码
# 1. 创建服务文件后,重载 systemd 配置
sudo systemctl daemon-reload

# 2. 启动服务测试
sudo systemctl start iscsi-mount.service

# 3. 查看状态
sudo systemctl status iscsi-mount.service

# 4. 设置开机自启
sudo systemctl enable iscsi-mount.service

# 5. 查看日志
sudo journalctl -u iscsi-mount.service -f

五、编写 Service 文件的最佳实践与注意事项

5.1 依赖关系设计

  1. 业务服务通常用 Wants 而非 Requires:避免因依赖失败导致连锁故障
  2. 网络依赖选 network-online.target:对于真正需要网络就绪的服务
  3. 避免依赖循环:严禁 A 依赖 B 且 B 依赖 A,systemd 会报错退出

5.2 启动类型选择

  • 不确定时用 simple:这是默认值,适合大多数场景
  • 传统守护进程用 forking :务必配合 PIDFile=
  • 一次性任务用 oneshot :配合 RemainAfterExit=yes 使用

5.3 重启策略

  • 生产环境用 Restart=on-failure
  • 配合 StartLimitBurstStartLimitIntervalSec:限制重启频率,防止疯狂循环
ini 复制代码
Restart=on-failure
RestartSec=5s
StartLimitBurst=5
StartLimitIntervalSec=60s

5.4 安全与权限

  1. 使用 User=Group= 降权运行
  2. 避免使用 root 运行非必要服务
  3. 使用 PrivateTmp= 隔离临时目录
  4. 文件权限 644,属主 root:root

5.5 调试与故障排查

命令 用途
systemctl status <service> 查看服务状态和最近日志
journalctl -u <service> -f 实时跟踪服务日志
systemctl list-dependencies <service> 查看服务的依赖关系
systemctl cat <service> 查看服务文件内容
systemctl edit <service> 创建 drop-in 覆盖配置

5.6 常见陷阱

  1. 修改服务文件后忘记 systemctl daemon-reload
  2. 混淆 network.targetnetwork-online.target
  3. Type 写错导致 systemd 误判主进程状态
  4. ExecStart 中使用相对路径------务必使用绝对路径
  5. 忽略日志 ------用 journalctl 而不是猜测问题原因

六、总结

Systemd 作为现代 Linux 系统的核心组件,其设计围绕 Unit 抽象声明式依赖并行启动三大支柱展开。掌握 Service Unit 文件的编写,是 Linux 运维的核心技能之一。

关键 takeaways:

  1. Unit 是核心抽象:所有系统资源都通过 Unit 文件定义和管理
  2. 依赖与顺序分离After/Before 控制顺序,Requires/Wants 控制依赖
  3. 网络依赖选对 Targetnetwork.target 用于关机顺序,network-online.target 用于启动等待
  4. Type 和 Restart 选对:直接影响服务的启动行为和可靠性
  5. iSCSI 等远程存储 :务必使用 network-online.target 确保网络就绪后再操作

通过本文的讲解和实战案例,相信读者已经能够独立编写符合生产环境要求的 Systemd 服务文件,并正确处理网络依赖等复杂场景。