systemd 服务实操手册 · 自启 / 状态 / 日志 / 定时

按 EX200 考纲裁剪:只写考纲里有的(启停服务与开机自启、默认目标、日志与保留、at/cron/timer、时间服务、中断启动)。每一步都写了「成功长什么样」,勾选框记进度。本手册随进度生长:走完一组再加下一组;还没在本机验证过的步骤会显式标注「尚未在本机验证」,不会当成已完成。

0 · 先把「服务」拆成三个维度 不建立这个模型,后面全是死记

考纲只写了一句「启动和停止服务,配置服务以在系统启动时自动启动」,但它背后是三个互相独立的维度。绝大多数错误判断,都是把这三个混成一个「服务开没开」。

维度问的问题查法取值
有没有这个 unit 存不存在systemctl cat X有 / No such file
开机启不启下次开机跑不跑is-enabledenabled / disabled / masked / static
现在跑不跑此刻有没有进程is-activeactive / inactive / failed
is-enabled = enabled+ is-active = active= 正常服务
disabled+ active= 正在跑,但重启后不会自己回来
enabled+ inactive= 设了开机自启,但现在没跑
命令各管一维,互不越界:start / stop / restart 只动进程(影响 is-active);enable / disable 只动链接(影响 is-enabled);mask / unmask 封锁的是「还能不能被启动」。

1 · 看清现状(纯只读)一个字节都不要改

