Linux 基础体系 · 第 84/85 篇。示例面向现代主流 Linux 发行版;发行版差异、权限和生产风险会明确说明。
Linux 补丁与发行版升级:安全更新、内核、重启、灰度和回滚
Linux 的“更新”不是一个动作,而是一组不同风险的状态变化:
- 安全更新:在不改变发行版主版本的前提下,修复已知漏洞或安全缺陷。
- 软件包更新:替换某个用户态软件包及其依赖,可能改变行为,但通常保持发行版兼容性。
- 内核更新:安装新的内核、模块和相关启动文件;新内核通常要在重启后才运行。
- 发行版升级:跨越发行版版本边界,例如 Ubuntu 22.04 到 24.04、RHEL 8 到 9,涉及仓库、ABI、默认组件和系统迁移工具的整体变化。
- 重启:使新内核以及部分需要重新启动的用户态服务真正接管运行。
- 灰度:先在少量、具有代表性的节点上应用变更,通过观测结果决定是否扩大范围。
- 回滚:把系统恢复到变更前可接受的状态。软件包回退、启动旧内核、文件系统快照恢复和备份恢复并不是同一种回滚。
补丁管理的核心不是“把所有包更新到最新”,而是控制如下状态转换:
flowchart LR
A[基线确认] --> B[仓库与签名校验]
B --> C[解析依赖与变更集合]
C --> D[预演与备份]
D --> E[灰度安装]
E --> F[服务与安全验证]
F --> G{是否通过}
G -- 是 --> H[扩大范围]
G -- 否 --> I[停止扩散]
I --> J[回滚或切换备用节点]
H --> K[确认是否需要重启]
K --> L[计划重启]
L --> M[重启后验证]
每一步都产生证据:机器身份、当前版本、候选版本、安装事务、服务状态、监控结果和回滚结果。没有这些证据,事后很难判断“补丁是否安装”“新内核是否运行”以及“故障是否由本次变更引起”。
一、先区分安全更新、软件包更新与发行版升级
1. 安全更新不等于所有最新版本
安全更新通常由发行版维护者从上游修复中构建,并回移植到当前发行版版本。一个老版本号可能已经包含补丁,因此不能只根据上游版本号判断是否安全。
例如,发行版可能将某个漏洞修复回移植到:
openssl-3.0.x-某发行版修订号
上游最新版本可能已经是 3.0.y,但发行版的修订号已经包含对应修复。判断依据应优先使用:
- 发行版安全公告;
- 软件包的发行版修订号;
- 包管理器提供的安全分类;
- 漏洞扫描器与发行版 OVAL、CVE 数据的对应关系。
CVE 表示漏洞标识,不直接表示某台机器一定受影响。是否受影响还取决于:
- 软件包是否安装;
- 漏洞代码路径是否被编译启用;
- 当前版本是否已包含回移植修复;
- 服务是否实际运行;
- 配置是否允许触发漏洞;
- 该漏洞是否只影响特定架构或特定功能。
因此,“存在 CVE”与“需要在此刻安装某个包”之间还存在适用性判断。
2. 软件包更新的边界
软件包更新通常发生在同一发行版大版本内,例如 Ubuntu 22.04 的普通更新,或 RHEL 9 内的维护更新。它可能同时更新:
- ELF 二进制文件;
- 动态库;
- systemd unit;
- 配置文件模板;
- 内核模块;
- 数据库客户端;
- Python、Ruby 等语言运行时;
- 依赖关系和触发器。
包管理器负责文件、依赖和安装脚本的一致性,但不负责证明业务兼容。一个包成功安装,只说明包事务完成,不说明应用行为正确。
3. 发行版升级是迁移,而不是“大号补丁”
发行版升级通常包含:
- 仓库地址和发行版代号变化;
- 大量包的版本和 ABI 变化;
- 编译器、Python、OpenSSL、systemd 等基础组件变化;
- 配置格式变化;
- 服务默认行为变化;
- 引导加载器和内核变化;
- 旧包淘汰或替换;
- 可能的数据库、容器运行时和第三方驱动兼容问题。
因此,发行版升级的回滚难度远高于普通包更新。即使能把部分包降级,也可能无法恢复已经执行的配置迁移、数据库 schema 迁移或数据格式转换。
二、包管理器真正保证什么
APT、DNF 等包管理器主要处理四类问题:
- 仓库元数据:有哪些包、版本、依赖、校验和、更新公告;
- 签名与完整性:仓库元数据是否由受信任密钥签名,下载文件是否匹配校验和;
- 依赖求解:安装或升级后,包之间的版本约束能否同时满足;
- 事务执行:解包、配置、触发器和安装脚本按顺序执行。
它们不自动保证:
- 新版本没有回归;
- 服务配置正确;
- 业务协议兼容;
- 数据库迁移可逆;
- 重启后仍能启动;
- 远程 SSH 连接一定不会中断;
- 旧版本的所有文件都能恢复。
1. 仓库、签名与包文件的数据流
典型数据流如下:
仓库地址
↓
下载 Release/repomd 元数据
↓
验证发行版信任的签名密钥
↓
解析包索引、依赖和安全公告
↓
下载具体 RPM/DEB
↓
校验包哈希
↓
解包与执行维护脚本
↓
更新本地包数据库
签名通常保护“元数据确实由受信任仓库发布”,哈希通常保护“下载的包与元数据声明的一致”。这两者都不能证明包没有逻辑缺陷,也不能替代来源治理。生产环境还应限制仓库来源,避免把不受控的第三方仓库混入基础系统。
2. APT 的基本检查与更新
以下命令适用于 Debian、Ubuntu 等使用 APT 的系统。需要 root 或 sudo 权限。
# 查看发行版和内核
cat /etc/os-release
uname -r
# 刷新仓库元数据,不安装软件
sudo apt-get update
# 查看可升级包
apt list --upgradable
# 预演普通升级,不改变系统
sudo apt-get -s upgrade
apt-get update 只刷新索引,不会安装补丁。apt-get -s upgrade 中的 -s 表示模拟,输出中的安装、升级、删除数量是依赖求解结果,不是最终业务风险评估。
普通升级可以使用:
sudo apt-get upgrade
它通常不会为了满足新依赖而删除现有包或安装大量新包。需要允许依赖集合发生更大变化时,常见命令是:
sudo apt-get full-upgrade
full-upgrade 可能删除包,因此必须阅读事务摘要。删除一个看似无关的元包,可能导致后续内核或桌面组件不再随更新安装。
安全更新并没有跨发行版统一的 APT 命令。可以先检查仓库是否提供安全来源:
grep -R --line-number --no-messages \
-E 'security|jammy|bookworm|stable' \
/etc/apt/sources.list /etc/apt/sources.list.d/
Ubuntu 常见的自动更新由 unattended-upgrades 实现;Debian、Ubuntu 的具体配置文件和默认来源可能不同。不能仅凭安装了该包就断定所有安全更新都会自动安装,应检查:
dpkg -l unattended-upgrades
systemctl status unattended-upgrades --no-pager
grep -R --line-number --no-messages \
'Unattended-Upgrade' /etc/apt/apt.conf.d/
3. DNF 的基本检查与安全分类
Fedora、RHEL、Rocky Linux、AlmaLinux 等系统常使用 DNF 体系,但命令、插件和仓库权限会因发行版版本不同而不同。
# 查看系统信息
cat /etc/os-release
uname -r
# 刷新并显示可用更新
sudo dnf check-update
dnf check-update 在存在可用更新时可能返回退出码 100,这不是命令执行失败。脚本中必须区分“有更新”和“出错”,不能简单使用:
dnf check-update && echo OK
查询安全公告:
sudo dnf updateinfo summary
sudo dnf updateinfo list --security
sudo dnf updateinfo info --security
在支持该功能和相应仓库元数据的系统上,可以只应用标记为安全更新的包:
sudo dnf upgrade --security
但“安全更新”依赖发行版发布的 updateinfo 元数据。如果仓库没有完整安全分类,命令可能无法覆盖所有安全修复。生产判断应以发行版公告和仓库内容为准,而不是把 --security 当成漏洞扫描器。
预演普通升级:
sudo dnf upgrade --assumeno
不同 DNF 版本对模拟、下载和插件选项的支持存在差异;执行前可查看:
dnf --help
dnf upgrade --help
4. 为什么不能混用包管理器
不要直接用 rpm -Uvh 或 dpkg -i 替代 DNF、APT 处理常规升级。底层工具可以安装单个文件,但通常不负责从仓库求解完整依赖闭包。
例如,一个 DEB 包依赖:
A → B (>= 2.0)
A → C (= 1.4)
直接 dpkg -i A.deb 可能先解包 A,再因为 B 或 C 不满足而留下“已解包但未配置”的中间状态。APT 能基于仓库求解依赖并完成配置,底层工具更适合包构建、救援或由高级包管理器调用。
三、依赖求解与“可重复安装”
1. 包管理器求解的是依赖约束,不是业务约束
设安装集合为 ,每个包的版本为 。包管理器需要找到一个版本选择,使得:
例如:
应用 A 需要 libssl >= 3.0
插件 B 需要 libssl < 3.2
那么 libssl=3.1 可能满足两者;如果仓库只提供 3.3,依赖求解可能失败,或者在允许替换包时移除 B。包管理器只知道这些形式化约束,不知道 B 是否是生产关键插件。
2. 依赖闭包扩大了变更范围
假设业务服务 app 依赖:
app
├── libhttp
│ └── openssl
└── systemd-libs
升级 openssl 可能触发 libhttp、app 或其他服务重启。一个安全库补丁因此可能影响多个进程。更新前应查看事务摘要,而不是只看最初的目标包:
# APT 模拟
sudo apt-get -s install openssl
# DNF 模拟
sudo dnf upgrade --assumeno openssl
如果计划只修复一个 CVE,却看到大量核心包被替换或删除,应先查明原因:仓库混用、模块流切换、发行版版本不一致、锁定包或依赖冲突都可能造成范围扩大。
3. 可重复安装需要固定的不只是包名
“执行 apt install nginx”不等于可重复安装,因为仓库候选版本会随时间变化。更可重复的方案至少记录:
- 发行版版本和架构;
- 仓库 URL、发行版代号和组件;
- 仓库签名密钥;
- 包名和精确版本;
- 配置管理代码;
- 第三方包来源;
- 内核与启动参数;
- 数据库 schema 版本。
APT 可以查询已安装版本:
dpkg-query -W -f='${Package}\t${Version}\n' > packages-debian.tsv
RPM 系统可以使用:
rpm -qa --qf '%{NAME}\t%{EPOCHNUM}:%{VERSION}-%{RELEASE}.%{ARCH}\n' \
> packages-rpm.tsv
这些清单是证据和重建输入,但不是完整系统备份。它们不包含所有配置、运行时数据、密钥、用户生成文件和外部服务状态。
四、内核更新:安装、运行与重启是三个不同状态
1. 内核包已安装,不代表新内核正在运行
Linux 内核通常由引导加载器在启动阶段加载。一次内核更新至少有三个状态:
旧内核正在运行
↓ 安装新内核包
新内核文件存在,但旧内核仍在运行
↓ 重启并由 bootloader 选择新内核
新内核正在运行
因此:
uname -r
显示的是当前运行中的内核,不是“系统上安装的最高版本内核”。
Debian/Ubuntu 可以查看已安装内核:
dpkg-query -W -f='${Package}\t${Version}\n' 'linux-image*' 2>/dev/null
RPM 系统可以查看:
rpm -qa | grep -E '^kernel(-core|-modules)?'
重启后必须再次执行 uname -r,并与预期版本比较。
2. 内核更新涉及哪些组件
一个内核更新可能包含:
- 内核镜像;
- initramfs;
- 内核模块;
- 固件;
linux-image或kernel-core等包;- DKMS 或第三方内核模块;
- GRUB 配置;
- 早期启动所需的存储、网络和加密模块。
如果根文件系统位于 RAID、LVM、iSCSI、NVMe、加密卷或特殊存储上,initramfs 中缺少对应模块可能导致“包安装成功但重启无法挂载根文件系统”。
常见检查:
# 查看 initramfs 文件
ls -lh /boot/initr* /boot/initramfs* 2>/dev/null
# 查看当前启动参数
cat /proc/cmdline
# 查看已加载模块
lsmod
不同发行版生成 initramfs 的工具可能是 initramfs-tools 或 dracut,不要跨发行版照抄参数。
3. DKMS 和第三方模块是高风险点
显卡、存储、虚拟化、安全代理等第三方模块可能需要针对每个内核重新编译。新内核安装后,模块编译失败不一定阻止包事务完成,但重启后设备可能消失或服务无法启动。
检查 DKMS 状态:
dkms status 2>/dev/null || true
检查新内核对应模块是否存在,需要先知道目标内核版本:
find /lib/modules -maxdepth 2 -type f -name '*.ko*' | head
在生产环境中,内核灰度节点必须覆盖真实硬件、存储、网卡、加密、监控和安全代理组合;只在普通云主机上通过,不能证明物理机或特殊设备通过。
五、重启:为什么需要、如何判断、如何验证
1. 用户态服务也可能继续使用旧代码
替换磁盘上的共享库后,已经运行的进程通常仍持有旧库的文件描述符和内存映射。新启动的进程才会加载新库。因此:
- 包文件可能已经是新版本;
- 旧进程仍执行旧代码;
- 重启服务可以让该服务加载新库;
- 重启主机才能切换内核,并处理更多早期启动组件。
可使用 lsof 检查已删除但仍被进程打开的文件:
sudo lsof +L1
输出中的 DEL 文件不一定表示故障,但说明磁盘上的文件已替换,旧进程仍持有旧 inode。对关键服务,应根据发行版维护脚本和服务管理器结果决定是否重启,而不是盲目重启所有服务。
2. “需要重启”的判断是发行版相关的
Debian/Ubuntu 常见提示文件:
test -f /var/run/reboot-required && echo "reboot required"
cat /var/run/reboot-required.pkgs 2>/dev/null || true
RHEL 系列常见检查工具:
command -v needs-restarting >/dev/null && sudo needs-restarting -r
这些机制属于发行版实现或工具提供的判断,不是 Linux 内核统一规范。没有提示也不代表绝对不需要重启;有提示也应核对具体包和业务计划。
3. 重启前后的验证闭环
重启前记录:
date -Is
hostnamectl
uname -a
systemctl --failed
df -hT
df -ih
重点是保存:
- 主机名和实例身份;
- 当前内核;
- 失败的 systemd 单元;
/boot、根分区和 inode 使用率;- 变更事务日志;
- 远程访问路径和带外控制台入口。
重启可以使用:
sudo systemctl reboot
重启后验证:
uptime
uname -r
systemctl --failed
systemctl is-system-running
journalctl -b -p warning..alert --no-pager
systemctl is-system-running 返回 degraded 时,不一定表示本次补丁失败,但必须检查失败单元。还应验证实际业务:
curl --fail --silent --show-error https://service.example/health
ss -lntp
健康检查应包含依赖,例如数据库连接、队列连接、证书加载和关键读写,而不是只检查 TCP 端口是否监听。
六、SSH 远程更新的特殊风险
OpenSSH 客户端、服务端和相关库更新时,存在两类不同风险:
- 当前连接风险:当前
sshd进程可能仍在使用旧库,更新文件本身通常不会立即断开已有会话; - 新连接风险:服务重启或主机重启后,新配置、主机密钥、算法和认证模块可能导致新连接失败。
更新 SSH 前应确认:
# 检查配置语法
sudo sshd -t
# 查看实际生效配置;不同版本支持的选项可能不同
sudo sshd -T | less
# 保留一个已验证的现有会话,并准备第二个会话测试
ssh -o BatchMode=yes user@host 'hostname; uname -r'
不要只依赖 SSH 作为唯一恢复通道。应准备云厂商串口、虚拟控制台、远程管理卡或现场带外入口。
OpenSSH 官方手册对 sshd_config、密钥、认证和命令行为的定义,应优先于博客中的参数示例。参考:OpenSSH Manual Pages。
七、普通补丁、内核 Live Patching 与重启不能混为一谈
Live patching 是在内核运行期间替换部分内核代码,以减少因安全漏洞而重启的次数。它通常只覆盖特定内核修复和受支持的发行版、架构及订阅范围。
它不能替代普通重启,因为以下内容仍可能需要重启:
- 未被 live patch 覆盖的内核组件;
- 新硬件驱动;
- 引导加载器和 initramfs;
- 用户态库和服务;
- 需要重新初始化的内核状态;
- 发行版升级后的整体组件变化。
因此,安全策略应记录两个状态:
漏洞修复已应用:是/否
运行中的内核与用户态进程已切换:是/否
“补丁已安装”与“正在运行的代码已修复”不是同一个布尔值。Linux 内核安全文档对内核安全机制、威胁模型和实现边界的说明可参考:Linux Security Documentation。
八、生产变更前的预检查
补丁风险往往不是下载失败,而是系统在不适合变更时被改变。至少应检查以下项目。
1. 身份、版本和仓库
hostnamectl
cat /etc/os-release
uname -r
# APT
apt-cache policy 2>/dev/null | head -50
# DNF
sudo dnf repolist
确认主机确实属于目标环境,避免把测试命令执行在生产节点。仓库应满足:
- 来源明确;
- 签名校验启用;
- 镜像同步完成;
- 与发行版版本匹配;
- 第三方仓库已评估;
- 代理、离线镜像和证书有效。
2. 容量和 inode
df -hT
df -ih
sudo du -xhd1 /var /boot 2>/dev/null | sort -h
/boot 空间不足可能阻止新内核和 initramfs 写入;inode 耗尽则可能在磁盘还有空间时仍无法创建文件。日志、缓存、旧内核和包下载目录都可能影响事务。
3. 当前健康状态和服务依赖
systemctl --failed
systemctl list-units --type=service --state=running
systemctl list-dependencies --reverse network-online.target
如果系统在补丁前已经存在失败单元,变更后出现同样故障将难以归因。应先记录并处理基线故障。
4. 备份的可恢复性
“有备份”不等于“可回滚”。至少需要知道:
- 备份时间点;
- 是否包含配置和密钥;
- 是否包含应用数据;
- 是否与当前版本兼容;
- 恢复需要多长时间;
- 是否做过恢复演练;
- 恢复后如何避免旧节点重新接收流量。
对数据库而言,备份恢复和事务日志恢复通常比单纯保存软件包更重要。软件包回滚无法撤销已经提交的业务数据。
九、灰度发布:不是“先更新一台”这么简单
1. 灰度节点必须具有代表性
如果生产集群中存在以下差异:
- 不同 CPU、网卡或存储;
- 不同内核模块;
- 不同流量角色;
- 不同配置开关;
- 不同数据库分片;
- 不同可用区;
- 不同容器运行时;
那么灰度集合必须覆盖这些风险维度。只挑一台“最空闲”的机器,可能恰好避开了最重要的故障路径。
2. 灰度的最小闭环
对每个灰度节点,应按顺序执行:
- 从负载均衡或服务发现中摘除;
- 等待连接和任务排空;
- 记录补丁前版本与业务指标;
- 进行模拟和实际安装;
- 按需重启服务或主机;
- 验证新版本、服务和业务;
- 观察足够长的窗口;
- 通过后重新接流;
- 再选择下一批节点。
摘流不能只停止新连接。如果存在长连接、异步任务、消息消费或定时任务,还应确认排空和重复执行语义。
3. 用可观测信号决定扩散
灰度期间应比较变更前后的:
- 错误率;
- 延迟分位数;
- 吞吐;
- CPU、内存、IO、连接数;
- OOM、内核日志和磁盘错误;
- 服务重启次数;
- TLS、认证、DNS 和数据库失败;
- 队列积压和任务重试。
可以定义一个简单的扩散条件。设基线错误率为 ,灰度错误率为 ,允许增量为 ;只有满足:
并且关键业务探针、日志和资源指标均未越界,才允许扩大范围。这里的阈值不是 Linux 规范,而是业务变更策略;关键在于事先定义、自动采集和明确停止条件。
十、回滚的四种层次
1. 服务回滚
如果问题只是新配置或服务行为,可以恢复配置并重启服务:
sudo cp /etc/example/service.conf.backup /etc/example/service.conf
sudo systemctl restart example.service
sudo systemctl status example.service --no-pager
配置备份必须与变更关联,且不能把包含密钥的备份文件暴露给普通用户。
2. 软件包版本回退
APT 可以查询版本并尝试安装指定版本:
apt-cache policy nginx
sudo apt-get install nginx=目标版本
前提是仓库仍保留该版本,且依赖允许回退。可能还需要同时指定相关依赖的版本。apt-mark hold 可以阻止某些包继续自动升级,但不是完整回滚机制:
sudo apt-mark hold nginx
sudo apt-mark unhold nginx
RPM 系统可以查看 DNF 事务历史:
sudo dnf history
sudo dnf history info 事务ID
某些情况下可以使用:
sudo dnf history undo 事务ID
这只能在事务可逆、旧包仍可获取、依赖关系允许的情况下使用。它不是时间机器,不能撤销安装脚本产生的任意副作用,也不能恢复数据库数据。
3. 启动旧内核
如果新内核无法启动,GRUB 通常会保留旧内核条目。应通过控制台选择旧内核启动,启动后检查:
uname -r
systemctl --failed
journalctl -b -1 -p err --no-pager
旧内核不要在灰度验证完成前删除。删除旧内核前应至少保留一个已验证可启动的回退内核,并确认引导配置已更新。
4. 文件系统快照或整机恢复
LVM、Btrfs、ZFS、云盘快照和虚拟机镜像可以提供更大范围的恢复点,但语义取决于实现:
- 快照是否包含
/boot; - 快照是否跨越多个独立磁盘;
- 数据库是否处于一致状态;
- 快照是否复制到故障域之外;
- 恢复后网络身份、挂载点和密钥是否正确;
- 是否能在规定时间内完成恢复。
跨发行版升级时,整机镜像、可替换节点和蓝绿环境通常比“逐包降级”更可靠。
十一、为什么回滚经常失败:数据库迁移反例
设旧版本应用使用表:
CREATE TABLE users (
id BIGINT PRIMARY KEY,
name TEXT NOT NULL
);
新版本执行了不可逆迁移:
ALTER TABLE users ADD COLUMN email TEXT NOT NULL DEFAULT '';
之后新版本开始向 email 写入数据。即使把应用包降级回旧版本,数据库仍然包含新 schema 和新数据。旧版本可能:
- 忽略新列,功能看似正常;
- 因为 schema 检查失败而拒绝启动;
- 读取新格式后产生错误;
- 覆盖或丢失新版本写入的数据。
因此,应用发布必须区分:
- 向前兼容:旧代码能处理新 schema;
- 向后兼容:新代码能处理旧 schema;
- 回滚兼容:发生降级时数据仍能被旧代码安全使用。
如果不具备回滚兼容性,正确的回滚单位通常是“切换到旧的完整环境并恢复或隔离数据”,而不是只降级 RPM/DEB。
十二、发行版升级的典型路径与差异
发行版升级不能使用一个跨发行版通用命令。常见路径如下:
1. Ubuntu
Ubuntu 的版本升级通常使用发行版提供的升级工具,例如:
sudo do-release-upgrade
是否允许从当前版本直接升级、是否需要先安装全部当前版本更新、是否支持服务器版本,取决于当前版本、目标版本和升级路径。生产环境应先阅读对应版本的发行说明,并在相同镜像、仓库和配置上演练。
2. Debian
Debian 大版本升级通常涉及:
- 备份并审计当前仓库;
- 将软件源从旧代号切换到新代号;
- 先完成当前版本更新;
- 分阶段执行最小升级和完整升级;
- 检查配置文件冲突;
- 重启并验证服务。
不能直接把所有源文本替换后无条件执行 full-upgrade。第三方源、混合发行版源和残留 pin 优先级可能让依赖求解结果不可预测。
3. RHEL 及兼容发行版
RHEL 主版本升级通常使用发行版指定的迁移工具和检查流程,例如 Leapp 体系,而不是仅执行 dnf upgrade。升级前的 pre-upgrade 报告、订阅状态、模块流、第三方驱动和已知 inhibitor 必须处理。
4. Fedora
Fedora 的版本升级通常使用 dnf system-upgrade 体系,但具体命令和步骤随 DNF 版本变化,应以目标版本官方文档为准。它与普通 dnf upgrade 的生命周期不同,往往会下载升级事务并在重启后的特殊目标中完成。
这些差异说明:包管理器升级同一发行版内的软件,发行版升级工具负责改变发行版生命周期和迁移状态。不要因为两个系统都使用 RPM 或 DEB,就认为升级流程等价。
十三、配置文件冲突与维护脚本
软件包升级可能同时修改配置文件。常见行为包括:
- 保留管理员修改过的旧配置;
- 安装发行商的新配置为
.dpkg-dist、.rpmnew等文件; - 将旧配置保存为
.dpkg-old、.rpmsave等文件; - 在交互界面要求选择;
- 由自动化工具按预设策略处理。
升级后应搜索这些痕迹:
sudo find /etc -xdev \
\( -name '*.dpkg-dist' -o -name '*.dpkg-old' \
-o -name '*.rpmnew' -o -name '*.rpmsave' \) \
-print
维护脚本可能执行用户、目录、缓存、systemd daemon reload、服务重启或 schema 操作。查看包内容和脚本有助于解释副作用:
# Debian
apt-get download 包名
dpkg-deb -I 包文件.deb
dpkg-deb -e 包文件.deb /tmp/pkg-control
# RPM
rpm -q --scripts 包名
不要在生产主机上随意手工执行从包中提取的脚本。这里的目的主要是审计和理解生命周期。
十四、失败表现与诊断路径
1. 包事务中断
常见表现:
unmet dependencies
dpkg was interrupted
transaction check error
file ... from install of ... conflicts with file ...
诊断顺序应是:
# APT
sudo dpkg --audit
sudo apt-get check
# RPM/DNF
sudo dnf check
sudo rpm -Va
rpm -Va 会验证已安装文件的元数据和校验信息,但配置文件被管理员修改并不自动表示损坏。不要在原因不明时直接删除包数据库或强制覆盖文件。
2. 服务安装成功但启动失败
systemctl status 服务名 --no-pager
journalctl -u 服务名 -b --no-pager
systemctl cat 服务名
应区分:
- ExecStart 路径变了;
- 配置语法不兼容;
- 权限或 SELinux/AppArmor 策略拒绝;
- 端口已被占用;
- 依赖服务未启动;
- 动态库或内核能力缺失;
- 数据目录版本不兼容。
只看 systemctl status 的最后几行通常不够,需结合本次包变更和服务完整日志。
3. 重启后系统无法正常启动
首先通过控制台确认:
- 是否进入了正确内核;
- 根文件系统是否挂载;
- initramfs 是否找不到设备;
- 网络是否启动;
- systemd 是否进入 emergency 或 degraded;
- 是否因为磁盘检查、加密解锁或挂载超时阻塞。
如果旧内核可以启动,记录新内核启动日志:
journalctl -k -b -1 --no-pager
其中 -b -1 表示上一次启动,实际可用的 boot ID 应通过 journalctl --list-boots 核对。
4. SSH 断开但主机仍健康
可能只是 sshd 重启导致连接短暂中断,也可能是:
- 新配置拒绝认证;
- 防火墙规则改变;
- 网络服务未启动;
- DNS 或路由失败;
- 安全模块阻止密钥读取;
- 新内核驱动异常。
应立即使用备用会话或带外控制台判断“主机不可达”与“SSH 服务不可用”的区别。
十五、自动更新、手工更新与维护窗口
自动安全更新适合补丁频繁、节点可替换且有充分监控的环境,但必须明确它会不会:
- 自动重启服务;
- 自动重启主机;
- 安装内核;
- 删除或替换依赖;
- 在业务高峰执行;
- 受仓库镜像延迟影响。
可将策略分成三层:
自动下载安全包
↓
自动安装用户态安全包
↓
人工批准内核和发行版升级
这不是所有系统的默认行为,而是一种降低爆炸半径的策略。对于不可中断节点,自动更新并不自动等价于安全,因为安装后的“尚未重启”状态可能持续很久,形成表面已修复、实际仍运行旧代码的差异。
十六、补丁优先级:不能只按版本新旧排序
可以把补丁优先级近似写成:
其中:
Impact:漏洞或回归可能造成的影响;Exposure:服务是否暴露在不可信网络;Exploitability:是否已有公开利用或正在被利用;AssetCriticality:节点对业务的关键程度。
这个公式不是标准评分算法,而是帮助排队的决策模型。它解释了两个常见反例:
- 内网测试机存在高危远程执行漏洞,可能比公网但未启用相关服务的机器优先;
- 关键数据库节点的普通内核更新,也可能需要比边缘 Web 节点更严格的灰度和窗口。
CVSS、CVE、发行版安全公告和业务资产等级应结合使用,不能只按 CVSS 分数自动决定是否立即重启。
十七、一个可审计的补丁执行示例
下面示例以 APT 系统为例,展示“记录—预演—更新—验证”的最小闭环。它不是所有生产环境都适用的通用脚本,执行前必须确认节点已摘流、备份可用且有带外访问。
#!/usr/bin/env bash
set -Eeuo pipefail
log="/var/log/patch-$(date +%Y%m%d-%H%M%S).log"
exec > >(tee -a "$log") 2>&1
echo "=== baseline ==="
date -Is
hostnamectl
cat /etc/os-release
uname -r
systemctl --failed || true
df -hT
df -ih
echo "=== refresh metadata ==="
apt-get update
echo "=== simulation ==="
apt-get -s upgrade
echo "=== apply update ==="
DEBIAN_FRONTEND=noninteractive apt-get \
-o Dpkg::Options::="--force-confold" \
upgrade -y
echo "=== post-package check ==="
dpkg --audit || true
apt-get check
test -f /var/run/reboot-required && echo "REBOOT_REQUIRED=yes" || true
cat /var/run/reboot-required.pkgs 2>/dev/null || true
echo "=== service failures ==="
systemctl --failed || true
关键点如下:
set -Eeuo pipefail让脚本更早暴露错误,但无法保证每个子命令的业务语义正确;- 日志保存主机身份、时间和命令结果,便于审计;
apt-get update与实际升级分开,便于定位是仓库问题还是包事务问题;- 模拟结果必须人工或自动策略检查,尤其是删除列表;
--force-confold会保留本地配置,可能导致新版本需要的配置项没有加入,不能盲目作为统一策略;- 脚本没有自动重启,因为重启是独立的可用性变更;
systemctl --failed只是发现异常,不是业务验证。
生产系统通常还应把“摘流、健康探针、重启、重新接流、观察窗口和停止条件”纳入发布编排,而不是只封装包管理命令。
十八、容易混淆的结论
“补丁安装成功,所以漏洞已经修复”
不一定。可能安装了错误架构、安装到了备用节点、漏洞修复需要重启、服务仍持有旧库,或者扫描器识别规则与发行版回移植版本不一致。应同时确认包版本、运行进程、运行内核和服务重启状态。
“重启后有旧内核,所以一定能回滚”
不一定。旧内核可能缺少当前硬件驱动,旧用户态包和新配置也可能不兼容。旧内核主要提供内核层回退路径,不等于完整系统回滚。
“DNF history undo 或 APT 指定旧版本可以恢复”
只对包文件和部分事务有效。安装脚本、配置迁移、数据库数据、外部状态和其他节点已经发生的变化不会自动恢复。
“只更新安全包风险最小”
安全包可能更新 glibc、OpenSSL、内核、systemd、认证栈或容器运行时,依赖闭包仍可能很大。安全优先级高,不代表变更风险为零。
“灰度一台机器就够了”
只有当这台机器覆盖了目标节点的硬件、配置、流量和依赖特征时才有代表性。集群异构时,灰度应按风险维度选样本。
补丁管理最终要维护的不是“最新版本”这一数字,而是一个可解释的系统状态:
哪些漏洞已修复?
哪些包已安装?
哪些进程仍使用旧代码?
哪个内核正在运行?
哪些服务已通过业务验证?
出现故障时能恢复到哪个状态?
恢复所需时间是否满足业务目标?
当这些问题都有版本、日志、监控和演练结果作为证据时,安全更新、内核重启、发行版升级、灰度扩散和回滚才构成一个真正可控的生产流程。
系列导航与关联阅读
- 系列入口:Linux 完整学习路线:从内核与文件系统到网络、性能和生产运维
- 上一篇:Linux cloud-init:实例初始化、Metadata、用户数据、幂等和安全
- 下一篇:Linux 主机事件响应:隔离、取证、时间线、证据保全和恢复
- 延伸:Linux 软件包管理:APT、DNF、仓库、签名、依赖与可重复安装
- 延伸:Linux 生产运行手册:容量、变更、监控、应急和复盘
官方资料
本文依据 Linux 内核、systemd 与主流发行版官方文档重新梳理;正文与实验由 WR BLOG 编写。

评论
0 条讨论