学习日志 · 2026 年 11 月
11 月的主线:把存储域的 swap 收尾,正式转入「软件包与仓库」。这个月开始每个能力域配一本实操手册,随进度长。新内容在最上面,每条末尾的「能力索引」指向 RHCSA 学习地图。
2026-11-05 星期四
今天做了什么
- 把 S3 日志这块收了尾:
systemd-lab.html从 20 步补到 23 步,补的三步是「持久化」「保留策略」「上限配置」。 - 持久化实测两条路:
mkdir+systemd-tmpfiles,和Storage=persistent。两条都能让日志活过重启,差别只在目录权限 —— 而权限决定了组员能不能读。 - 保留策略跑全了:
--rotate切出归档,再分别用--vacuum-size/--vacuum-time/--vacuum-files裁。 - 上限用 drop-in 写
SystemMaxUse=20M,max从1.0G变成20.0M,rotate 后单文件从 8M 降到 2.5M。 - 顺手摸了 S4 的环境:
cronie已装、crond是 enabled + active、/etc/crontab在;at没装,atd.service都不存在,S4 第一步得先补它。
卡住的地方
- 「
restart之后/var/log/journal里有没有文件」,我原来的说法是「没有,要--flush才出现」。他说「并不空」—— 他是对的。重测:restart 一返回,<machine-id>/和 8MB 的system.journal当场就在。--flush干的是把/run里已有记录搬过去,而 restart 启动时自己已经刷过一遍,所以再敲--flush一行输出都没有。两次实测结论不一致时,要换一个更硬的判据,而不是挑自己更喜欢的那个。 --disk-usage从 1.6M 涨到 8.0M,我第一反应是「条目变多了」。其实那个 8MB 文件里当时只有 17 行 —— 涨的是预分配空间,不是内容。--vacuum-size=1M打印freed 3.6M,我看--disk-usage还有 8.0M 就以为没降到位。其实总量是从 11.6M 降到 8.0M,正好少 3.6M;活动文件永远不会被 vacuum 删掉,那 8.0M 是下限。- 撤销 drop-in 时被 root 的
rm -i卡住:敲rm之后它问remove regular file …?,我紧接着敲的systemctl restart systemd-journald被当成回答吃掉了 —— 文件没删、服务也没重启,max一直显示 20.0M,看着像「配置改不回去」。用\rm或rm -f才对。 echo '[Journal]\nSystemMaxUse=20M'会把\n原样写进文件,得用printf或echo -e。
关键命令或配置
1 | # 持久化:建目录 → 修权限 → 重启(不需要 --flush) |
1 | # 裁日志:先切归档,再裁 |
1 | # /etc/systemd/journald.conf.d/99-lab.conf |
1 | systemctl restart systemd-journald |
今天真正记住的判断
- 上限 ≠ 目标值:设了 1.0G,
--disk-usage停在 11.6M 是正常的 —— 没到阈值它不清理。 - 判配置是否生效看
MainPID变没变,比grep可靠。grep不加tail -1会读到上一条旧记录。 ls -l是表观大小,du是实际块:journal 文件按固定槽位预分配,归档后两个数能差一倍(8MB vs 3.7M)。cat /etc/xxx.conf只看到一份配置,systemd-analyze cat-config才能看到主文件 + 所有 drop-in 合并后的结果。
下一步
S4 计划任务:先把 at 装上(dnf install -y at + systemctl enable --now atd),再走 cron(crontab -e/-l/-r、/etc/cron.d、run-parts、/var/log/cron)→ systemd timer(.timer + .service 配对、OnCalendar、list-timers)。考纲三样都要。
能力索引 —— 今天涉及的知识点在地图上的位置:
2026-11-04 星期三
今天做了什么
- S1 服务生命周期一次跑完:
disable→stop/start→mask→unmask。四象限全造出来了,还自己多试了一步 —— mask 状态下enable也失败,不只是start。 - 用
MainPID把restart和reload分开取证:restart 7565 → 7922,reload 8025 → 8025。顺手查了CanReload,crond、sshd 是 yes,chronyd、dnf-makecache 是 no;硬 reload 一个 no 的 unit,退出码是 3,不是 1。 enable --now/disable --now实测;static这一类(本机 96 个)单独测了一轮。- S2 target:
default.target是一条软链、不是服务;set-default同值是幂等的;graphical是multi-user的超集;isolate和set-default各管一头。 - S3 前半 journald:日志到底存在哪、
-p的语义、debug 级只进 journal 不进 messages、结构化字段、--flush在没持久化时静默成功、systemd-tmpfiles造出来的目录权限。 - 手册侧:
systemd-lab.html今天从 6 步长到 14 步(服务生命周期 10 步 + target 4 步),日志这块再补 6 步。
卡住的地方
- 第 2 问「stop 之后为什么
is-enabled还是 enabled」,我答成了「is-enabled 是干什么的」。问的是为什么,答的是是什么 —— 正解是 stop 和 enable 改的是两个不同的东西,两条路径根本不通信。 --no-paper、-1:真参数是--no-pager、-l(小写 L)。打错时 systemd 只甩一句unrecognized option。list-unit-files --type=service --state=running→0 unit files listed.,stderr 全空、退出码 1;接上| wc -l之后变成0加退出码 0。看着像「没有服务在跑」,其实是这个参数它不认 ——running是 sub-state,list-unit-files只收 load state 和 active state。ls -l $pwd/crond.service→/crond.service。shell 变量是大写$PWD,$pwd未定义就静默展开成空串(bash 默认行为,set -u才会报)。- 在 wants 目录里
wc -l crond.service得到 15 ——wc跟随软链读了真 unit 文件,而ls -l看的是链接本身。同一条路径,不同命令看不同层。 - mask 之后在 wants 目录
ls,看到还指向/usr/lib,一度以为 mask 没生效。其实那是自启链接,mask 盖的是/etc/systemd/system/crond.service这个同名文件,两个不同的东西。 --state=enable(少一个 d)同样返回 0 个、stderr 空。- 我给「只进 journal、不进 messages」写的演示命令是
logger -t labtest "hello",默认 priority 是 info,两边都有。cr 一句「不都有吗」就把我堵住了。真正的例子是 debug 级。给演示命令之前,必须先验证它真能证明标题里的结论。 journalctl --flush在/var/log/journal不存在时:零输出、退出码 0,什么也没做。
关键命令或配置
1 | # mask 之后:Loaded 是 masked,Active 还是 active |
1 | # restart 换进程,reload 不换 |
1 | # static:没有 [Install] 段,enable 返回 0 却什么都没建 |
1 | # target 是一条软链,不是服务 |
1 | # 日志:先确认持久化开没开 |
几条今天真正记住的判断
- stop / enable 是两条不通信的路径:一条动进程,一条动 wants 里的链接。
isolate和set-default是同一个结构 —— 一个管现在,一个管下次。 - 这一域的退出码几乎不能当判据:
enable一个 static 服务返回 0 却什么都没建;is-enabled对 enabled 和 static 也都返回 0,只看文本才分得出来。 --state是过滤器,不是校验器。零结果先怀疑参数拼错,别怀疑机器。- mask 是「禁止存在」,不是「禁止自启」:
Loaded: masked的同时Active: active (running)是对的,它只回答「以后还能不能启动」。 -p err是「≤ err」,不是「= err」。数字越小越严重,-p 2一定比-p 3少。- journald 是上游,rsyslog 是下游消费者:进程 → journald → imjournal → rsyslog → messages / secure / cron。所以「两边都有」是常态,要找只进 journal 的东西得挑它过滤掉的级别(debug)。
/run是 tmpfs,所以默认日志重启就丢。持久化的开关不是改某个配置项,而是建不建/var/log/journal这个目录。
下一步
- 日志持久化实操:
mkdir /var/log/journal→systemd-tmpfiles --create --prefix /var/log/journal→restart systemd-journald→--flush→ 验证目录里真有文件。今天只验到 tmpfiles 把权限改成root:systemd-journal 2755,journald 还没重启过。 - 保留策略:
SystemMaxUse=/MaxRetentionSec=/--vacuum-size/--vacuum-time/--rotate。 - 然后 S4:cron / at / systemd timer。
今天的配套手册
- systemd 实操手册 —— 今天长到 20 步:第 1 章服务生命周期(T1–T10)、第 2 章 target(T11–T14)、第 3 章 journald(T15–T20,最后一步持久化明确标了「尚未在本机验证」)。
- 实操手册总览 —— 所有手册的入口和进度。
能力索引 —— 今天涉及的知识点在地图上的位置:
2026-11-03 星期二
今天做了什么
- 软件包域 P12–P16 一次走完:
history undo、versionlock、provides、软件包组、module 流。其中 provides 三条、module 那块(enable → install → list –installed → reset)都对了。 - 但 versionlock 连着三次实验都不成立,最后干脆自己上机(root 免密)把疑点一个个测掉:锁只作用在求解层、
--disableexcludes=all绕不过去、显式写全 NVRA 也被过滤、对没安装的包执行 add 会把仓库里所有版本一起锁上。 - 顺手把 rpm 直接操作 P17–P21 在实机上全跑了一遍(
rpm -ql/-qc/-qR、rpm -V三态、rpm2cpio还原、--test试穿、卸载边界)。 - 查了 EX200 考纲原文:
rpm -V/rpm2cpio/--nodeps都不在考纲里,考纲说的「从本地文件系统安装」标准做法是dnf install /路径/x.rpm。还有一条更要紧的:EX200 现在已经基于 RHEL 10,实验机是 Rocky 9,练手可以,但 dnf5 和 module 的差异要以 RHEL 10 为准。 - 开新域 systemd:先取基线(
get-default= multi-user.target、四个服务全是 enabled+active、multi-user.target.wants/里全是符号链接),环境太干净没有现成样本,就用 crond 人为造出「disabled 但 active」的对照,把 mask 的机制测通了。 - 博客侧:做了「实操手册总览」页,又把手册做成正式文章归到「教程」分类下;顺手修了一处浅色主题下正文发白的问题 ——
--light-grey在 Butterfly 浅色主题里是#eee,名字像文字色,实际是极浅的描边色。
卡住的地方
- 第一轮拿
tree测锁:dnf update tree输出Nothing to do.,我以为锁生效了。其实tree本来就是仓库里唯一版本 —— 拿一个没有升级路径的包测锁,等于没测。 - 第二轮我设计「锁前 / 锁后各跑一次
dnf list --upgrades」做对照,实测两次都显示 127。这个对照设计本身是错的,因为list走查询层,压根不看锁。 dnf history info latest不报「参数非法」,报No transaction which manipulates package 'latest' was found—— 那个位置也能被当成包名。正解是last。rpm2cpio x.rpm | cpio -idmv etc/ssh/sshd_config少写一个./:一个文件都没提取,却照样打印2992 blocks、退出码 0。rpm -ivh --test /tmp/nonexist.rpm | head之后echo $?是 0,去掉管道才是 1。管道把退出码换成了右边那个命令的。- 恢复 sshd_config 的 mtime 手写
20:05:00不行,真值20:05:41,最后靠touch -r借包内原文件的时间戳才让rpm -V彻底无输出。
关键命令或配置
1 | # versionlock 的正确取证:两层各取一次,缺一不可 |
1 | # rpm -V 的三态:只有第 3 位(内容摘要)亮才是真被改了 |
1 | # systemd:两个维度分开验,别用 status 一句话概括 |
几条今天真正记住的判断
- 「没报错」和「跑完了」都不是验证。 任何查询类输出(
Nothing to do、空列表、0 条结果)都得能回答一句:如果它有东西可做,会显示成什么样? 答不上来,这条命令就不能当验收。 - 同一份配置,会被不同层的命令给出不同答案。 判断「是否生效」前先问:这条命令属于哪一层?存储域里是
partedFlags(分区表层)vsblkid(设备签名层)、swap 的类型 GUID vsmkswap签名;软件包域里是list/repoquery(查询层)vsupdate/install(求解层),versionlock 只塞进后者。 - 命令的语义有一部分写在配置文件里。
clean_requirements_on_remove=True让dnf remove htop把hwloc-libs一起清掉,这个场景下 remove 与 undo 结果完全相同。undo 真正不可替代的是三种:升级后能降级、remove 后能装回、一个事务动了 N 个包能整体回退。 - 「现在跑着吗」和「开机自启吗」是两个独立问题。
is-active和is-enabled各答一个,四种组合都真实存在;enable的实质就是 wants 目录里那条符号链接。 - mask 不是「禁止自启」,是「禁止存在」 —— 往
/etc放一个指向/dev/null的空 unit,把/usr/lib里的真 unit 盖掉。unit 查找顺序/etc>/run>/usr/lib。 rpm -qc只列文件级的配置,目录永远不列(所以比-ql | grep ^/etc少一行);一次查多个包时,只要有一个没装,退出码就是 3。
下一步
- systemd 按依赖顺序走:S1 服务生命周期(样本已经备好)→ S2 target 与默认目标 → S3 journalctl 与日志持久化 → S4 cron / at / timer → S5 chronyd + timedatectl → S6 中断启动取 root。
- 范围只认考纲:自己写
.service文件、socket activation、cgroup 原理都标选做,不占主线。
今天的配套手册
- 软件包实操手册 —— P13 重写了:删掉那个「锁前锁后对比 list」的错误设计,换成「候选存在 + update 不动」的双层判据,并补上
--disableexcludes绕不过、未安装包 add 是陷阱。 - 实操手册总览 —— 所有手册的入口和进度都在这一页。
- 存储手册今天没动。
能力索引 —— 今天涉及的知识点在地图上的位置:
2026-11-02 星期一
今天做了什么
- 把存储域最后一块 swap 做完了(分区版全流程):
parted mkpart→partprobe→mkswap→blkid→ 写 fstab →swapon -a→ 验收。swapon --show出两行,free -h的 Swap 从 820M 变成 1.3Gi。还没重启验证,先挂着。 - 存储手册加了实验 D(swap 分区 + swap 文件,D1–D8);其中 swap 文件那一步只是写在纸上,本机还没做过。
- 开了新的能力域:软件包与仓库。先弄清「仓库到底是什么」,再搭一个本地
file://仓库,P1–P11 走完。 - 定下一条规矩:以后每做完一个能力域就配一本
<域>-lab.html,实验用独立小节隔开,随进度长 —— 存储的 A/B/C/D 就是模板。
卡住的地方
- 以为「把 rpm 放进目录,dnf 就能装」。实际 dnf 只去
<baseurl>/repodata/repomd.xml取索引,从不扫目录。故意先不跑createrepo_c试了一次,makecache报Curl error (37): Couldn't read a file:// file,正好对上。单个 rpm 走的是另一条路:dnf install ./x.rpm。 - 以为
mount -a会把 swap 也带上。结构上不可能:mount 需要一个挂载点,而 swap 的 fstab 第 2 字段就是none;内核里是mount(2)和swapon(2)两条不同的系统调用。 .repo里把enabled=1手滑写成enable=1,dnf 照跑不误 —— 它不校验未知键,而enabled默认值就是 1,等于蒙对了。★ 反方向才致命:写enable=0想关仓库会静默无效、毫无提示。grep找[crb]那一段:[在正则里是字符类起点,必须转义。试了grep '\$crb\$'、grep '\$crb\$'全无匹配,最后grep '\[crb\]'才对;最省事的是grep -F '[crb]'。
关键命令或配置
1 | # swap(分区版),起点照 parted print free 的结果写,不要照抄 |
1 | # 本地 file:// 仓库的最小闭环 |
几条今天真正记住的判断
- swap 有两套互不校验的身份:① 分区表里的类型标记(
parted写的,纯标签,print的 Flags 列能看到);② 分区开头约 4 KiB 的签名(mkswap写,blkid出TYPE="swap")。内核只认 ②,所以「Flags 里标了 swap」不能当验收。 swapon --show里系统盘显示/dev/dm-1是内核临时编号,重启可能变 dm-2 —— fstab 绝不能写 dm-N,要写UUID=或/dev/mapper/vg-lv。- 只写 fstab、不跑
swapon -a,重启照样生效:开机读 fstab 的不是 swapon 命令,而是 systemd-fstab-generator 把每行翻译成.swapunit。预演的价值在于把失败挪到有人看着的时候(命令行当场报错 vs 开机静默丢/进 emergency)。 dnf install那两行大小是两层:先 4.3 kB 取repomd.xml(索引目录),确认变了才拉 44 MB 的全量元数据。所以clean all之后必然两行,平时只有第一行才正常。--disablerepo="*"下装 htop 报nothing provides libhwloc.so.15,不是本地仓库坏:依赖求解只在「当前启用的仓库集合」里做,hwloc-libs 在 appstream 里被一起关掉了。这条命令只适合证明「某包来自本地仓库」。- 仓库跟着文件走,不跟名字走:把
rocky.repo改名,baseos和appstream一起消失,extras还在(它住在rocky-extras.repo)。实测 7 个.repo文件 → 48 个仓库定义 → 5 个启用,三个数字互不相等。 --enablerepo(只管这一条命令)、--set-enabled(改文件、持久)、--add-repo(写出新文件)三者别混。--set-enabled crb之后grep能看到enabled从 0 被改成 1 —— 这就是「临时」和「持久」的实证。
下一步
- 重启验 swap:
free -h+swapon --show;若只剩 820M,先journalctl -b | grep -i swap。 - 软件包从 P12 接:
dnf history/history undo→dnf provides→versionlock→ 组与 module 流 → rpm 直接操作。
今天的配套手册
- 存储实操手册 —— swap 已补成实验 D(D1–D8),存储域四个实验至此齐全。
- 软件包实操手册 —— 今天 P1–P11 走的就是这一本:本地
file://仓库、依赖跨仓库、--set-enabled与--enablerepo的区别都在里面。 - 实操手册总览 —— 所有手册的入口和进度,以后从这里进。
能力索引 —— 今天涉及的知识点在地图上的位置:
All articles on this blog are licensed under CC BY-NC-SA 4.0 unless otherwise stated.