woner's blog

Systemd 服务管理:从入门到日常够用

字数统计: 2.3k阅读时长: 9 min
2026/07/22
loading

之前写低配服务器优化那篇时,好几次提到 systemd 的 MemoryMaxRestart 这些配置。有朋友来问这些具体怎么配、systemd 到底怎么用。干脆写一篇,把最常用的场景都摊开说清楚。


Systemd 是什么?

Systemd 是现在几乎所有 Linux 发行版默认的 init 系统——说人话就是:它是操作系统启动后第一个运行的进程(PID=1),负责拉起和管理所有其他进程。

CentOS 7 之后、Ubuntu 15.04 之后、Debian 8 之后,全部默认用 systemd。

你可以把它理解成「操作系统自带的进程管理器」,比 pm2、supervisor、forever 这些第三方工具都更底层、更轻量、更稳定。因为——它就是系统本身的一部分。

1
2
3
# 看看是不是 systemd
ps -p 1 -o comm=
# 输出应该是 systemd

核心概念:Unit

Systemd 管理的一切资源都叫 Unit(单元)。最常见的几种:

类型 后缀 管理什么
Service .service 守护进程、后台服务
Timer .timer 定时任务(cron 替代)
Socket .socket 网络或进程间通信 socket
Path .path 监控文件/目录变化
Mount .mount 挂载点
Target .target 一组 unit 的组合(类似运行级别)

用得最多的是 Service 和 Timer。下面重点讲这两个。


写一个 Service 单元文件

Service 文件放在两个位置:

  • /etc/systemd/system/ — 用户自定义或覆盖的系统级服务(优先)
  • /usr/lib/systemd/system/ — 软件包安装自带的服务

永远在第一个目录下创建你的服务文件。

来看一个完整的例子。假设你有一个 Python 写的 API 服务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# /etc/systemd/system/myapi.service
[Unit]
Description=My Python API Service
After=network.target
Wants=network-online.target

[Service]
Type=simple
User=admin
WorkingDirectory=/home/admin/myapi
ExecStart=/usr/bin/python3 /home/admin/myapi/server.py
Restart=on-failure
RestartSec=5

# 资源限制
MemoryMax=500M
MemoryHigh=400M
CPUQuota=50%

# 安全加固
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=full

[Install]
WantedBy=multi-user.target

逐段说明

[Unit]

  • Description — 人可读的描述,systemctl status 时能看到
  • After= — 指定启动顺序:在这个 unit 之后启动。不意味着依赖,只意味着排序。
  • Wants= — 弱依赖:如果该 unit 可用就启动它,不可用不影响自己
  • Requires= — 强依赖:如果该 unit 启动失败,自己也失败

[Service]

  • Type=simple最常用。ExecStart 的进程就是主进程,立即处于 running 状态
  • Type=forking — 服务会 fork 出子进程后父进程退出(传统 daemon 模式)。Nginx、Apache 等用这种
  • Type=oneshot — 执行一次就退出的任务,常配合 RemainAfterExit=yes 使用
  • Type=notify — 服务启动完成后会发通知给 systemd(需要 sd_notify 支持)

User= 指定运行用户——永远不要用 root 跑服务,除非真的需要。

WorkingDirectory= 设工作目录,和 cd 到目录再运行等效。

Restart= 策略:

行为
no 不自动重启(默认)
on-success 仅退出码为 0 时重启
on-failure 退出码非 0 或被信号终止时重启
on-abnormal 被信号终止或超时时重启
on-abort 未捕获的异常退出时重启
always 不管什么退出原因都重启

**我推荐 on-failure**——正常退出(比如手动停)不重启,崩溃了自动拉起。

RestartSec=5 — 重启前等 5 秒,防止频繁崩溃时疯狂重启。

资源限制部分(需要 systemd v240+,CentOS 8/Ubuntu 20.04 以上都有):

1
2
3
4
MemoryMax=500M      # 硬限制,超过直接 OOM kill
MemoryHigh=400M # 软限制,超过会降速(throttling)
CPUQuota=50% # 最多用半个 CPU 核心
TasksMax=100 # 最多创建 100 个进程/线程

这是我最喜欢 systemd 的地方——不用 cgroups 命令、不用 docker 限制,原生就在那里。

安全加固(强烈推荐所有对外服务加上):

1
2
3
4
NoNewPrivileges=true   # 防止通过 setuid 等方式提权
PrivateTmp=true # 有自己的 /tmp,不和别的进程共享
ProtectSystem=full # 除了配置目录,/usr 和 /etc 只读
ReadWritePaths=/var/lib/myapp # 允许写的路径

[Install]

WantedBy=multi-user.target 表示这个服务在系统进入多用户模式(正常启动后的状态)时就该启动——这其实就是在说「开机自启」。


日常命令速查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 启动/停止/重启
sudo systemctl start myapi
sudo systemctl stop myapi
sudo systemctl restart myapi

# 查看状态(最有用的命令)
sudo systemctl status myapi

# 开机自启管理
sudo systemctl enable myapi # 开机自启
sudo systemctl disable myapi # 取消开机自启

# 重新加载服务文件(修改了 .service 文件后必须执行)
sudo systemctl daemon-reload
sudo systemctl restart myapi

# 查看日志
sudo journalctl -u myapi # 只看这个服务的日志
sudo journalctl -u myapi -f # 实时跟踪(类似 tail -f)
sudo journalctl -u myapi --since "1 hour ago"
sudo journalctl -u myapi -n 50 # 只看最近 50 行

# 查看所有正在运行的服务
systemctl list-units --type=service --state=running

实战 1:用 systemd Timer 替代 Cron