is-enabled / is-active
systemctl is-enabled crond sshd firewalld
systemctl is-active  crond sshd firewalld
systemctl status crond --no-pager -l
成功长什么样:is-enabled 三行 enabled;is-active 三行 active;status 头部能读到两行关键信息 —— Loaded: loaded (/usr/lib/systemd/system/crond.service; enabled; preset: enabled) 和 Active: active (running)。
参数别打错:是 --no-pager(不是 --no-paper),是 -l(小写字母 L,不是数字 1)。打错时 systemd 只会甩一句 unrecognized option,不给你任何提示。
preset: 是什么:装机时按厂商策略自动设定的默认值(Rocky/RHEL 一般是 enabled)。它只解释「这个状态是怎么来的」,不影响你现在的操作。
disable 不动进程
ls -l /etc/systemd/system/multi-user.target.wants/crond.service
systemctl disable crond
systemctl is-enabled crond
systemctl is-active  crond
ls -l /etc/systemd/system/multi-user.target.wants/crond.service
成功长什么样:动手前能看到 crond.service -> /usr/lib/systemd/system/crond.service 的软链接;disable 打印 Removed "/etc/systemd/system/multi-user.target.wants/crond.service".;之后是 disabled + active;最后 ls 报 No such file or directory。
disable 只做一件事:删掉 wants/ 目录里那个指向 unit 文件的软链接。它不碰 /usr/lib/systemd/system/crond.service,也不碰正在运行的进程。所以「服务还在跑」和「不自启」同时成立,很正常。
stop 不动自启
systemctl stop crond
systemctl is-enabled crond
systemctl is-active  crond
systemctl start crond
systemctl enable crond
systemctl is-enabled crond; systemctl is-active crond
成功长什么样:stop 之后是 enabled + inactive (dead),status 里 Main PID 那行消失、出现 Duration: 2d 2h 50min;start 之后 Main PID 换成一个新数字(原来的进程已经退出);最后两行回到 enabled + active。
只拿 crond 练,不要动 sshd。你是通过 SSH 连进去的,systemctl stop sshd 会当场断开,而且它不会自己回来。
PID 一定会变:start / restart 都是「新建进程」,不是「恢复原进程」。看到 PID 变了不是异常,是必然。
/etc 遮盖 /usr/lib
systemctl mask crond
systemctl is-enabled crond
systemctl enable crond
systemctl start crond
systemctl status crond --no-pager -l | head -6
ls -l /etc/systemd/system/crond.service
成功长什么样:mask 打印 Created symlink /etc/systemd/system/crond.service -> /dev/null.;is-enabled 返回 masked;enable 失败并说 Failed to enable unit: Unit file /etc/systemd/system/crond.service is masked.;start 失败并说 Failed to start crond.service: Unit crond.service is masked.;status 显示 Loaded: masked (Reason: Unit crond.service is masked.) 但 Active: active (running)。
为什么 enable 也会失败:enable 要先读 unit 的 [Install] 段,才知道该往哪个 target 的 wants/ 里建链接。mask 之后这个路径指向 /dev/null,内容是空的,读不到 WantedBy= —— 于是无从下手。顺序只能是 unmask → enable。
两个同名文件,别搞混:/etc/systemd/system/multi-user.target.wants/crond.service 是开机自启的链接;/etc/systemd/system/crond.service 是 mask 的遮盖。在 wants/ 目录里 ls 看到还指向 /usr/lib,不代表 mask 没生效。
mask 不杀进程:正在跑的 crond 依然是 active。mask 只回答「以后还能不能启动它」,不回答「现在要不要停」。日志里会多一条 crond.service: Current command vanished from the unit file —— 这是 systemd 发现 ExecStart= 不见了,只警告、不终止。
wants 链接一直没动
systemctl unmask crond
systemctl is-enabled crond
systemctl is-active crond
ls -l /etc/systemd/system/crond.service
systemctl status crond --no-pager -l | head -4
成功长什么样:unmask 打印 Removed "/etc/systemd/system/crond.service".;随后 enabled + active;ls 报 No such file or directory(遮盖已删);status 回到 Loaded: loaded (/usr/lib/systemd/system/crond.service; enabled; ...)。
关键:unmask 删的只是 /etc/systemd/system/crond.service -> /dev/null 这个遮盖。wants/ 里的自启链接从 mask 到 unmask 全程没被碰过,所以一揭开就还是 enabled。
list-unit-files vs list-units
systemctl list-unit-files --type=service --no-legend | wc -l
systemctl list-unit-files --type=service --state=enabled --no-legend | wc -l
systemctl list-units  --type=service --state=running --no-legend | wc -l
systemctl list-units  --type=service --all --no-legend | wc -l
成功长什么样:四个数依次约 157 / 23 / 16 / 110(服务装得不一样会浮动,重点是理解口径)。
口径:list-unit-files 数的是磁盘上的 unit 文件(静态视角,含没加载的);list-units 数的是已经加载进内存的 unit(运行时视角,默认只列 active,--all 才全看)。所以 16 和 23 分属两个集合,本来就不该相等。
★ 这条是今天最坑的一处:list-unit-files --state=running 会输出 0 unit files listed.、stderr 一片空白、退出码 1。因为 running 是 sub-state,list-unit-files 不认;但如果你接了 | wc -l,退出码换成 wc 的 0,你只会看到一个「0」,看着像「没有服务在跑」。
正确做法:--state=running 只配 list-units;要判成败,别接管道。
盯住 MainPID
systemctl show -p MainPID --value crond
systemctl restart crond
systemctl show -p MainPID --value crond
systemctl show -p CanReload crond
systemctl reload crond
systemctl show -p MainPID --value crond
成功长什么样:restart 前后 PID 一定不同(实测 7565 → 7922);reload 前后 PID 一定相同(实测 8025 → 8025);CanReload=yes。
机制:restart = stop + start,旧进程被杀、新进程拉起,PID 必然变。reload 不杀进程,只按 unit 里的 ExecReload= 给现有进程发信号(crond 是 /bin/kill -HUP $MAINPID),进程本身没死,PID 自然不变。
reload 不是万能的:unit 里没写 ExecReload= 就不支持。先查 systemctl show -p CanReload X(yes/no)。硬要 reload 会得到 Failed to reload X: Job type reload is not applicable for unit X.,退出码 3(不是 1)。本机实测:crond、sshd 是 yes,chronyd、dnf-makecache 是 no。
考试怎么选:改监听地址/端口、换了被监听的 socket → restart;只是重读配置(日志级别、白名单)→ reload,不断连接。
enable --now / disable --now
systemctl disable --now crond
systemctl is-enabled crond; systemctl is-active crond
systemctl enable --now crond
systemctl is-enabled crond; systemctl is-active crond
成功长什么样:disable --now 之后 disabled + inactive;enable --now 之后 enabled + active。会同时打印 Removed ... / Created symlink ...。
等价关系:enable --now = enable + start;disable --now = disable + stop。--now 只负责「顺手把另一个维度也做了」,没有新增语义。
所以 enable --now 不等于 restart:服务已经在跑时,它不会再起一次,PID 不变。
考试省时间:装完 httpd 要「开机自启且现在就跑」,一条 systemctl enable --now httpd 就够,别写成两条。
enable 返回 0 却没生效
systemctl is-enabled dnf-makecache.service; echo "exit=$?"
systemctl cat dnf-makecache.service | grep -n Install; echo "grep_exit=$?"
systemctl enable dnf-makecache.service; echo "enable_exit=$?"
systemctl is-enabled dnf-makecache.service
ls /etc/systemd/system/multi-user.target.wants/ | grep makecache; echo "ls_exit=$?"
成功长什么样:is-enabled 输出 static、exit=0;grep Install 无输出、exit=1(证明没有 [Install] 段);enable 往 stderr 打一大段 The unit files have no installation config (WantedBy=, RequiredBy=, Also=, Alias= ...),但 enable_exit=0;最后 is-enabled 仍是 static,ls | grep 找不到链接。
static 的含义:unit 文件里没有 [Install] 段。而 enable 的全部工作就是读 [Install] 里的 WantedBy= 去建链接 —— 没有这段,它不知道往哪儿建,所以「没法被 enable」。这种 unit 只能:被别的 unit 用 Wants=/Requires= 拉起来,或者手动 start。
★ 又一个「验收命令会骗人」:systemctl enable 退出码 0 ≠ 启用成功。因为它认为「这个 unit 不需要安装配置」不算错误。判断是否真生效,只能看 is-enabled 的结果和 wants/ 里有没有新链接。
反过来:is-enabled 对 enabled 和 static 都返回 0,光看退出码区分不出来,必须看文本。
systemctl 不校验 state 取值
systemctl list-unit-files --type=service --state=enable; echo "exit=$?"
systemctl list-unit-files --type=service --state=enable | wc -l; echo "pipe_exit=$?"
systemctl list-unit-files --type=service --state=enabled --no-legend | wc -l
成功长什么样:前两条都打印 0 unit files listed.,但第一条 exit=1、第二条 pipe_exit=0;第三条是 23(正确拼写)。
为什么会这样:合法值是形容词 enabled,不是动词 enable。而 --state 对取值不做白名单校验 —— 它拿这个字符串去过滤,匹配不上任何 unit,就是 0 个,stderr 一个字节都没有(实测 err_size=0)。它不会告诉你「你拼错了」。
同类:--state=running 也是 0 个(T6)。记住一句话 —— systemctl 的 --state 是过滤条件,不是校验器;零结果先怀疑自己写错,别怀疑机器。

