09 - systemd 服务管理

systemd 是现代 Linux 发行版的事实标准初始化系统,作为 PID 1 运行,负责启动、管理并监督系统中的所有服务。本章从基础到进阶,覆盖 systemd 的完整使用体系。


9.1 systemd 是什么

systemd 是内核启动后执行的第一个用户空间进程(PID 1),它的核心职责是:

  • 系统初始化:按依赖关系并行启动所有系统服务
  • 服务监督:监控服务运行状态,支持自动重启
  • 资源管理:通过 cgroup v2 对服务进行 CPU、内存、IO 限制
  • 日志系统:journald 统一收集所有服务的输出
  • 定时任务:systemd timer 替代传统 cron

几乎所有主流发行版均已采用 systemd:Debian/Ubuntu(2015 年起)、Fedora/RHEL(2011 年起)、Arch Linux(2012 年起)、openSUSE(2011 年起)。

graph TD
 KERNEL[Linux 内核] --> INITRAMFS[initramfs]
 INITRAMFS --> PID1["systemd (PID 1)"]
 PID1 --> JOURN["systemd-journald<br/>日志"]
 PID1 --> LOGIN["systemd-logind<br/>会话管理"]
 PID1 --> RESOLV["systemd-resolved<br/>DNS 解析"]
 PID1 --> NETWRK["systemd-networkd<br/>网络管理"]
 PID1 --> UDEV["systemd-udevd<br/>设备管理"]
 PID1 --> TIMER["systemd timer<br/>定时任务"]
 PID1 --> CRASH["systemd-coredump<br/>崩溃转储"]
 PID1 --> OOM["systemd-oomd<br/>内存压力管理"]

systemd 组件一览:

组件守护进程职责
PID 1systemd系统与服务管理器
日志systemd-journald二进制日志收集与存储
会话systemd-logind用户登录与会话管理
DNSsystemd-resolved域名解析缓存
网络systemd-networkd网络接口配置
设备systemd-udevd设备热插拔管理
临时文件systemd-tmpfiles临时目录创建与清理
用户创建systemd-sysusers声明式系统用户管理
容器systemd-nspawn轻量级容器(增强 chroot)
引导systemd-bootUEFI 引导管理器
崩溃systemd-coredump核心转储收集与查询
内存systemd-oomdOOM 守护进程

9.2 systemctl 基本操作

systemctl 是管理 systemd 系统和服务的主要命令。

# --- 服务启停 ---
sudo systemctl start nginx.service # 启动
sudo systemctl stop nginx.service # 停止
sudo systemctl restart nginx.service # 重启
sudo systemctl reload nginx.service # 重载配置(不中断服务)
sudo systemctl try-restart nginx.service # 仅在已运行时重启
 
# --- 开机自启 ---
sudo systemctl enable nginx.service # 设为开机自启
sudo systemctl disable nginx.service # 取消开机自启
sudo systemctl enable --now nginx # 启用并立即启动
sudo systemctl disable --now nginx # 禁用并立即停止
 
# --- 状态查询 ---
systemctl status nginx.service # 完整状态(推荐)
systemctl is-active nginx.service # 是否正在运行
systemctl is-enabled nginx.service # 是否开机自启
systemctl is-failed nginx.service # 是否处于失败状态
 
# --- 列出 Unit ---
systemctl list-units # 列出所有已加载的 Unit
systemctl list-units --type=service # 只列 service 类型
systemctl list-units --type=service --state=running # 正在运行的
systemctl list-units --failed # 处于失败状态的
 
# --- 列出 Unit 文件(包括未加载的) ---
systemctl list-unit-files
systemctl list-unit-files --type=service

mask 与 unmask

maskdisable 更强硬——它将 Unit 链接到 /dev/null,使其完全无法启动(包括作为依赖被间接拉起):

sudo systemctl mask NetworkManager.service
# 等价于: ln -s /dev/null /etc/systemd/system/NetworkManager.service
 
sudo systemctl unmask NetworkManager.service # 取消屏蔽

辅助命令