很多人不知道 systemd 自带定时任务功能,而且比 cron 更强——可以设随机延迟、防止任务重叠、失败通知。

一个 Timer 需要两个文件:

第一步:创建 Service 文件(定义要做什么)

1
2
3
4
5
6
7
8
# /etc/systemd/system/backup.service
[Unit]
Description=Daily backup

[Service]
Type=oneshot
ExecStart=/home/admin/scripts/backup.sh
User=admin

第二步:创建 Timer 文件(定义什么时候做)

1
2
3
4
5
6
7
8
9
10
11
# /etc/systemd/system/backup.timer
[Unit]
Description=Run backup daily at 3am

[Timer]
OnCalendar=daily
RandomizedDelaySec=30m
Persistent=true

[Install]
WantedBy=timers.target

几个 Timer 参数值得单独说说:

  • OnCalendar=daily — 每天零点触发。也可以用 Mon..Fri 03:00:00*:0/15(每15分钟)、2026-08-01 00:00:00(单次)
  • RandomizedDelaySec=30m — 在触发时间后 0~30 分钟内随机执行,避免多台机器同时冲 backend
  • Persistent=true — 如果机器在计划时间关机了,开机后补执行
  • OnUnitActiveSec=1h — 上次启动后过 1 小时再执行(不是按日历时间)

启动 timer:

1
2
3
4
5
sudo systemctl daemon-reload
sudo systemctl enable --now backup.timer

# 查看所有 timer
systemctl list-timers --all

相比 cron 的优势:

  • 日志统一走 journalctl,不用单独配日志轮转
  • 可以用 Restart 规则,任务失败了能重试
  • 可以用 sandboxing 加固定时任务
  • 配置文件可以和 .service 一起放 git 管理

实战 2:网络等待

很多服务启动时网络还没完全就绪。一个常见的翻车场景是「服务启动了但连不上外网」。

1
2
3
4
[Unit]
Description=A service that needs the network
After=network-online.target
Wants=network-online.target

但这还不够——network.target 只是说网络服务启动了,DHCP 可能还没拿到 IP。

要真正等网络可用,需要先启用 NetworkManager 或 systemd-networkd 的 wait 服务:

1
2
3
sudo systemctl enable systemd-networkd-wait-online
# 或者如果你用 NetworkManager:
sudo systemctl enable NetworkManager-wait-online

然后你的 service 文件里的 After=network-online.target 才会真正生效。


实战 3:日志管理

Systemd 的日志(journald)默认只保留在内存中,重启后丢失。想持久化:

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

限制日志大小:

1
2
3
4
# /etc/systemd/journald.conf
SystemMaxUse=500M
SystemMaxFileSize=100M
MaxRetentionSec=30day

查错:常见翻车点

1. 配置文件改了没生效

1
2
3
# 记得先 reload,再 restart
sudo systemctl daemon-reload
sudo systemctl restart myapi

2. 服务启动失败,但不知道原因

1
2
3
4
5
# 第一件事
sudo systemctl status myapi

# 如果 status 不够看
sudo journalctl -u myapi -n 30 --no-pager

Status 输出里的信息量很大——它会告诉你:主进程 PID、运行时间、内存占用、重启次数、最近几条日志。一看就懂。

3. 权限问题

systemd 默认用严格的权限隔离。如果你的服务访问不了某个文件,检查:

  • User= 有没有设对
  • ProtectSystem=full 会让 /usr/etc 只读
  • PrivateTmp=true 给了独立的 /tmp

4. Type 选错了

如果你启动一个老式 daemon(fork 模式),却用了 Type=simple,systemctl start 会卡住——因为父进程退出后 systemd 以为服务死了。

确认服务是哪种方式运行:

1
2
3
# 看看它是前台还是 fork
file /path/to/binary
# 或者看文档,或者直接 strace 看

和 pm2 / supervisor 比怎么样?

systemd pm2 supervisor
资源占用 0(系统自带) ~30-50MB ~10-20MB
内存限制 原生 cgroups v2
CPU 限制 原生
日志管理 journalctl 自带 + 轮转 自带
开机自启 enable 一句话 额外配 额外配
学习成本 稍微高一点

我的建议:

  • 如果你在管一台云服务器上跑的 1-3 个服务,用 systemd 就够了,别多装一个 pm2
  • 如果你在服务器上跑 Node.js 服务但不想离开 JS 生态,pm2 也很香
  • 如果有多台机器、需要统一管理,那就上 k8s——但那是另一个话题了

小总结

Systemd 作为 Linux 自带的进程管理工具,可以覆盖绝大多数日常需求:

需求 systemd 方案
服务开机自启 systemctl enable
服务崩溃自动恢复 Restart=on-failure
限制内存使用 MemoryMax=500M
定时任务 Timer unit
查看日志 journalctl -u
安全隔离 PrivateTmp + ProtectSystem

接触 systemd 之前,我总觉得「操作系统自带的 init 系统」很高深、很底层,不敢碰。但其实日常够用的那一小部分并不复杂——无非就是写一个 .service 文件,设好启动策略和资源限制,剩下交给 systemctl 命令去管。

而且,它是操作系统自带的——系统在,它就在。比任何第三方工具都靠谱、都持久。

CATALOG
  1. 1. Systemd 是什么?
  2. 2. 核心概念:Unit
  3. 3. 写一个 Service 单元文件
    1. 3.1. 逐段说明
  4. 4. 日常命令速查
  5. 5. 实战 1:用 systemd Timer 替代 Cron
  6. 6. 实战 2:网络等待
  7. 7. 实战 3:日志管理
  8. 8. 查错:常见翻车点
  9. 9. 和 pm2 / supervisor 比怎么样?
  10. 10. 小总结