引言
现代 Linux 发行版几乎都基于 systemd 管理服务——它是 PID 1(init 进程),负责拉起、监督、重启你的所有守护进程。本文讲透 systemd 服务管理:先给 Unit 文件全景(Service / Socket / Timer / Path 四大类型),再拆解 Service 单元的常用配置(依赖、重启策略、资源限制),接着讲 systemctl 的完整命令体系、Socket 激活与 Timer 定时任务,最后演示如何从零写一个开机自启的服务并排错。
前置:/linux-process-management/(进程与启动)。进程信号与后台概念见该文。容器视角见 [[docker]]。
目录
- 1. systemd 是什么:PID 1 与 Unit 体系
- 2. Unit 文件四大类型
- 3. Service 单元核心配置
- 4. systemctl 命令体系
- 5. 依赖与启动顺序:After/Wants/Requires
- 6. 资源控制与安全隔离
- 7. Timer 定时任务与 Socket 激活
- 8. 从零写一个开机自启服务
- 9. 排错:journalctl 与 unit 状态
- 10. 速查表
- 延伸阅读
1. systemd 是什么:PID 1 与 Unit 体系
systemd = 第一个进程(PID 1)+ 服务管理器:
内核 → systemd(PID 1) → 并行拉起系统服务 → 开机自启应用
↑
监督每个服务,挂了自动重启
核心概念:Unit(单元)——一切被 systemd 管理的「东西」:
| 类型 | 后缀 | 管理什么 |
|---|---|---|
| Service | .service | 守护进程/服务 |
| Socket | .socket | 网络/IPC 端口 |
| Timer | .timer | 定时任务 |
| Path | .path | 文件/目录变化监控 |
| Mount | .mount | 挂载点 |
| Target | .target | 一组单元的「启动目标」 |
心智:systemd 把「服务、定时、端口、挂载」都抽象成 Unit——统一用 systemctl 管理,这是它统治现代 Linux 的原因。
2. Unit 文件四大类型
Unit 文件存放位置:
/usr/lib/systemd/system/ # 发行版安装的
/etc/systemd/system/ # 管理员自定义(优先)
/lib/systemd/system/ # 旧式软链
① Service(服务)——最常用:
# /etc/systemd/system/myapp.service
[Unit]
Description=My App Service
After=network.target
[Service]
Type=simple
ExecStart=/usr/bin/myapp --config /etc/myapp.conf
Restart=on-failure
User=myapp
[Install]
WantedBy=multi-user.target
② Socket(端口激活)——服务不用常驻,有人连接才启动:
# myapp.socket
[Socket]
ListenStream=8080
[Install]
WantedBy=sockets.target
③ Timer(定时)——cron 的 systemd 替代:
# backup.timer
[Unit]
Description=Daily backup
[Timer]
OnCalendar=daily
Persistent=true
[Install]
WantedBy=timers.target
④ Path(路径监控)——文件变化触发:
# config.path
[Path]
PathModified=/etc/myapp/config.json
Unit=myapp-reload.service
记忆:Service 管常驻、Socket 管按需拉起、Timer 管定时、Path 管文件触发——四大类型覆盖守护进程的绝大多数场景。
3. Service 单元核心配置
[Service] 段核心参数:
| 参数 | 含义 | 示例 |
|---|---|---|
| Type | 进程类型 | simple/forking/oneshot |
| ExecStart | 启动命令 | /usr/bin/myapp |
| ExecStartPre | 启动前钩子 | /bin/mkdir -p /var/run |
| Restart | 重启策略 | on-failure/always |
| RestartSec | 重启间隔 | 2s |
| User/Group | 运行用户 | myapp |
| Environment | 环境变量 | KEY=value |
| EnvironmentFile | 环境文件 | /etc/myapp/env |
| WorkingDirectory | 工作目录 | /opt/myapp |
| LimitNOFILE | 文件句柄上限 | 65535 |
Type 详解:
simple — 主进程直接前台(推荐现代服务)
forking — 启动后 fork 到后台(老式守护进程)
oneshot — 一次性任务,执行完即退
notify — 启动完成后通过 sd_notify 通知 systemd
Restart 策略:
Restart=on-failure # 非正常退出才重启(推荐)
Restart=always # 任何退出都重启(含正常)
RestartSec=2s # 重启间隔,防崩溃风暴
记忆:Type=simple + Restart=on-failure 是现代服务的黄金组合——前台进程、异常自动拉起。
4. systemctl 命令体系
最常用的 systemctl 命令:
# 启动/停止/重启
systemctl start myapp
systemctl stop myapp
systemctl restart myapp
systemctl reload myapp # 热加载配置(需服务支持)
# 开机自启
systemctl enable myapp # 开机自启
systemctl disable myapp # 取消
systemctl enable --now myapp # 自启 + 立即启动
# 查看状态
systemctl status myapp # 状态 + 最近日志
systemctl is-active myapp # active/inactive/failed
systemctl is-enabled myapp # enabled/disabled
# 列表
systemctl list-units --type=service --all
systemctl list-units --failed # 查看失败服务
状态解读:
loaded = 已加载 unit
active(running) = 正在运行
active(exited) = 已完成(oneshot)
failed = 启动失败
记忆:enable 管「开机自启」、start 管「立即运行」——
enable --now两步合一,最常用。
5. 依赖与启动顺序:After/Wants/Requires
启动顺序(After/Before)与依赖(Requires/Wants)是两回事:
[Unit]
Description=Web app
# 顺序:network 先就绪
After=network-online.target postgresql.service
# 依赖:network 失败则不启动本服务
Requires=network-online.target
# 软依赖:postgresql 尽力拉起,失败不阻塞
Wants=postgresql.service
| 指令 | 语义 |
|---|---|
| After | 本服务在其后启动(顺序) |
| Before | 本服务在其前启动 |
| Requires | 硬依赖:对方失败→本服务失败 |
| Wants | 软依赖:对方失败→本服务照跑 |
| BindsTo | 强绑定:对方停止→本服务也停 |
依赖方向的直觉:
数据库必须先于应用启动?→ After=db.service
数据库挂了应用必须停? → BindsTo=db.service
数据库挂了应用照常跑? → Wants=db.service
记忆:After 管先后,Wants/Requires 管生死——顺序用 After、软依赖用 Wants、硬依赖用 Requires,别混为一谈。
6. 资源控制与安全隔离
systemd 用 cgroup 给服务限资源:
[Service]
# CPU 与内存
CPUWeight=100 # 相对权重(100 默认)
MemoryMax=1G # 内存硬上限
MemoryHigh=512M # 软上限(压力下回收)
TasksMax=512 # 线程数上限
# IO
IOWeight=100
# 文件描述符
LimitNOFILE=65535
# 安全隔离
ProtectSystem=strict # 只读保护系统目录
ProtectHome=true # 隐藏家目录
PrivateTmp=true # 独立临时目录
NoNewPrivileges=true # 禁止提权
ReadOnlyPaths=/etc/myapp # 只读特定目录
常用安全指令:
| 指令 | 作用 |
|---|---|
| ProtectSystem=strict | 系统目录只读 |
| ProtectHome=true | /home、/root 只读 |
| PrivateTmp=true | 隔离 /tmp |
| NoNewPrivileges | 禁止 setuid 提权 |
| CapabilityBoundingSet | 裁剪内核能力 |
记忆:systemd 是「零成本的容器」——不装 Docker 也能给服务限内存、隔离文件系统、防提权。
7. Timer 定时任务与 Socket 激活
Timer 取代 cron 的优势:可精确调度、可持久(错过补跑)、日志统一。
# /etc/systemd/system/backup.timer
[Unit]
Description=Daily backup at 02:30
[Timer]
OnCalendar=*-*-* 02:30:00 # 每天 02:30
Persistent=true # 机器关机错过则开机补跑
RandomizedDelaySec=5m # 随机延迟防风暴
[Install]
WantedBy=timers.target
# /etc/systemd/system/backup.service
[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.sh
systemctl enable backup.timer
systemctl list-timers # 查看定时器
Socket 激活(按需启动):
# 监听 8080,有人连才拉起服务
systemctl start myapp.socket
ss -tlnp | grep 8080 # socket 监听中
# 服务本体 idle 状态,连接触发自动启动
| 方案 | 适用 |
|---|---|
| Timer | 定时任务(替代 cron) |
| Socket 激活 | 低流量按需服务 |
| Path | 文件变化触发 |
记忆:Timer + oneshot Service 是 cron 的现代替代——
OnCalendar精确调度、Persistent错过补跑、日志统一走 journald。
8. 从零写一个开机自启服务
完整实战:让一个 Node/Python 应用开机自启:
# 1. 应用放 /opt/myapp,创建 systemd unit
sudo tee /etc/systemd/system/myapp.service > /dev/null <<'EOF'
[Unit]
Description=My API Service
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=myapp
WorkingDirectory=/opt/myapp
ExecStart=/usr/bin/node /opt/myapp/server.js
Environment=NODE_ENV=production
EnvironmentFile=/etc/myapp/env.conf
Restart=on-failure
RestartSec=3s
MemoryMax=1G
ProtectSystem=strict
PrivateTmp=true
NoNewPrivileges=true
[Install]
WantedBy=multi-user.target
EOF
# 2. 创建运行用户(可选)
sudo useradd --system --shell /sbin/nologin myapp
# 3. 重新加载 + 启动 + 开机自启
sudo systemctl daemon-reload
sudo systemctl enable --now myapp
# 4. 验证
systemctl status myapp
curl localhost:8080/health
铁律:写完 unit 必须
daemon-reload——否则 systemd 不会识别新配置;改完 unit 同样要 reload。
9. 排错:journalctl 与 unit 状态
查看服务日志(journald):
journalctl -u myapp # 该服务的全部日志
journalctl -u myapp -f # 跟随实时
journalctl -u myapp --since today # 今天
journalctl -u myapp --since "10 min ago"
journalctl -u myapp -p err # 只看错误
journalctl -u myapp --no-pager -n 100 # 最后 100 行
排错流程:
1. systemctl status myapp → 看状态 + 错误信息
2. journalctl -u myapp -p err → 看日志
3. systemctl show myapp → 查看生效配置
4. systemd-analyze blame → 启动耗时分析
5. systemd-analyze verify unit → 校验 unit 语法
常见失败原因:
| 报错 | 原因 | 解法 |
|---|---|---|
Failed to start | ExecStart 路径/权限错 | 检查命令可执行 |
(code=exited) | 进程退出非零 | 看日志退出码 |
[FAILED] + 重启循环 | 配置/依赖问题 | 看 Restart 日志 |
| Unit not found | 未 daemon-reload | reload 后再试 |
记忆:systemctl 看状态、journalctl 看日志——先 status 定位、再 journalctl 看细节,两步走完 80% 的排错。
10. 速查表
| 需求 | 做法 |
|---|---|
| 启动/停止/重启 | systemctl start/stop/restart <svc> |
| 开机自启 | systemctl enable --now <svc> |
| 查看状态 | systemctl status <svc> |
| 查看日志 | journalctl -u <svc> |
| 跟随日志 | journalctl -u <svc> -f |
| 定时任务 | Timer + oneshot Service |
| 端口按需拉起 | Socket 激活 |
| 限内存 | MemoryMax=1G |
| 防提权 | NoNewPrivileges=true |
| 改配置生效 | systemctl daemon-reload |
| 启动耗时 | systemd-analyze blame |
一句话记忆:systemd 用 Unit 管理一切——Service 常驻、Socket 按需、Timer 定时、Path 触发;systemctl 管启停与自启、journalctl 管日志、daemon-reload 让配置生效;After 管顺序、Wants 软依赖、Requires 硬依赖;cgroup 限资源、Protect 隔离文件——一套 unit 吃透服务管理。
延伸阅读
- /linux-process-management/ — 进程管理与启动
- /linux-journald-logging/ — journald 日志深入
- /linux-kernel-boot-process/ — systemd 启动目标
- [[docker]] — cgroup/namespace 容器化
- [[os]] — 操作系统原理
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。