# 查看 Unit 文件完整内容(含所有 drop-in 覆盖)
systemctl cat nginx.service
 
# 以机器可读格式查看属性
systemctl show nginx.service
systemctl show -p MainPID nginx.service
systemctl show -p MemoryCurrent nginx.service
 
# 查看依赖关系
systemctl list-dependencies nginx.service
systemctl list-dependencies --reverse nginx.service

9.3 Unit 文件搜索路径与优先级

systemd 从多个目录加载 Unit 文件,优先级从高到低:

优先级路径用途
1(最高)/etc/systemd/system/管理员自定义
2/run/systemd/system/运行时自动生成
3(最低)/usr/lib/systemd/system/软件包安装的默认配置

用户级 Unit 文件路径(systemctl --user):

优先级路径
1~/.config/systemd/user/
2/etc/systemd/user/
3/usr/lib/systemd/user/

规则:同名 Unit 文件,高优先级目录的版本会覆盖低优先级。例如,如果你在 /etc/systemd/system/nginx.service 中放置了修改版,它将替代 /usr/lib/systemd/system/ 中软件包自带的版本。


9.4 Unit 类型详解

类型后缀说明
service.service系统服务或守护进程
socket.socketIPC 套接字、网络端口,支持按需启动
target.target一组 Unit 的同步点(替代 SysV runlevel)
timer.timer定时触发任务(替代 cron)
mount.mount文件系统挂载点
automount.automount按需自动挂载
device.device硬件设备(由 udev 自动生成)
slice.slicecgroup 资源分组
scope.scope外部创建的进程组
swap.swap交换分区/文件
path.path基于文件系统事件的触发

常用 Target

Target 是 systemd 的”运行级别”机制,比传统 SysV init 的 runlevel 更灵活:

# 查看当前默认 target
systemctl get-default
 
# 设置默认 target
sudo systemctl set-default multi-user.target # 命令行模式
sudo systemctl set-default graphical.target # 图形界面
 
# 临时切换 target
sudo systemctl isolate multi-user.target
sudo systemctl isolate rescue.target # 救援模式(单用户)
sudo systemctl isolate emergency.target # 紧急模式(最小环境)
Target对应功能类似 SysV
graphical.target完整图形界面runlevel 5
multi-user.target多用户命令行runlevel 3
rescue.target单用户恢复模式runlevel 1
emergency.target紧急 shell(最小化)
poweroff.target关机runlevel 0
reboot.target重启runlevel 6
network-online.target网络已完全就绪
timers.target所有 timer 的汇集点
sockets.target所有 socket 的汇集点

9.5 编写 .service 文件

[Unit] 段——基本信息与依赖

[Unit]
Description=我的应用程序
Documentation=https://example.com/docs
Documentation=man:myapp(8)
 
# 继承需在指定 Unit 之后启动
After=network-online.target postgresql.service
Before=nginx.service
 
# 强依赖:被依赖项失败则本 Unit 也失败
Requires=postgresql.service
 
# 弱依赖:一起启动,但被依赖项失败不影响本 Unit
Wants=redis.service
 
# 绑定:被依赖项停止,本 Unit 也停止
BindsTo=dev-sda1.device
 
# 冲突:不能同时运行
Conflicts=iptables.service
 
# 条件检查(不满足则跳过,不报错)
ConditionPathExists=/etc/myapp/config.toml
ConditionVirtualization=!container
ConditionMemory=>=2G
 
# 断言检查(不满足则报错)
AssertPathExists=/usr/bin/myapp
 
# 启动失败控制
StartLimitIntervalSec=60 # 在此时间窗口内
StartLimitBurst=5 # 最多启动失败次数
StartLimitAction=reboot # 超过后执行的动作
 
# 失败通知
OnFailure=notify-admin@%n.service

[Service] 段——运行定义

Type 参数