2 · target 与默认启动目标 考纲:配置系统自动启动进入某个特定目标

get-default 读的就是这条软链
systemctl get-default
ls -l /etc/systemd/system/default.target
readlink -f /etc/systemd/system/default.target
systemctl cat multi-user.target | head -20
成功长什么样:get-default 打印 multi-user.target;ls -l 显示 /etc/systemd/system/default.target -> /usr/lib/systemd/system/multi-user.target;readlink -f 把这条链一路解析到底。
机制:开机时 systemd 只启动一个东西 —— default.target。它本身没有进程、没有 ExecStart=,只是一条软链接,指向真正的目标(这里是 multi-user.target)。systemd 顺着链接找到它,再按它的 Wants=/Requires= 把一整片服务拉起来。
所以「换默认运行级别」在文件系统上就是换一条软链接,没有别的魔法。
两个位置:/etc/systemd/system/default.target 是管理员可改的(set-default 写这里);/usr/lib/systemd/system/ 是发行版自带的。前者优先级更高。你改 /usr/lib 里的东西,下次包升级会被覆盖。
幂等
systemctl set-default multi-user.target; echo "exit=$?"
ls -l /etc/systemd/system/default.target
成功长什么样:一行输出都没有,exit=0,链接的时间戳不变(说明没重建)。
判据:set-default 发现目标已经是它,就直接返回。想确认真的生效,不能看「有没有输出」,要看 get-default 和 ls -l 的结果 —— 无输出既可能是成功,也可能是没执行。
依赖树
systemctl set-default graphical.target; echo "exit=$?"
ls -l /etc/systemd/system/default.target; systemctl get-default
systemctl list-dependencies graphical.target --no-pager | head -20
systemctl set-default multi-user.target; echo "exit=$?"
systemctl get-default
成功长什么样:切换时打印两行 —— Removed "/etc/systemd/system/default.target". 再 Created symlink ... → ...graphical.target.;get-default 立刻跟着变;依赖树第一层是 display-manager.service、systemd-update-utmp-runlevel.service、multi-user.target。最后必须切回 multi-user.target。
包含关系:graphical.target 在 multi-user.target 之上再加一个图形登录管理器(display-manager.service)。所以设成 graphical 不会丢掉 multi-user 那一层 —— 它是超集,不是替代品。
本机的实际情况:/usr/lib/systemd/system/display-manager.service 不存在(Minimal 没装桌面),list-units --all 里它是 not-found inactive dead。所以即使设成 graphical,开机也只是「想拉但拉不到」,照样进 multi-user。不要用「有没有图形界面」判断 set-default 成没成功,要看软链接。
两条互不通信的路
systemctl isolate multi-user.target; echo "exit=$?"
systemctl get-default
ls -l /etc/systemd/system/default.target
systemctl show -p AllowIsolate multi-user.target rescue.target
成功长什么样:isolate 无输出、exit 0;get-default 和链接都不变;AllowIsolate 两个都是 yes。
和 stop/enable 一模一样的结构:isolate 只切换当前运行目标(会停掉不属于新目标的服务),不碰 default.target 链接;set-default 只改下次开机用的链接,不动当前运行状态。两者正交。
不要远程 isolate rescue/emergency:rescue.target 只拉起 sysinit.target + rescue.service,网络、sshd 全部不在里面 —— 从 SSH 执行会立刻失联。这类操作只能在 VMware 控制台做。手册里只观察依赖树,不动手。

