服务管理 systemd
Linux 运维基础 · 第十三章 目标:掌握 systemd 体系,能写 service 文件、管理服务和日志
1. 概述#
systemd 是现代 Linux 的初始化系统和服务管理器。它替代了老旧的 SysV init 脚本,提供并行启动、依赖管理、cgroup 集成、统一日志等功能。
本章让你从"会用 systemctl start/stop"升级到"能写 service 文件 + 会查日志排障"。
2. systemd 设计理念#
2.1 SysV init 的痛点#
SysV init(老方式):
- 按顺序启动:A 启动 → B 启动 → C 启动(慢)
- 脚本管理:每个服务一个 shell 脚本(维护难)
- 日志:每个服务自己写日志文件(不统一)
- 依赖:简单的数字优先级(不够精细)2.2 systemd 的改进#
| 特性 | SysV init | systemd |
|---|---|---|
| 启动方式 | 串行 | 并行(按依赖关系) |
| 服务定义 | Shell 脚本 | 声明式 Unit 文件 |
| 日志 | 各管各的 | journald 统一收集 |
| 资源管理 | 无 | cgroup 集成 |
| 依赖管理 | 数字优先级 | 精确依赖描述 |
| socket 激活 | 不支持 | 支持(按需启动) |
| 定时任务 | cron | 内置 Timer Unit |
cgroup v2 是现代默认:RHEL/Rocky/Alma 9+、Ubuntu 22.04+、Fedora 默认已启用 cgroup v2(统一层级 unified hierarchy),取代 v1 的"每个资源一棵树"。 systemd 靠它给每个 service 做资源统计与限制。查看当前用哪版:
bashstat -fc %T /sys/fs/cgroup/ # 输出 cgroup2fs = v2;tmpfs = 还是 v1(如 RHEL/Rocky 8) systemctl show <svc> -p MemoryCurrent # v2 下能直接读到服务实时内存service 里可直接写
MemoryMax=512M、CPUQuota=50%、TasksMax=100做资源限制(见 5.3)。
2.3 核心概念:Unit#
systemd 中一切皆 Unit。不同类型处理不同职责:
| Unit 类型 | 后缀 | 用途 |
|---|---|---|
| service | .service | 服务(最常用) |
| target | .target | 一组 Unit 的集合(相当于 SysV 的 runlevel) |
| timer | .timer | 定时任务(替代 cron) |
| socket | .socket | socket 激活 |
| mount | .mount | 挂载点管理 |
| device | .device | 设备管理 |
| slice | .slice | cgroup 资源分组 |
3. systemctl 核心操作#
3.1 启停服务#
systemctl start nginx # 启动
systemctl stop nginx # 停止
systemctl restart nginx # 重启(关 + 开)
systemctl reload nginx # 重载配置(不中断服务)
systemctl status nginx # 查看状态
# 开机自启
systemctl enable nginx # 设为开机自启(只登记,不会立刻启动!)
systemctl disable nginx # 取消开机自启(不会停掉当前正在跑的)
systemctl is-enabled nginx # 检查是否开机自启
systemctl enable --now nginx # 一步到位:登记开机自启 + 立即启动
# 坑:enable 与 start 是两件事,容易只做一半
# 只 start 不 enable → 这次跑得好好的,重启后服务没了
# 只 enable 不 start → 现在没跑,要等下次开机才生效
# 屏蔽/取消屏蔽(连手动启动都不让)
systemctl mask nginx # 屏蔽(创建 /dev/null 的符号链接)
systemctl unmask nginx # 取消屏蔽3.2 查看状态#
systemctl status nginx # 完整状态(进程 + 日志摘要)
# 关键输出解读
# Loaded: loaded (/usr/lib/systemd/system/nginx.service; enabled; vendor preset: enabled)
# ^^ enabled = 开机自启, disabled = 不开机自启, masked = 已屏蔽
# Active: active (running) since ...
# ^^ active=运行中, inactive=未运行, failed=启动失败, activating=启动中
# Main PID: 1234 (nginx)
# CGroup: /system.slice/nginx.service3.3 列出 Unit#
# 列出所有已加载的 Unit
systemctl list-units # 活动的 Unit
systemctl list-units --all # 所有 Unit(含不活动的)
# 只看服务
systemctl list-units --type=service
# 只看开机自启的服务
systemctl list-unit-files --type=service --state=enabled
# 看失败的 Unit
systemctl --failed
# 看所有已安装的服务(不论是否启用)
systemctl list-unit-files --type=service3.4 重启 / 关机#
systemctl reboot # 重启
systemctl poweroff # 关机
systemctl suspend # 休眠
systemctl rescue # 救援模式(单用户)4. journalctl — 日志查看#
4.1 为什么重要#
systemd 用 journald 统一收集所有服务的 stdout/stderr + 内核日志 + syslog。不再需要去 /var/log/ 逐文件翻。
4.2 基本用法#
journalctl # 所有日志(可能很多)
journalctl -u nginx # 只看 nginx 服务的日志
journalctl -u nginx -f # 实时跟踪(类似 tail -f)
journalctl -u nginx --since "10min ago" # 最近 10 分钟
journalctl -u nginx --since "2026-07-01 10:00:00" --until "2026-07-01 11:00:00"
# 只看今天的
journalctl --since today
# 看最近启动的日志(本次系统启动后的)
journalctl -b # 本次启动
journalctl -b -1 # 上一次启动
# 按优先级过滤
journalctl -p err # 只看错误及以上级别
journalctl -p warn # 只看警告及以上
# 级别:emerg(0) alert(1) crit(2) err(3) warning(4) notice(5) info(6) debug(7)
# 搜索
journalctl --grep="timeout" # 搜索关键词
journalctl -u nginx --grep="error"
# 内核日志
journalctl -k # 只看内核日志(同 dmesg)
journalctl -k --since today4.3 日志分析#
# 看服务为什么启动失败
systemctl status nginx --no-pager -l # 显示完整日志
journalctl -u nginx -n 50 --no-pager
# 统计某个服务今天的错误数
journalctl -u nginx --since today -p err --no-pager | wc -l
# 查看日志占用的磁盘空间
journalctl --disk-usage
# 清理旧日志(只保留最近 7 天)
journalctl --vacuum-time=7d
# 或限制大小
journalctl --vacuum-size=500M4.4 日志持久化(默认可能一重启就没了!)#
大坑:journald 默认 Storage=auto,只有当目录 /var/log/journal/ 存在时才落盘持久化;
否则日志写在 /run/log/journal/(tmpfs 内存),一重启全丢。RHEL 系默认就没有这个目录
(历史日志查不到,journalctl -b -1 看上次启动会直接报错),排障前务必先开持久化。
# 判断当前是否持久化
journalctl --disk-usage # 若显示的是 /run/... 路径 → 没持久化
ls /var/log/journal/ 2>/dev/null # 目录不存在 = 没持久化
# 开启持久化(两种方式,任选其一)
# 方式 A:建目录,auto 模式即自动落盘
mkdir -p /var/log/journal
systemd-tmpfiles --create --prefix /var/log/journal
systemctl restart systemd-journald
# 方式 B:显式配置
# 编辑 /etc/systemd/journald.conf
# [Journal]
# Storage=persistent
# SystemMaxUse=1G # 顺手限制上限,防止日志撑爆磁盘
systemctl restart systemd-journald5. 编写 Unit 文件#
5.1 文件位置#
| 路径 | 用途 | 优先级 |
|---|---|---|
/etc/systemd/system/ | 用户自定义(运维装的服务放这) | 最高 |
/run/systemd/system/ | 运行时生成的 | 中 |
/usr/lib/systemd/system/ | 系统包管理器安装的(不要手改) | 最低 |
5.2 三段结构#
一个 .service 文件有三个主要段落:
[Unit]
# 服务描述和依赖
[Service]
# 如何启动/停止这个服务
[Install]
# 安装相关的配置(开机自启等)5.3 完整示例:myapp.service#
# /etc/systemd/system/myapp.service
[Unit]
Description=My Application Service
Documentation=https://docs.myapp.com
After=network.target # 网络就绪后启动
Requires=network.target # 硬依赖(网络挂了本服务也停)
[Service]
Type=simple # 最常用:主进程就是服务进程
User=myapp # 用哪个用户运行
Group=myapp
WorkingDirectory=/opt/myapp
ExecStart=/opt/myapp/bin/myapp --config /etc/myapp/config.yaml
ExecReload=/bin/kill -HUP $MAINPID # reload 时发 SIGHUP
ExecStop=/bin/kill -TERM $MAINPID # 停止命令
Restart=on-failure # 异常退出时自动重启
RestartSec=5 # 重启前等 5 秒
StandardOutput=journal # stdout 进 journald
StandardError=journal # stderr 进 journald
SyslogIdentifier=myapp # journald 中的标识
LimitNOFILE=65536 # 文件描述符上限
# 资源限制(cgroup v2,防止单个服务拖垮整机)
MemoryMax=512M # 内存硬上限,超了会被 OOM 掉该服务
CPUQuota=50% # 最多用 0.5 个 CPU
TasksMax=100 # 最多 100 个线程/进程
# 环境变量
Environment="APP_ENV=production"
Environment="DATABASE_URL=postgresql://localhost:5432/myapp"
# 或从文件加载
# EnvironmentFile=/etc/myapp/env.conf
[Install]
WantedBy=multi-user.target # 多用户模式下启动5.4 Type 类型说明#
| Type | 含义 | 何时用 |
|---|---|---|
| simple | ExecStart 启动的进程就是主进程 | 最常用 |
| forking | 服务自己 fork 到后台,父进程退出 | 老式守护进程(如 nginx) |
| oneshot | 执行一次就退出,配合 RemainAfterExit | 初始化脚本 |
| notify | 服务就绪后发 sd_notify() 通知 | 支持 systemd 的新式服务 |
| dbus | 服务从 D-Bus 获取名称 | 桌面环境 |
| idle | 等待所有其他作业结束后启动 | 延迟到启动末尾 |
5.5 Restart 策略#
| 值 | 含义 |
|---|---|
no | 不重启(默认) |
always | 不管怎样退出都重启 |
on-success | 退出码 0 才重启 |
on-failure | 退出码非 0 或被信号杀才重启(推荐) |
on-abnormal | 被信号杀或超时才重启 |
on-watchdog | 看门狗超时 |
5.6 安装和生效#
# 创建文件后
systemctl daemon-reload # 重载 systemd 配置
# 启动并设为开机自启
systemctl enable --now myapp # enable + start 一步到位
# 查看状态
systemctl status myapp
# 删服务
systemctl stop myapp
systemctl disable myapp
rm /etc/systemd/system/myapp.service
systemctl daemon-reload6. Target vs Runlevel#
6.1 对应关系#
| SysV Runlevel | systemd Target | 含义 |
|---|---|---|
| 0 | poweroff.target | 关机 |
| 1 | rescue.target | 单用户/救援模式 |
| 3 | multi-user.target | 多用户命令行(服务器默认) |
| 5 | graphical.target | 图形界面(桌面版默认) |
| 6 | reboot.target | 重启 |
# 查看当前 target
systemctl get-default
# 设置默认 target
systemctl set-default multi-user.target
# 临时切换 target
systemctl isolate rescue.target7. systemd Timer(cron 的现代替代)#
Timer 是 systemd 内置的定时机制,逐渐取代 cron。相比 crontab 的优势:
统一进 journald(journalctl -u 直接看每次跑的输出,cron 得自己重定向日志)、
支持 Persistent=(关机期间错过的任务,开机后补跑,cron 做不到)、
依赖/资源限制/失败自动重试都能复用 service 的能力。
7.1 Timer + Service 成对出现#
一个 timer 负责"何时触发",它拉起一个同名的 .service 负责"干什么"。
# /etc/systemd/system/backup.service —— 干活的(Type=oneshot,跑完就退)
[Unit]
Description=Daily backup job
[Service]
Type=oneshot
User=backup
ExecStart=/usr/local/bin/backup.sh# /etc/systemd/system/backup.timer —— 定时的(文件名主干必须和 service 一致)
[Unit]
Description=Run backup daily at 02:00
[Timer]
OnCalendar=*-*-* 02:00:00 # 每天 02:00(格式:星期 年-月-日 时:分:秒)
Persistent=true # 错过了(比如那会儿关机)开机后立即补跑一次
RandomizedDelaySec=300 # 随机延迟 0~5 分钟,避免整点惊群
[Install]
WantedBy=timers.target# 启用的是 timer,不是 service
systemctl daemon-reload
systemctl enable --now backup.timer
systemctl list-timers --all # 看所有定时器:下次触发时间 / 上次触发 / 还剩多久
journalctl -u backup.service # 看历次执行的输出(cron 得自己配日志才有)
systemd-run --on-active=30s /path/cmd # 临时一次性定时(不用建文件)7.2 OnCalendar 常用写法 & 与 cron 对照#
OnCalendar=hourly # 每小时(整点)
OnCalendar=daily # 每天 00:00
OnCalendar=*-*-* 02:00:00 # 每天 02:00 cron: 0 2 * * *
OnCalendar=Mon *-*-* 09:00:00 # 每周一 09:00 cron: 0 9 * * 1
OnCalendar=*-*-* *:00/15:00 # 每 15 分钟 cron: */15 * * * *
OnCalendar=*-*-01 03:00:00 # 每月 1 号 03:00 cron: 0 3 1 * *systemd-analyze calendar "*-*-* 02:00:00" # 校验表达式并算出下几次触发时间(写完先验证!)另一种触发方式:OnBootSec=/OnUnitActiveSec=(相对时间),如
OnBootSec=15min(开机 15 分钟后跑)、OnUnitActiveSec=1h(上次跑完 1 小时后再跑)。
8. 启动耗时分析(systemd-analyze)#
systemd-analyze # 总启动时间(firmware/loader/kernel/userspace 各占多少)
systemd-analyze blame # 按耗时排序,揪出拖慢开机的服务
systemd-analyze critical-chain # 关键路径:哪些服务串在启动链上、各自等了多久
systemd-analyze plot > boot.svg # 生成可视化启动时间线排查"开机很慢"就从 blame 入手:某个服务耗时几十秒,多半是它在等网络/DNS/依赖。
9. 实战#
实战 1:看开机自启的服务#
systemctl list-unit-files --type=service --state=enabled
# 常见结果
# nginx.service enabled
# sshd.service enabled
# docker.service enabled
# cron.service enabled
# 检查有没有不该自启的、或者应该自启的没开实战 2:排查服务启动失败#
# Step 1:看状态
systemctl status myapp
# Active: failed (Result: exit-code)
# Step 2:看日志
journalctl -u myapp -n 50 --no-pager
# Step 3:手动跑 ExecStart 命令,看报错
sudo -u myapp /opt/myapp/bin/myapp --config /etc/myapp/config.yaml
# Step 4:常见问题
# - 配置文件路径不对
# - User 不存在
# - 端口被占用
# - 权限不够实战 3:修改已安装服务的配置#
# 不要直接改 /usr/lib/systemd/system/nginx.service
# 覆盖机制:
# 创建覆盖目录
mkdir -p /etc/systemd/system/nginx.service.d/
# 创建覆盖文件
cat > /etc/systemd/system/nginx.service.d/override.conf << EOF
[Service]
LimitNOFILE=65535
Environment="EXAMPLE=value"
EOF
# 重载
systemctl daemon-reload
systemctl restart nginx
# 查看合并后的完整配置
systemctl cat nginx10. 小结#
| 知识点 | 一句话记忆 |
|---|---|
| systemctl | start/stop/restart/reload/enable/disable/status |
| enable vs start | enable=开机自启(不立即跑),start=立即跑(不持久);enable --now 两件事一起做 |
| 改 unit 后 | 必须 systemctl daemon-reload 才生效 |
| 服务文件 | 三段:[Unit] 依赖 + [Service] 启动方式 + [Install] 自启 |
| 文件位置 | /etc/systemd/system/ 用户自定义,优先级最高 |
| 常用 Type | simple(最常用)/ forking(老式)/ oneshot(一次性/timer) |
| journalctl | -u 指定服务,-f 实时,--since 时间过滤,-p 级别过滤 |
| 日志持久化 | 默认可能不落盘,建 /var/log/journal 或 Storage=persistent |
| Timer | cron 的现代替代,OnCalendar + Persistent=true 补跑,list-timers 看 |
| systemd-analyze | blame 揪出拖慢开机的服务 |
| 覆盖配置 | /etc/systemd/system/<service>.d/override.conf,不修改原文件 |
| target | multi-user.target≈runlevel 3(命令行)、graphical.target≈runlevel 5(图形) |
| cgroup v2 | RHEL9/Ubuntu22.04+ 默认,service 里可写 MemoryMax/CPUQuota 限资源 |
上一章:十二、存储与磁盘管理 下一章:十四、Shell 脚本