Type说明适用场景
simple默认值,ExecStart 进程即主进程大部分现代服务 (Node.js, Go, Python)
forking传统 daemon:父进程 fork 后退出,子进程持续运行传统 C 程序 (nginx, Apache prefork)
oneshot执行完就退出的一次性任务初始化脚本,配合 RemainAfterExit=yes
notify服务通过 sd_notify() 告知 systemd 已就绪支持 systemd 通知协议的服务
dbus服务在 D-Bus 上注册名称后算就绪D-Bus 激活服务
idle等待所有作业完成后才启动延迟到系统空闲
exec类似 simple,但 exec() 成功才算启动需确认二进制可用
[Service]
Type=simple # 服务类型
User=myapp # 运行用户
Group=myapp # 运行组
DynamicUser=yes # 动态创建临时用户(更安全)
WorkingDirectory=/opt/myapp # 工作目录
 
# 启动/停止/重载命令
ExecStart=/usr/bin/myapp serve --config /etc/myapp/config.toml
ExecStop=/bin/kill -s SIGTERM $MAINPID
ExecReload=/bin/kill -s SIGHUP $MAINPID
 
# 启动前/后
ExecStartPre=/usr/bin/myapp --validate-config
ExecStartPost=/usr/local/bin/notify-ready.sh
 
# 超时
TimeoutStartSec=30
TimeoutStopSec=30
 
# 重启策略
Restart=on-failure # 仅异常退出时重启
RestartSec=5 # 重启前等待 5 秒
# 可选值: no, on-success, on-failure, on-abnormal, on-watchdog, on-abort, always
 
# 看门狗(需服务周期性调用 sd_notify(WATCHDOG=1))
WatchdogSec=30
 
# 环境变量
Environment=NODE_ENV=production
EnvironmentFile=/etc/myapp/env # 从文件加载
 
# 标准输出/错误
StandardOutput=journal
StandardError=journal
 
# 资源限制
LimitNOFILE=65536 # 文件描述符上限
LimitNPROC=4096 # 进程数上限
 
# PID 文件(Type=forking 时必需)
PIDFile=/run/myapp.pid
 
# 退出时清理 IPC 资源
RemoveIPC=yes

[Install] 段——安装行为

[Install]
WantedBy=multi-user.target # 被哪个 target 间接拉入
RequiredBy=critical-app.target # 强依赖版本(少用)
Alias=myapp.service # 别名
Also=myapp-worker.service # 同时启用的相关 Unit

完整示例

# /etc/systemd/system/myapp.service
[Unit]
Description=My Web Application
After=network-online.target postgresql.service
Wants=network-online.target
Requires=postgresql.service
 
[Service]
Type=simple
User=myapp
Group=myapp
DynamicUser=yes
WorkingDirectory=/opt/myapp
ExecStart=/usr/bin/myapp serve
ExecReload=/bin/kill -SIGHUP $MAINPID
Restart=on-failure
RestartSec=5
LimitNOFILE=65536
Environment=MYAPP_ENV=production
StandardOutput=journal
StandardError=journal
 
# 安全加固
NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=strict
ProtectHome=yes
ReadWritePaths=/var/lib/myapp
 
[Install]
WantedBy=multi-user.target

Type=forking 示例(传统守护进程)

# /etc/systemd/system/legacy-app.service
[Unit]
Description=Legacy Forking Application
After=network.target
 
[Service]
Type=forking
PIDFile=/run/legacy-app.pid
ExecStart=/usr/sbin/legacy-app --daemon
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
 
[Install]
WantedBy=multi-user.target

Type=oneshot 示例(一次性任务)

# /etc/systemd/system/startup-task.service
[Unit]
Description=Run Once After Boot
 
[Service]
Type=oneshot
RemainAfterExit=yes # 退出后仍视为 active
ExecStart=/usr/local/bin/init.sh
ExecStop=/usr/local/bin/cleanup.sh
 
[Install]
WantedBy=multi-user.target

9.6 重载与 Drop-in 覆盖

修改 Unit 文件后必须重载:

sudo systemctl daemon-reload # 重新读取所有 Unit 文件

Drop-in 覆盖(推荐方式,不修改软件包源文件):