3 · journald:日志在哪、怎么筛、怎么留 考纲:定位并解读系统日志 / 保留系统日志

journalctl --disk-usage
ls /run/systemd/journal/
ls /run/log/journal/
ls -d /var/log/journal; echo "exit=$?"
journalctl --disk-usage
成功长什么样:第一条只有 socket dev-log stdout streams 这类文件;第二条是 <machine-id> 目录,里面躺着一堆 *.journal;第三条报 No such file or directory(exit=2);--disk-usage 打印 Archived and active journals take up 3.3M 之类的数字(本机实测约 3.3M)。
三个路径别混:/run/systemd/journal/ 是 socket 目录(进程把日志交给 journald 的通道),不是数据;数据在 /run/log/journal/<machine-id>/;/var/log/journal/ 是持久化目录,不存在就说明现在没开持久。
★ 记住这条因果:/run 是 tmpfs,内存里的东西 ⇒ 默认重启即丢。所以「日志重启后没了」不是故障,是默认行为。
journalctl -p 3 / -p 2
journalctl -p 3 --no-pager | wc -l
journalctl -p 2 --no-pager | wc -l
journalctl -p err --no-pager | wc -l
journalctl -p crit -n 5 --no-pager
成功长什么样:-p 3 与 -p err 条数完全相同(本机 13 行),-p 2 一定更少(7 行);-p crit -n 5 只出 5 行。
级别表:0 emerg / 1 alert / 2 crit / 3 err / 4 warning / 5 notice / 6 info / 7 debug。-p err 的含义是「err 及更严重」,所以它包含 emerg / alert / crit / err 四档。
数字和字母是同一个东西的两种写法,-p 3 == -p err。
为什么条数会变少:数字越小越严重,能满足「≤ 2」的记录必然比「≤ 3」少。这是包含关系,不是过滤得更狠。
logger -p debug
logger -p debug -t labdebug "debug line only"
journalctl -t labdebug --no-pager
grep labdebug /var/log/messages; echo "grep_exit=$?"
logger -p info -t labinfo "info line"
grep labinfo /var/log/messages | tail -1
grep -n "messages" /etc/rsyslog.conf
成功长什么样:journalctl -t labdebug 能看到 debug line only;grep labdebug /var/log/messages 什么都没有、exit=1;换成 -p info 立刻就在 messages 里 grep 得到;rsyslog.conf 里能看到 *.info;mail.none;authpriv.none;cron.none  /var/log/messages。
管线:进程 / 内核 → systemd-journald → imjournal 模块 → rsyslog → /var/log/messages / secure / cron。rsyslog 是 journald 的下游消费者,不是平行的一套。
*.info 是「info 及以上」⇒ debug 在这一层被丢掉,只留在 journal 里。所以「两边都有」是常态,想找只进 journal 的证据必须挑它过滤掉的级别。
试错记录:一开始我用 logger -t labtest "hello" 演示,默认 priority 是 info,两边都有,等于什么都没证明。给演示命令前先验证它能证明标题里的结论。
-o json-pretty / -F / 字段=值
journalctl -o json-pretty -n 1 --no-pager
journalctl -F _SYSTEMD_UNIT | head
journalctl _SYSTEMD_UNIT=crond.service --no-pager | tail -5
journalctl -u crond --since "1 hour ago" --no-pager
成功长什么样:json 里能看到 _PID _UID _COMM PRIORITY _SYSTEMD_UNIT _BOOT_ID;-F 列出该字段出现过的所有取值;按字段过滤的结果与 -u crond 一致。
差别在哪:journald 存的是结构化记录,每行日志除了 MESSAGE 还带一堆元数据。grep 只能搜文本,没法区分「谁产的、哪个 boot、什么优先级」;按字段过滤能在源头筛,不会把别的 unit 里同样字样的行一起捞进来。
考试够用的三个维度:-u(哪个 unit)、-p(多严重)、--since/--until/-b(哪段时间 / 本次启动)。三个可以叠用。
又一个会骗人的验收
ls -d /var/log/journal; echo "before_exit=$?"
journalctl --flush; echo "flush_exit=$?"
ls -d /var/log/journal; echo "after_exit=$?"
成功长什么样:before_exit=2(目录不存在);--flush 一行输出都没有、flush_exit=0;after_exit 仍然是 2 —— 目录压根没被创建。
它在干什么:--flush 只把 /run/log/journal 里已有的记录刷进 /var/log/journal。目标目录不存在时它无事可做,但「无事可做」不算错误,于是静默返回 0。
★ 不能当验收:零输出 + 退出码 0 在这里什么都没证明。判据是 ls /var/log/journal/*/ 里有没有 *.journal 文件、--disk-usage 的数字有没有变大。
已在本机跑通
mkdir -p /var/log/journal
ls -ld /var/log/journal
systemd-tmpfiles --create --prefix /var/log/journal
ls -ld /var/log/journal
systemctl restart systemd-journald
journalctl -u systemd-journald -b --no-pager | grep "Journal ("
ls -ld /var/log/journal/*/
ls -l /var/log/journal/*/*.journal
journalctl --disk-usage
成功长什么样(本机实测,逐条对):
① 第一次 ls -ld:drwxr-xr-x. root root —— 755,没有 s 位、没有 +
② tmpfiles 之后:drwxr-sr-x+ root systemd-journal —— 2755 + ACL(+ 就是 ACL 的标记)
③ 重启后 grep 那行:从 Runtime Journal (/run/log/journal/...) 变成 System Journal (/var/log/journal/<machine-id>) is 8.0M, max 1.0G
④ ls 出现 <machine-id>/system.journal,大小 8388608(8 MiB)
⑤ --disk-usage 从 1.6M 变 8.0M
为什么 mkdir 就够:Storage= 默认是 auto(配置里通常是注释掉的),语义只有一句 —— /var/log/journal 存在才持久化。所以要动手的是目录,不是配置项。
★ 权限不是装饰:systemd-tmpfiles --create 这一步不能省。本机实测:把 natasha 加进 systemd-journal 组后,2755 + ACL 的目录下她能读(journalctl 退出 0);而 Storage=persistent 自动建出来的 755 root:root 目录下,她照样读不到(No journal files were opened due to insufficient permissions,退出 1)。
★ 修正旧说法:本手册上一版写「restart 之后目录还是空的,要 --flush 才出现」——不准确。本机重测:restart 一返回,<machine-id>/ 和 8MB 的 system.journal 当场就在。--flush 干的是「把 /run 里已有记录搬过去」,而 restart 启动时自己已经刷过一遍了,所以再敲 --flush 一行输出都没有。
判据用 grep "Journal (" 那行 Runtime→System 的切换,别用「目录里有没有文件」。
但权限是 root:root
cp /etc/systemd/journald.conf /tmp/jc.bak
mv /var/log/journal /tmp/jw
sed -i '/^Storage=/d' /etc/systemd/journald.conf
sed -i 's/^\[Journal\]/[Journal]\nStorage=persistent/' /etc/systemd/journald.conf
systemctl restart systemd-journald
ls -ld /var/log/journal /var/log/journal/*/
ls -l /var/log/journal/*/*.journal
systemd-analyze cat-config systemd/journald.conf | tail -6
# 还原
mv /var/log/journal /tmp/jauto; mv /tmp/jw /var/log/journal
cp /tmp/jc.bak /etc/systemd/journald.conf; systemctl restart systemd-journald
rm -rf /tmp/jauto /tmp/jc.bak
成功长什么样:/var/log/journal 和 <machine-id>/ 自动出现(你根本没 mkdir),但权限是 drwxr-xr-x. root root、文件 -rw-r-----. root root —— 没有 s 位、没有 +。cat-config 尾部能看到 Storage=persistent。
两条路的区别只有权限:
· Storage=persistent:journald 自己 mkdir,来不及跑 tmpfiles ⇒ 只有 root 能读
· mkdir + systemd-tmpfiles --create:目录是 2755 root:systemd-journal + ACL ⇒ 组员也能读
考试两种都算对;生产上建议后者(或补一次 systemd-tmpfiles --create)。
systemd-analyze cat-config 才是「配置到底生效了没」的正确验收:它把主文件 + 所有 .conf.d/*.conf 合并后打印出来。cat /etc/systemd/journald.conf 只能看到一份,看不到 drop-in。
--vacuum-size / -time / -files
journalctl --rotate
ls -l /var/log/journal/*/*.journal
du -h /var/log/journal/*/*.journal
journalctl --disk-usage
journalctl --vacuum-size=1M
journalctl --disk-usage
journalctl --vacuum-time=1s
journalctl --vacuum-files=1
成功长什么样(本机实测数字):
· --rotate 后多出一个 system@<machine-id>-0000…-00065f81….journal
· ls -l:归档文件 8388608,活动文件也是 8388608 —— 两个都显示 8MB
· du -h:归档 3.7M,活动 8.0M —— 同一个文件,两个命令两个答案
· --disk-usage:11.6M(8.0 + 3.6)
· --vacuum-size=1M:Deleted archived journal … (3.6M) + freed 3.6M,之后 --disk-usage = 8.0M
· --vacuum-time=1s:freed 0B(没有归档文件可删了)
为什么 ls 和 du 不一样:journal 文件是按固定大小预分配的(这里是 8 MiB 一个槽),文件名里的 8388608 是表观大小;归档时只保留实际写过的块,du 看到的是实际占用(3.7M)。--disk-usage 统计的是实际块。
★ --vacuum-* 永远删不到 0:它只删归档文件(system@…),正在写的 system.journal 一个都不碰。所以活动文件有多大,--disk-usage 的下限就是多大。别拿「删完还是 8.0M」判断 vacuum 失败。
三个 vacuum 的区别:--vacuum-size= 按总量砍、--vacuum-time= 按时间砍、--vacuum-files= 按文件个数砍。都只作用于归档文件,都需要 --rotate 先把活动文件切出去。本机 --vacuum-files=1 时只剩一个活动文件,输出 freed 0B。
用 drop-in,不改进主配置
mkdir -p /etc/systemd/journald.conf.d
printf '[Journal]\nSystemMaxUse=20M\n' > /etc/systemd/journald.conf.d/99-lab.conf
systemctl restart systemd-journald
journalctl -u systemd-journald -b --no-pager | grep "Journal ("
systemd-analyze cat-config systemd/journald.conf | tail -6
journalctl --rotate; ls -l /var/log/journal/*/system.journal
# 还原
rm /etc/systemd/journald.conf.d/99-lab.conf
systemctl restart systemd-journald
成功长什么样(本机全文):
· 默认:System Journal (…) is 6.2M, max 1.0G, 1.0G free.
· 加 drop-in 后:is 6.2M, max 20.0M, 13.7M free.
· cat-config 尾部多出一段 # /etc/systemd/journald.conf.d/99-lab.conf + SystemMaxUse=20M
· rotate 后新的活动文件变成 2621440(2.5 MiB)
为什么新文件是 2.5M:SystemMaxFileSize 没写时默认 = SystemMaxUse / 8。20M ÷ 8 = 2.5M,实测 2621440 正好对上。调小 SystemMaxUse 会连带把单个文件切小。
两个维度别混:SystemMaxUse= 限总量(按空间淘汰),MaxRetentionSec= 限保留时长(按时间淘汰,如 1week)。--vacuum-* 是立即执行一次,写进配置才是以后一直执行。
★ 为什么用 drop-in 而不是改 /etc/systemd/journald.conf:drop-in 好还原、升级不冲突、一个文件一件事。但验收必须用 systemd-analyze cat-config —— 只看主文件会以为「我没改过」,实际被 drop-in 改了。
★ 撤销时踩的坑(本机实测):root 的 rm 是 alias rm='rm -i'(写在 /root/.bashrc:20)。敲 rm /etc/systemd/journald.conf.d/99-lab.conf 会停在 rm: remove regular file …?,你紧接着敲的那条命令会被当成它的回答 —— 不是 y 就等于「不删」,而且那条命令根本不会执行。实测结果:drop-in 留了下来、journald 的 MainPID 还是上一次重启的,max 一直显示 20.0M,看起来像「配置改不回去」,其实是既没删也没重启。
绕法:\rm 文件(反斜杠跳过别名)或 rm -f 文件;删完 ls 确认空目录,再 restart。
写 drop-in 别用 echo:echo '[Journal]\nSystemMaxUse=20M' 会原样写进 \n(bash 的 echo 默认不解释转义),cat 出来是一行带反斜杠的乱内容。用 printf(本步给的写法)或 echo -e。

