LANUNION ARCHIVELANUNION蓝色札记

← 档案索引

ARCHIVE NO. 002

Ubuntu Server 例行巡检笔记:journal 占满根分区与内核待重启

CLASSIFICATIONLINUX
DATE2026.09.10
状态 STATUSDECLASSIFIED
READING TIME4 MIN
REVISION1.1

巡检背景

每季度要给机房那批 24.04 LTS 节点做一次例行检查。这批机器跑的是一套 内部时序数据库与几个常驻采集器,平时没什么人登,出了问题才发现已经积压很久。 这次的检查项是磁盘水位、待重启内核、以及无人值守升级的落地情况。

先做快照,再动手

巡检难免要清理东西,动手之前先留一份现场。用一段只读的采集脚本把状态落盘, 后面所有判断都基于这份快照,避免”清理之后想复现却复现不出来”:

TERMINAL / BASHLINE 01–17
set -euo pipefail
STAMP=$(date +%Y%m%d-%H%M)
OUT=/var/log/inspect/$STAMP
install -d -m 0750 "$OUT"

{
  echo "=== uptime ===";        uptime
  echo "=== disk ===";          df -hT
  echo "=== journal ===";       journalctl --disk-usage
  echo "=== timers ===";        systemctl list-timers --all --no-pager
  echo "=== held ===";          apt-mark showhold
  echo "=== running kernel ==="; uname -r
} > "$OUT/report.txt" 2>&1

# 把大的单元日志单独留一份,方便回看是谁在刷屏
journalctl --since "-30 days" --no-pager -o short-iso |
  gzip -9 > "$OUT/journal-30d.log.gz"

发现一:journal 吃掉了 41 GB

df -hT 显示根分区 92% 使用率。du 逐层下钻之后指向 /var/log/journal/——单机 41 GB 的 systemd 日志。

TERMINAL / PLAINTEXTLINE 01–02
$ journalctl --disk-usage
Archived and active journals take up 41.3G in the file system.

根因:默认配额是按比例的

systemd-journald 默认的 SystemMaxUse 取分区容量的 10%,而 SystemKeepFree 取 15%。这台机器的根分区有 400 GB,10% 就是 40 GB——日志刚好把它撑满, 不再增长,于是轮转卡在临界点上,/var/log/syslog 也开始丢条目。

真正把日志推起来的是一个配置写错的采集器:它的日志级别被留在了 DEBUG, 每次重连都往 journal 里灌几十行堆栈,一天下来上百万条。 配额不是根因,只是把根因盖住了。

处理:先定位噪声源,再收配额

顺序很重要。如果先 --vacuum-size 清空,那个 DEBUG 级别的采集器还会继续刷, 一周之后同一个坑再踩一次:

TERMINAL / BASHLINE 01–14
# 1. 找出刷得最凶的单元(按最近 30 天条目数排序)
journalctl --since "-30d" -o json --no-pager |
  jq -r '.UNIT // ._SYSTEMD_UNIT // "kernel"' |
  sort | uniq -c | sort -rn | head -10

# 2. 把噪声源降到 INFO 级别并重启
sudo systemctl edit collector-agent     # 追加 -log.level=info
sudo systemctl restart collector-agent

# 3. 确认每分钟新增条目回落到正常量级
journalctl --since "-5 min" --no-pager | wc -l

# 4. 再回收历史日志
sudo journalctl --vacuum-size=4G

配额通过 drop-in 固化,而不是直接改 /etc/systemd/journald.conf—— drop-in 在包升级时不会被覆盖,也更容易进版本库:

TERMINAL / BASHLINE 01–11
sudo install -d -m 0755 /etc/systemd/journald.conf.d
sudo tee /etc/systemd/journald.conf.d/99-archive.conf >/dev/null <<'CONF'
[Journal]
SystemMaxUse=4G
SystemKeepFree=20G
MaxRetentionSec=90day
Compress=yes
CONF

sudo systemctl restart systemd-journald
journalctl --disk-usage

一个容易漏掉的坑

SystemKeepFree 的意思是”至少给文件系统留这么多空闲”,它是硬下限。 如果分区已经快满,journald 会优先满足这一条,此时 SystemMaxUse 形同虚设, 你会看到磁盘用量在 90% 附近僵持、而日志量远小于配额。判断到底是哪一条在生效, 看 journalctl --disk-usage 的数字和分区剩余空间哪个先触线即可。

发现二:装了新内核,但跑的还是旧的

uname -r6.8.0-31-generic,而 /boot 里躺着 6.8.0-45-genericunattended-upgrades 在两周前的凌晨自动装了新内核,却因为 Automatic-Reboot 默认为 false 而没有重启,进程树仍然映射着旧内核的 /usr/lib/modules——被删掉的旧模块目录只是 inode 还活着而已。

这类”悬挂状态”最危险的地方在于它是静默的:安全更新显示已安装, 但漏洞其实还在内存里跑着。

确认与处理

TERMINAL / BASHLINE 01–08
# 哪些包需要重启才能生效(reboot-required 只覆盖内核与 libc 的一部分)
cat /var/run/reboot-required.pkgs 2>/dev/null

# 更细的粒度:逐个进程检查是否有已删除但仍被映射的库
sudo lsof -nP +L1 2>/dev/null | grep -E '/(lib|usr)' | head -20

# 选定维护窗口后重启
sudo systemctl reboot

在维护窗口前,先用 needrestart 摸清影响面。它在 Debian 系上默认以 list 模式运行,只会给出报告;如果被配成 a(自动重启服务), 在数据库主节点上会直接引发一次非计划主从切换:

TERMINAL / BASHLINE 01–02
sudo needrestart -b      # -b 批处理模式,只输出结论
# NEEDRESTART-KSTA: 3   ← 内核需要重启

顺手把定时任务确认一遍

TERMINAL / BASHLINE 01–01
systemctl list-timers --all --no-pager | grep -E 'apt|unattended'

输出里确认 apt-daily.timerapt-daily-upgrade.timer 都是 active, 且 unattended-upgrade.service 最近一次运行是成功退出(Result=success)。 若某台机器的 timer 是 inactive,通常是被手工 systemctl disable 过, 需要问清楚原因再决定是否恢复。

复盘

  • 磁盘告警只报”使用率 92%“,不报”谁在用”。巡检脚本里应该把 journalctl --disk-usagedu -sh /var/log/* 一并采集, 否则每次都要重新下钻。
  • 配额类默认值(10% / 15%)在大分区上等于没有上限, 必须在装机阶段就显式写死绝对数值。
  • Automatic-Reboot=false 是保守的正确选择,但必须配套一个 “待重启超过 N 天” 的告警,否则它会变成无声的漏洞敞口。
  • needrestart 的自动重启模式绝不要在数据库节点上开启。