# 创建覆盖文件
sudo systemctl edit nginx.service
# 这会创建 /etc/systemd/system/nginx.service.d/override.conf
 
# 编辑完整 Unit(创建副本,完全替换源文件)
sudo systemctl edit --full nginx.service
 
# 为不存在的 Unit 新建
sudo systemctl edit --force --full my-new-service.service

Drop-in 覆盖示例(只覆盖资源限制):

# /etc/systemd/system/nginx.service.d/override.conf
[Service]
LimitNOFILE=65536
MemoryMax=2G
CPUQuota=200%
 
[Unit]
After=network-online.target

使用 systemctl cat nginx.service 可查看所有片段组合后的完整内容。


9.7 用户级 systemd 服务

以非 root 身份管理用户自己的服务,存储在 ~/.config/systemd/user/

# 用户级命令(不加 sudo,使用 --user)
systemctl --user daemon-reload
systemctl --user status myapp.service
systemctl --user enable --now myapp.service
systemctl --user start myapp.service
 
# 查看用户实例日志
journalctl --user -u myapp.service
# ~/.config/systemd/user/dev-server.service
[Unit]
Description=Development Server
 
[Service]
Type=simple
WorkingDirectory=%h/projects/myapp
ExecStart=/usr/bin/node server.js
Restart=on-failure
Environment=PORT=3000
 
[Install]
WantedBy=default.target

enable-linger

默认情况下,用户级 systemd 实例在用户登录时启动、登出时停止。若需在未登录时保持运行:

sudo loginctl enable-linger alice
# 现在 alice 的服务开机即启动,即使未登录

9.8 journalctl 日志查看

16-日志系统 中有详尽说明,这里列出最常用的 systemd 日志操作:

# 查看所有日志
journalctl
 
# 本次启动日志
journalctl -b
 
# 上次启动日志
journalctl -b -1
 
# 列出所有启动记录
journalctl --list-boots
 
# 按 Unit 过滤
journalctl -u nginx.service
journalctl -u nginx.service -u php-fpm.service
 
# 实时跟踪
journalctl -f
journalctl -f -u nginx.service
 
# 按时间过滤
journalctl --since "2025-01-01 00:00:00"
journalctl --since "1 hour ago"
journalctl --since yesterday --until today
 
# 按优先级
journalctl -p err # 仅错误及以上
journalctl -p warning..err # 警告到错误
 
# 按进程/用户
journalctl _PID=1234
journalctl _UID=1000
 
# 内核消息
journalctl -k
 
# 输出格式
journalctl -o json-pretty # JSON 格式
journalctl -o short-full # 完整时间戳
journalctl -o cat # 仅消息正文
 
# 磁盘管理
journalctl --disk-usage # 查看占用
sudo journalctl --vacuum-time=30d # 删除 30 天前日志
sudo journalctl --vacuum-size=500M # 限制总大小 500M
 
# 验证日志完整性
journalctl --verify

持久化配置——默认日志存于内存 (/run/log/journal),重启丢失。创建目录以持久化:

sudo mkdir -p /var/log/journal
sudo systemctl restart systemd-journald

或在 /etc/systemd/journald.conf 中设置:

[Journal]
Storage=persistent
Compress=yes
SystemMaxUse=4G
SystemMaxFileSize=128M
MaxRetentionSec=1month

9.9 systemd-analyze 启动分析

systemd-analyze # 总启动时间
systemd-analyze blame # 各服务启动耗时排行
systemd-analyze critical-chain # 关键路径(阻塞链)
systemd-analyze critical-chain nginx.service # 特定服务的关键链
systemd-analyze plot > boot.svg # SVG 启动时序图
 
# 安全审计
systemd-analyze security # 所有服务安全评分
systemd-analyze security nginx.service # 特定服务安全评分
 
# Unit 文件验证
systemd-analyze verify /etc/systemd/system/myapp.service
 
# 日历表达式验证
systemd-analyze calendar "Mon..Fri *-*-* 08:00:00"

9.10 systemd Timer 定时任务

systemd timer 可替代传统 cron,优势:与 journald 集成日志、支持秒级精度、错过执行可补偿、可设置随机延迟。