4 · 附录:命令速查 按「我想干什么」查

我想…命令注意
看它现在跑不跑systemctl is-active X只看进程,不看自启
看它开机会不会自己起systemctl is-enabled X只看链接,不看进程
设成开机自启并立刻启动systemctl enable --now X等于 enable + start
取消自启并立刻停止systemctl disable --now X等于 disable + stop
彻底封死,连手动启都不行systemctl mask X先 unmask 才能 enable
看这个服务最近干了什么systemctl status X尾部日志是摘要,完整要用 journalctl
看 unit 文件全文systemctl cat X会显示实际生效的路径
看它挂在哪个 target 下systemctl show -p WantedBy X决定 enable 往哪建链接
重启进程(会断连接)systemctl restart XPID 一定变
只重读配置(不断连接)systemctl reload XPID 不变;先查 CanReload
这个服务支不支持 reloadsystemctl show -p CanReload Xno 时硬 reload 退出码 3
它为什么不能 enablesystemctl is-enabled X返回 static 就是没有 [Install] 段
看当前默认启动目标systemctl get-default读的是 default.target 软链
改下次开机的目标systemctl set-default X.target只改链接,不影响当前
立刻切到某个目标systemctl isolate X.target只改现在,不改默认;rescue 会断网
看某个目标包含哪些东西systemctl list-dependencies X.target看的是依赖树,不是配置
查最近日志journalctl -n 50 --no-pager不加 --no-pager 会进 less
只看某个服务的journalctl -u X等价于按 _SYSTEMD_UNIT 过滤
只看错误及以上journalctl -p err是「≤ err」,含 crit/alert/emerg
只看本次开机journalctl -b-b -1 是上一次
看日志占多大journalctl --disk-usage统计实际块,不是 ls 的表观大小
让日志重启后还在mkdir /var/log/journal + systemd-tmpfiles --create --prefix /var/log/journal + restart journaldtmpfiles 那步决定组员能不能读
立即清理日志journalctl --rotate 再 --vacuum-size=100M只删归档,活动文件不碰
永久限制日志体积SystemMaxUse= 写进 drop-in 再 restart用 systemd-analyze cat-config 验收

