暗色模式

systemd 服务管理与日志排查实战:从单元文件到 journalctl

技术教程
2026-08-03
10
0

systemd 是当今几乎所有主流 Linux 发行版(Ubuntu、Debian、RHEL、Arch 等)的默认初始化系统,负责管理开机启动、服务运行、定时任务与系统日志。对运维和开发者来说,systemctljournalctl 是日常使用频率最高的两个命令,而读懂单元文件(Unit File)则是排查服务起不来、反复重启等问题的基本功。

本文基于 Ubuntu 24.04 LTS(systemd 255)与 systemd 最新稳定版 v258(2025 年 9 月发布)展开,从单元文件编写、定时任务到日志排查,全部命令可直接在服务器上照抄执行。

单元文件基础:一切皆单元

systemd 把系统上需要管理的东西抽象为「单元」(Unit),每种单元管理一类对象,常见类型如下:

单元类型扩展名作用
service.service后台服务/守护进程
timer.timer定时器(替代 cron)
socket.socket套接字监听(可配合服务按需启动)
mount.mount挂载点
target.target单元组,系统状态(如 multi-user.target
slice.slice资源分组(配合 cgroup v2)

查看系统上全部服务单元:

systemctl list-units --type=service        # 已加载的服务
systemctl list-units --type=service --all  # 包含未激活的
systemctl list-unit-files --type=service   # 查看 enabled/disabled 状态
systemctl status ssh.service               # 查看单个服务的详细状态
v258 起 systemd 只支持 cgroup v2,旧的 System V runlevel 支持(initctltelinitrunlevel0-6.target)已被彻底移除,SysV 脚本也将在 v259 被移除。新写服务请一律使用原生单元文件。

systemctl 命令速查

systemctl start  nginx.service    # 立即启动
systemctl stop   nginx.service    # 停止
systemctl restart nginx.service   # 重启
systemctl reload nginx.service    # 平滑重载配置(服务需支持)
systemctl enable  nginx.service   # 开机自启
systemctl disable nginx.service   # 取消自启
systemctl enable --now nginx.service  # 自启 + 立即启动,组合拳
systemctl daemon-reload           # 重载单元文件(改完文件必须执行!)
systemctl mask    nginx.service   # 彻底禁用(连手动启动都不行)
systemctl unmask  nginx.service
systemctl is-active nginx.service # 输出 active/inactive,适合脚本判断
systemctl list-dependencies multi-user.target  # 查看启动依赖树

忘记 daemon-reload 是新手最常见的错误:修改单元文件后直接 systemctl restart,systemd 用的还是旧配置,此时状态会提示 Warning: Unit file changed on disk, 'systemctl daemon-reload' recommended.

先看看系统里当前都在跑哪些服务(systemctl list-units 的输出):

systemctl 查看运行中的服务

手写一个服务单元文件

把任意命令或脚本包装成系统服务,只需一个 .service 文件。以部署一个简单的 Node.js Web 服务为例,新建 /etc/systemd/system/myapp.service

[Unit]
Description=My Node.js Web Application
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=www-data
WorkingDirectory=/opt/myapp
ExecStart=/usr/bin/node /opt/myapp/server.js
Restart=on-failure
RestartSec=5
Environment=NODE_ENV=production
EnvironmentFile=-/etc/myapp.env

[Install]
WantedBy=multi-user.target

[Unit]Description 是描述;After= 声明启动顺序(本服务在 network 就绪后启动),Wants= 是弱依赖(network 失败不影响本服务)。顺序和依赖要分清:After 只排顺序,Requires/Wants 才建立依赖关系。

[Service]是核心,几个关键指令:

指令含义常用值
Type启动类型simple(默认)、forkingoneshotnotify
User= / Group=以哪个用户运行不要用 root 跑业务服务
ExecStart=启动命令必须写绝对路径
Restart=崩溃后重启策略noalwayson-failureon-abnormal
RestartSec=重启间隔秒数防止崩溃后疯狂重启
Environment= / EnvironmentFile=注入环境变量配置文件可用 - 前缀容忍缺失

Type 的选型直接决定服务是否被误判为「已启动」:

  • simple:ExecStart 一执行就认为服务已启动,适合前台运行的程序(Node、Python 脚本等);
  • forking:程序自己 fork 到后台(老式守护进程,如旧版 nginx),需要配合 PIDFile= 指定 pid 文件;
  • oneshot:一次性任务,执行完即退出(适合初始化脚本),常配合 RemainAfterExit=yes 让状态保持 active;
  • notify:程序启动成功后通过 sd_notify 主动通知 systemd,需程序支持(如 systemd 自带的 networkd)。

Restart= 建议统一使用 on-failure(进程异常退出才重启),避免 always 让手动 stop 后又被拉起。

资源限制与安全加固

一条龙限制 CPU、内存和文件句柄,防止服务失控拖垮整机:

[Service]
MemoryMax=512M          # 内存硬上限,超出即 OOM kill
MemoryHigh=384M         # 软上限,超过会持续回收
CPUQuota=50%            # CPU 上限为 1 核的 50%
TasksMax=200            # 最大进程/线程数
LimitNOFILE=65535       # 文件句柄上限

sandbox 类指令按需开启,让服务只访问它该访问的东西:

[Service]
ProtectSystem=strict        # 整个文件系统只读
ProtectHome=true            # 隐藏 /home
PrivateTmp=true             # 独立临时目录
ReadWritePaths=/var/lib/myapp   # 只放行这几个可写路径
NoNewPrivileges=true        # 禁止提权
PrivateDevices=true         # 屏蔽设备节点(按需,部分程序需要 /dev)

这些指令大多默认关闭(兼容性考虑),对可信部署建议逐步打开——出问题先关掉再对比。

文件位置与优先级

/etc/systemd/system/      # 管理员自定义(优先级最高)
/usr/lib/systemd/system/  # 软件包自带(apt/dnf 安装的)

同名的单元文件,/etc/systemd/system 会覆盖 /usr/lib/systemd/system。改完文件记得 systemctl daemon-reload

改完怎么验证

sudo systemctl daemon-reload
sudo systemctl enable --now myapp.service
systemctl status myapp.service --no-pager     # 看状态与最近日志
systemctl show myapp.service -P MainPID       # 查主进程 PID
systemctl cat myapp.service                   # 回显最终生效的配置

执行后的真实输出(enable --now 一步完成自启+启动,status 一眼确认状态、主进程与日志):

myapp 服务启动与状态验证

定时任务:用 systemd timer 替代 cron

cron 的问题:任务执行记录不统一、环境变量与交互式 shell 不同、无资源限制。systemd timer 完全覆盖这些场景,且与 service 单元无缝协作。写法分两步:一个 oneshot 服务 + 一个 timer。

/etc/systemd/system/backup.service

[Unit]
Description=Daily backup job

[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.sh

/etc/systemd/system/backup.timer

[Unit]
Description=Run backup every day at 03:30

[Timer]
OnCalendar=*-*-* 03:30:00
Persistent=true      # 错过的触发(如关机时段)下次开机补跑
RandomizedDelaySec=60   # 随机延迟,错峰执行

[Install]
WantedBy=timers.target

启用并检查:

sudo systemctl enable --now backup.timer
systemctl list-timers --no-pager    # 查看所有定时器与下次触发时间
systemctl start backup.service      # 手动触发一次(直接跑服务)

启用后 list-timers 输出(可以看到刚创建的 backup.timer 已排入队列,下次触发是明天 03:30):

定时器列表与下次触发时间

OnCalendar 语法比 cron 更灵活,几个常用写法:

OnCalendar=*-*-* 03:30:00      # 每天 03:30
OnCalendar=Mon..Fri 09:00:00   # 工作日 09:00
OnCalendar=*-*-1,15 00:00:00   # 每月 1 号、15 号零点
OnCalendar=minutely            # 每分钟(另有 hourly/daily/weekly/monthly)
OnCalendar=*:0/15              # 每 15 分钟

相比 cron 的三个优势:任务状态可用 systemctl status / journalctl -u backup.service 统一查看;可加 MemoryMax 等资源限制;Persistent=true 天然解决「服务器关机错过任务」的问题。

journalctl 日志排查实战

systemd 把服务的 stdout/stderr、内核日志、开机过程统一收进 journal 日志,配合 journalctl 一条命令完成过滤、检索、分析。例如只看 sshd 服务最近 8 条日志:

journalctl 查看 sshd 服务日志

常用过滤组合

journalctl -u nginx.service --no-pager        # 只看某个服务
journalctl -u nginx.service -f                # 实时跟踪(同 tail -f)
journalctl --since "1 hour ago"               # 最近 1 小时
journalctl --since today --until "1 hour ago" # 时间范围
journalctl -p err -b                          # 本次开机以来的错误级及以上
journalctl -k                                 # 仅内核消息(类似 dmesg)
journalctl -b -1                              # 上一次开机(-2 是上上次)
journalctl --list-boots                       # 列出所有开机记录
journalctl -g "Failed password|authentication failure"  # 正则搜索
journalctl -u myapp.service -p err --since "30 minutes ago"  # 组合条件

优先级从高到低:emerg(0)、alert(1)、crit(2)、err(3)、warning(4)、notice(5)、info(6)、debug(7)。-p err 表示显示 err 及以上。

排查服务崩溃的标准流程

# 1. 找出失败的服务
systemctl list-units --failed --no-pager

# 2. 查看该服务的错误日志
journalctl -u myapp.service -p err --no-pager

# 3. 如果重启循环,看最近 20 条完整输出
journalctl -u myapp.service -n 20 --no-pager

# 4. 结合本次运行 ID 精确查看某一次运行的全部输出
systemctl show -P InvocationID myapp.service
journalctl _SYSTEMD_INVOCATION_ID=$(systemctl show -P InvocationID myapp.service)

# 5. 崩溃通常是信号 11 (Segmentation fault),全局搜一下
journalctl -g "Segmentation fault|signal 11|core dump" --since "24 hours ago"

持久化与磁盘占用控制

Ubuntu 24.04 默认 journal 存在内存 /run/log/journal,重启即丢,且全部服务日志都往里写,长时间运行会占不少磁盘。两步搞定:持久化到磁盘 + 设上限。

sudo mkdir -p /var/log/journal
sudo systemd-tmpfiles --create --prefix /var/log/journal
sudo systemctl restart systemd-journald
journalctl --disk-usage        # 查看当前占用

编辑 /etc/systemd/journald.conf[Journal] 段:

[Journal]
Storage=persistent        # 持久化到 /var/log/journal
SystemMaxUse=2G           # journal 总占用上限 2G
SystemMaxFileSize=128M    # 单个日志文件大小
MaxRetentionSec=6month    # 保留 6 个月

手动清理(对应 --vacuum-time--vacuum-size--vacuum-files 三种策略):

sudo journalctl --vacuum-size=500M   # 压缩到 500M 以内
sudo journalctl --vacuum-time=30d    # 只留最近 30 天
排查阶段需要完整堆栈信息时,可临时给 journalctl--all 参数,避免长字段被截断。

小结

systemd 入门的三件事:用 systemctl 管理服务、用单元文件把脚本变成受管服务、用 journalctl 看日志。掌握了这三个环节,绝大多数服务起不来、反复重启、日志丢失的问题都能独立定位。进阶方向是 socket 激活、systemd-run 临时任务、资源组(slice)管理,以及 v258 引入的 ConcurrencySoftMax= 并发限制等新特性,之后可以单独成文。

参考资料

发表评论

暂无评论,快来抢沙发吧!