15-计划任务与自动化 中有完整的 cron 和 systemd timer 对比,这里快速示例:

单调定时器(相对时间)

# /etc/systemd/system/backup.timer
[Unit]
Description=Run Backup Every 6 Hours
 
[Timer]
OnBootSec=15min # 开机后 15 分钟
OnUnitActiveSec=6h # 上次执行后 6 小时
AccuracySec=1min # 触发精度
RandomizedDelaySec=30min # 随机延迟
 
[Install]
WantedBy=timers.target

日历定时器(绝对时间)

# /etc/systemd/system/daily-report.timer
[Unit]
Description=Daily Report at 2:30 AM
 
[Timer]
OnCalendar=*-*-* 02:30:00
Persistent=true # 错过后补偿执行
 
[Install]
WantedBy=timers.target

OnCalendar 语法:

表达式含义
daily / *-*-* 00:00:00每天午夜
hourly / *-*-* *:00:00每小时整点
Mon *-*-* 09:00:00每周一 9 点
*-*-01,15 00:00:00每月 1 日和 15 日
*-*-* *:00/5:00每 5 分钟
Mon..Fri *-*-* 08..18:00:00工作日 8-18 点,每小时
weekly每周日零点
# 查看所有 timer
systemctl list-timers
systemctl list-timers --all

9.11 cgroup v2 资源控制

systemd 通过 cgroup v2 精细控制每个服务的资源使用:

[Service]
# CPU
CPUQuota=150% # 最多使用 1.5 个 CPU 核心
CPUWeight=100 # CPU 权重(默认 100)
CPUAffinity=0-3 # 绑定到 CPU 0-3
AllowedCPUs=0-3 # 限制可用 CPU
 
# 内存
MemoryMax=4G # 硬限制
MemoryHigh=3G # 软上限(超限后逐步回收)
MemoryLow=512M # 低于此值时优先保护
MemorySwapMax=1G # Swap 上限
 
# IO
IOWeight=100 # IO 权重
IOReadBandwidthMax=/dev/sda 100M # 读带宽上限
IOWriteBandwidthMax=/dev/sda 50M # 写带宽上限
 
# 任务数
TasksMax=512 # 最大进程/线程数

运行时修改(无需重启服务):

sudo systemctl set-property nginx.service MemoryMax=8G
sudo systemctl set-property nginx.service CPUQuota=200%
 
# 查看 cgroup 实时状态
systemd-cgtop
systemd-cgls

9.12 安全沙箱选项

systemd 提供了丰富的安全隔离能力,无需额外安装 AppArmor 或 SELinux 即可增强服务安全性:

[Service]
# 文件系统隔离
ProtectSystem=strict # / 和 /usr 只读
ProtectHome=yes # /home、/root、/run/user 不可访问
PrivateTmp=yes # 私有 /tmp 和 /var/tmp
ReadWritePaths=/var/lib/myapp # 在白名单之外唯一可写路径
ReadOnlyPaths=/etc/myapp/config # 指定只读路径
InaccessiblePaths=/mnt/secret # 完全不可见
 
# 权限与能力限制
NoNewPrivileges=yes # 禁止提升权限(最重要)
CapabilityBoundingSet=CAP_NET_BIND_SERVICE # 仅保留绑定特权端口能力
AmbientCapabilities=CAP_NET_BIND_SERVICE
 
# 命名空间隔离
PrivateNetwork=yes # 独立网络命名空间
PrivateDevices=yes # 私有 /dev(仅基本设备)
PrivateUsers=yes # 独立用户命名空间
PrivateIPC=yes # 独立 IPC 命名空间
 
# 内核接口保护
ProtectKernelTunables=yes # /proc/sys 只读
ProtectKernelModules=yes # 禁止加载内核模块
ProtectKernelLogs=yes # 禁止读取内核日志
ProtectControlGroups=yes # cgroup 只读
ProtectHostname=yes # 禁止修改主机名
ProtectClock=yes # 禁止修改系统时钟
 
