ARCHIVE NO. 002
Ubuntu Server 例行巡检笔记:journal 占满根分区与内核待重启
巡检背景
每季度要给机房那批 24.04 LTS 节点做一次例行检查。这批机器跑的是一套 内部时序数据库与几个常驻采集器,平时没什么人登,出了问题才发现已经积压很久。 这次的检查项是磁盘水位、待重启内核、以及无人值守升级的落地情况。
先做快照,再动手
巡检难免要清理东西,动手之前先留一份现场。用一段只读的采集脚本把状态落盘, 后面所有判断都基于这份快照,避免”清理之后想复现却复现不出来”:
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 日志。
$ 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 级别的采集器还会继续刷,
一周之后同一个坑再踩一次:
# 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 在包升级时不会被覆盖,也更容易进版本库:
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 -r 报 6.8.0-31-generic,而 /boot 里躺着 6.8.0-45-generic。
unattended-upgrades 在两周前的凌晨自动装了新内核,却因为
Automatic-Reboot 默认为 false 而没有重启,进程树仍然映射着旧内核的
/usr/lib/modules——被删掉的旧模块目录只是 inode 还活着而已。
这类”悬挂状态”最危险的地方在于它是静默的:安全更新显示已安装, 但漏洞其实还在内存里跑着。
确认与处理
# 哪些包需要重启才能生效(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(自动重启服务),
在数据库主节点上会直接引发一次非计划主从切换:
sudo needrestart -b # -b 批处理模式,只输出结论
# NEEDRESTART-KSTA: 3 ← 内核需要重启顺手把定时任务确认一遍
systemctl list-timers --all --no-pager | grep -E 'apt|unattended'输出里确认 apt-daily.timer 与 apt-daily-upgrade.timer 都是 active,
且 unattended-upgrade.service 最近一次运行是成功退出(Result=success)。
若某台机器的 timer 是 inactive,通常是被手工 systemctl disable 过,
需要问清楚原因再决定是否恢复。
复盘
- 磁盘告警只报”使用率 92%“,不报”谁在用”。巡检脚本里应该把
journalctl --disk-usage与du -sh /var/log/*一并采集, 否则每次都要重新下钻。 - 配额类默认值(10% / 15%)在大分区上等于没有上限, 必须在装机阶段就显式写死绝对数值。
Automatic-Reboot=false是保守的正确选择,但必须配套一个 “待重启超过 N 天” 的告警,否则它会变成无声的漏洞敞口。needrestart的自动重启模式绝不要在数据库节点上开启。