5 · 自测 先自己答,再展开

1. disable 之后服务还在跑,正常吗?

正常。disable 只删 wants/ 里的软链接,影响的是「下次开机」,不影响当前进程。要停得另外 stop。

2. stop 之后 is-enabled 为什么还是 enabled?

因为 stop 走的是进程控制路径,enable 走的是链接路径,两者根本不通信。

3. mask 之后为什么连 enable 都失败?

enable 需要读 unit 的 [Install] 段才知道往哪建链接;mask 让该路径指向 /dev/null,读不到内容,于是无从下手。必须 unmask 先。

4. unmask 之后为什么直接回到 enabled 而不是 disabled?

因为 wants/ 里的自启链接在 mask 期间从未被删除。unmask 只删掉 /etc/systemd/system/X -> /dev/null 这个遮盖。

5. list-unit-files --state=running 为什么返回 0?

running 是 sub-state,list-unit-files 不接受;它静默返回 0 unit files listed. 并给退出码 1。running 只能配 list-units。

6. static 的服务能 enable 吗?

不能。static 表示 unit 文件里没有 [Install] 段,enable 没有可写的目标,会报 The unit files have no installation config。本机例子:dnf-makecache.service。但注意它的退出码是 0 —— 不能靠 $? 判断。

7. restart 和 reload 怎么选?

