前言
在现代 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仅控制启动顺序 ,不建立依赖关系。如果需要依赖关系,必须配合Requires或Wants使用。
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.target 或 Requires=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 连接远程存储,执行以下操作:
- 发现 iSCSI 目标:
iscsiadm -m discovery -t sendtargets -p <IP> - 登录 iSCSI 目标
- 挂载发现的磁盘到指定目录
要求:必须在网络完全就绪后才能执行这些操作。
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 依赖关系设计
- 业务服务通常用
Wants而非Requires:避免因依赖失败导致连锁故障 - 网络依赖选
network-online.target:对于真正需要网络就绪的服务 - 避免依赖循环:严禁 A 依赖 B 且 B 依赖 A,systemd 会报错退出
5.2 启动类型选择
- 不确定时用
simple:这是默认值,适合大多数场景 - 传统守护进程用
forking:务必配合PIDFile= - 一次性任务用
oneshot:配合RemainAfterExit=yes使用
5.3 重启策略
- 生产环境用
Restart=on-failure - 配合
StartLimitBurst和StartLimitIntervalSec:限制重启频率,防止疯狂循环
ini
Restart=on-failure
RestartSec=5s
StartLimitBurst=5
StartLimitIntervalSec=60s
5.4 安全与权限
- 使用
User=和Group=降权运行 - 避免使用 root 运行非必要服务
- 使用
PrivateTmp=隔离临时目录 - 文件权限
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 常见陷阱
- 修改服务文件后忘记
systemctl daemon-reload - 混淆
network.target和network-online.target - Type 写错导致 systemd 误判主进程状态
- ExecStart 中使用相对路径------务必使用绝对路径
- 忽略日志 ------用
journalctl而不是猜测问题原因
六、总结
Systemd 作为现代 Linux 系统的核心组件,其设计围绕 Unit 抽象 、声明式依赖 和并行启动三大支柱展开。掌握 Service Unit 文件的编写,是 Linux 运维的核心技能之一。
关键 takeaways:
- Unit 是核心抽象:所有系统资源都通过 Unit 文件定义和管理
- 依赖与顺序分离 :
After/Before控制顺序,Requires/Wants控制依赖 - 网络依赖选对 Target :
network.target用于关机顺序,network-online.target用于启动等待 - Type 和 Restart 选对:直接影响服务的启动行为和可靠性
- iSCSI 等远程存储 :务必使用
network-online.target确保网络就绪后再操作
通过本文的讲解和实战案例,相信读者已经能够独立编写符合生产环境要求的 Systemd 服务文件,并正确处理网络依赖等复杂场景。