# 系统调用过滤
SystemCallFilter=@system-service # 允许系统服务常用调用
SystemCallFilter=~@mount @privileged @obsolete # 禁止危险调用
SystemCallArchitectures=native # 仅允许本机架构
 
# SUID/SGID 限制
RestrictSUIDSGID=yes # 禁止创建 SUID/SGID 文件
RestrictNamespaces=yes # 禁止创建新命名空间
RestrictRealtime=yes # 禁止实时调度
LockPersonality=yes # 锁定进程个性
MemoryDenyWriteExecute=yes # W^X 内存保护
RemoveIPC=yes # 退出时清理 System V IPC
 
# 地址族限制
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX

使用安全审计工具检查配置:

systemd-analyze security nginx.service
# 输出安全评分和改进建议

9.13 Socket 激活——按需启动

Socket 激活允许 systemd 预先监听端口/套接字,当有连接时才启动实际服务进程:

# /etc/systemd/system/myapp.socket
[Unit]
Description=MyApp Socket
 
[Socket]
ListenStream=8080 # 监听 TCP 端口
ListenStream=/run/myapp.sock # 监听 Unix 套接字
SocketMode=0660
SocketUser=myapp
SocketGroup=myapp
 
[Install]
WantedBy=sockets.target
# /etc/systemd/system/myapp.service(不设置 WantedBy)
[Unit]
Description=MyApp Service
Requires=myapp.socket
After=myapp.socket
 
[Service]
Type=notify # 或 simple
ExecStart=/usr/bin/myapp
NonBlocking=true
# 服务通过 sd_listen_fds() 或 $LISTEN_FDS 获取文件描述符
# 仅启用 socket,不为 service 设开机自启
sudo systemctl enable --now myapp.socket
 
# 端口由 systemd 监听,首次连接时启动服务

9.14 systemd-run——临时运行

无需编写 Unit 文件,临时创建服务或 scope:

# 作为后台 service 运行
sudo systemd-run --unit=my-task /usr/local/bin/task.sh
 
# 作为 scope 运行(前台)
systemd-run --scope --unit=build-session make -j$(nproc)
 
# 带资源限制
systemd-run --scope -p MemoryMax=2G -p CPUQuota=50% ./heavy-task
 
# 带安全沙箱
systemd-run --scope -p ProtectHome=yes -p PrivateTmp=yes ./untrusted.sh
 
# 定时任务(无需 timer 文件)
systemd-run --on-calendar="*-*-* 03:00:00" /usr/local/bin/cleanup.sh
 
# 用户实例中运行
systemd-run --user --unit=my-task /usr/local/bin/task.sh

9.15 特殊变量与模板 Unit

模板 Unit 文件名包含 @,允许同一个 Unit 文件产生多个实例:

# /etc/systemd/system/myapp@.service
[Unit]
Description=MyApp Instance %i
 
[Service]
Type=simple
ExecStart=/usr/bin/myapp --instance=%i
sudo systemctl start myapp@worker1.service
sudo systemctl start myapp@worker2.service

常用特殊变量:

变量含义
%n完整 Unit 名称
%pUnit 前缀(@ 之前)
%i实例名(@ 之后)
%h运行用户家目录
%H主机名
%t运行时目录 (/run$XDG_RUNTIME_DIR)
%S状态目录 (/var/lib~/.local/share)
%C缓存目录 (/var/cache~/.cache)

9.16 排错与调试

# 查看失败的服务
systemctl --failed
 
# 重置失败状态
sudo systemctl reset-failed nginx.service
 
# 深入服务状态信息
systemctl status -l nginx.service
 
# 验证 Unit 文件语法
systemd-analyze verify /etc/systemd/system/myapp.service
 
# 启用 systemd 调试日志
systemd-analyze log-level debug
# 完成调试后恢复
systemd-analyze log-level info
 
# 跟踪 D-Bus 消息
busctl monitor
 
# 内核命令行添加调试参数(GRUB 启动时按 e 编辑)
# systemd.log_level=debug systemd.log_target=console