restart 杀进程重开,PID 变,连接全断;reload 只给现有进程发信号(如 HUP),PID 不变,连接不断。改端口/换 socket 必须 restart;只改配置内容优先 reload,但要先用 show -p CanReload 确认支持。

8. enable --now 等于什么?服务已经在跑时它会不会重启它?

等于 enable + start。已经在跑时 start 是空操作,不会重启,PID 不变。要换进程得用 restart 或 try-restart。

9. --state=enable 和 --state=enabled 差在哪?

前者是拼错的非法值,但 systemctl 不校验,直接过滤出 0 个、stderr 全空、退出码 1;后者才是合法值(23 个)。--state 只是过滤器,不是校验器。

10. default.target 是什么?get-default 看的是什么?

它不是服务,是一条软链接(/etc/systemd/system/default.target),指向真正的 target(本机是 multi-user.target)。get-default 就是读这条链接,所以它和 ls -l 看的是同一个对象。

11. graphical.target 和 multi-user.target 谁包含谁?

graphical 包含 multi-user(依赖树里 multi-user 是它的子项),只多了一个 display-manager.service。所以设成 graphical 不会丢服务。

12. isolate 之后 get-default 会变吗?

不会。isolate 换的是当前运行目标,set-default 改的是下次开机的链接,两条路互不通信。和 stop / enable 的关系完全同构。

13. 什么时候该用 rescue / emergency?

rescue:挂载本地文件系统、拉起 sysinit.target + rescue.service(单用户、要 root 密码),修 fstab、改服务配置。emergency:只起 emergency.service,连本地文件系统都只读挂 /,用于根分区都起不来的情况。两者都没有网络和 sshd,只能走控制台。

14. restart systemd-journald 之后 /var/log/journal 里就一定有 *.journal 吗?

有。<machine-id>/system.journal 在 journald 启动时就建好并预分配(本机 8 MiB),不需要额外 --flush。判据看 journalctl -u systemd-journald -b | grep "Journal (" 那行是 Runtime 还是 System。

15. --disk-usage 从 1.6M 变 8.0M,是日志条目变多了吗?

不是。新建的 system.journal 预分配 8 MiB 固定槽位,而里面当时只有 17 行记录。变的是预分配的空间,不是内容。ls -l 看表观大小、du 看实际块,--disk-usage 跟的是实际块。

16. --vacuum-size=1M 打印 freed 3.6M,但 --disk-usage 还有 8.0M,失败了吗?

没失败。它删的是归档文件(system@…,实际占 3.6M),活动文件 system.journal 本身占 8.0M 且永远不会被 vacuum 删掉。总量从 11.6M 降到 8.0M,正好少 3.6M。

17. Storage=persistent 和 mkdir + systemd-tmpfiles 差在哪?

都能持久化,差在权限:前者 journald 自己建目录 ⇒ 755 root:root,非 root 读不到;后者靠 tmpfiles 修成 2755 root:systemd-journal + ACL ⇒ systemd-journal 组成员能读(本机用 natasha 实测:前者 exit=1,后者 exit=0)。

18. 日志状态里那个 max 1.0G 是谁定的?

不是配置里写死的——journald.conf 里 #SystemMaxUse= 是注释掉的。它是默认算法:取 /var/log 所在文件系统大小的 10%。本机根分区 10875830272 字节 × 10% ≈ 1.01 GiB,所以显示 1.0G。SystemMaxFileSize 默认再取它的 1/8。改 SystemMaxUse 会连带改单文件大小(20M ⇒ 2.5M,实测 2621440)。

19. 敲 rm 文件 时它问了一句 remove regular file …?,文件删了吗?

看你回了什么。root 的 rm 是 alias rm='rm -i'(/root/.bashrc),回车不是 y 就不删;更坑的是你下一条命令会被它当成回答,于是那条命令也不会执行。绕法:\rm 文件 或 rm -f 文件。