Ubuntu服务器如何升级系统版本?升级前检查与回滚方案详解

一台跑了两年的 Ubuntu 20.04 业务服务器,某天安全扫描报告甩过来一长串 CVE 编号,其中几条的修复"仅在新版本软件包提供"。运维最纠结的时刻就此到来:升,怕半夜叫醒的电话;不升,怕合规过不了。Ubuntu 发行版升级(release upgrade)看起来只有一条 do-release-upgrade 命令,但真正决定成败的,是升级前那几十项检查和一条随时能用的回退通道。本文把这两个环节拆开讲透。

一、先分清三种"升级":别把 full-upgrade 当升版本

很多人以为 apt upgrade 会把系统从 20.04 变成 22.04,这是个常见误解。三者的边界必须先划清楚:

  • 包升级(apt update && apt full-upgrade):只更新当前发行版仓库里的软件版本,/etc/os-release 里的 VERSION_ID 不会变,属于日常安全补丁范畴。
  • 发行版升级(do-release-upgrade):把 sources.list 中的发行代号全部替换为新版本,替换核心组件并重启,版本号真正跳变。
  • 重装(fresh install):格式化重装新版本,再迁移数据与应用配置,风险最高但环境最干净。
方式适用场景停机时长回滚难度主要风险
apt full-upgrade日常安全补丁、小版本修复分钟级,多数服务可热更低,可降级单个包配置文件合并冲突(conffile prompt)
do-release-upgradeLTS 跨大版本升级,同架构 x86_64/arm6430 分钟到 2 小时中到高,需快照或备份语言栈版本跳变、配置文件被替换、源失效
全新安装跨多个版本、架构变更、环境已严重污染小时级中,旧机可并行在线配置遗漏、数据迁移出错

判断口径很直接:如果你的目标是"版本号从 20.04 变成 22.04/24.04",那就必须走 do-release-upgrade;如果只是为了修 CVE,先查一下该 CVE 在新 LTS 和当前 LTS 的补丁覆盖情况,很多漏洞在当前版本的 updates 仓库里就已经修好了。

1.1 什么时候不该升级

以下四条命中任意一条,建议推迟窗口:

  • 这台机器上跑着闭源或厂商认证的二进制驱动(如特定版本的 GPU 驱动、RAID 卡工具),且厂商未声明支持目标版本。
  • 磁盘剩余空间不足。建议根分区(/)剩余不低于 5 GB、/boot 若独立分区剩余不低于 500 MB——升级过程要下载完整包集并安装新内核与 initrd,空间不足会中途失败。
  • 当前系统存在半安装包(dpkg -l 里状态列出现 iU、iF),这类状态下继续升级等于叠故障。
  • 业务高峰期前 48 小时内。发行版升级无法保证"零感知",务必留足排障窗口。

二、升级前的七项必备检查

下面这套体检命令建议全部执行一遍,并把输出存档。它既是升级的依据,也是事后复盘的依据。

# 1. 确认当前版本与支持状态
lsb_release -a
cat /etc/os-release

# 2. 只检查不做操作:确认是否存在可用的升级目标
sudo do-release-upgrade -c

# 3. 被冻结(hold)的包必须先处理,否则会阻塞升级
apt-mark showhold

# 4. 是否存在半装/失败包(正常情况下应该无输出)
dpkg -l | awk '$1 ~ /^i[UF]/ {print}'

# 5. 磁盘空间评估
df -h / /boot /var
du -sh /var/cache/apt/archives

# 6. 第三方软件源清单(PPA、Docker、Nginx 官方源等)
ls -1 /etc/apt/sources.list.d/

# 7. 当前失败的 systemd 单元(升级后要和升前存档对比)
systemctl --failed

逐项说明判断标准:

  1. do-release-upgrade -c 会明确打印是否有新版本可用。若毫无输出,多半是 /etc/update-manager/release-upgrades 里的 Prompt= 不是 lts(还有 never、normal 两个取值),或者目标 LTS 的首个点版本尚未发布。Ubuntu 的策略是:LTS 到 LTS 的升级通道,要等目标 LTS 的 .1 点版本放出后才开放——这一点决定了你的升级窗口能不能排在发版当月。
  2. apt-mark showhold 有输出时,逐个判断是人为冻结还是误操作。解除用 apt-mark unhold 包名,升级完成后再恢复 apt-mark hold 包名。
  3. ls -1 /etc/apt/sources.list.d/ 列出的第三方源是对 do-release-upgrade 最大的干扰源。升级器会把不认识或不被支持的源注释掉,相关软件(如 Docker CE、某些监控 agent)会随之停在旧版本甚至丢失。建议提前确认这些源是否提供目标版本代号的支持。
  4. 独立估算一版"如果回滚要多久"的时间预算,并写进变更单里。

2.1 必须做的备份动作

发行版升级没有"一键回滚"这种东西——不要指望 apt 能把 Python 3.8 变回 3.8。备份要覆盖配置、包状态与数据三块:

# 配置文件与包状态(最低限度)
sudo mkdir -p /backup
sudo tar czf /backup/etc-$(date +%F).tar.gz /etc
dpkg --get-selections > /backup/dpkg-selections-$(date +%F).txt

# 更完整的方案:apt-clone(需先安装)
sudo apt install -y apt-clone
sudo apt-clone clone /backup/apt-clone-$(date +%F)

# 数据库逻辑备份(MySQL 示例,按你的实例替换参数)
mysqldump -uroot -p --all-databases --routines --triggers \
  --single-transaction > /backup/mysql-all-$(date +%F).sql

# PostgreSQL 示例
sudo -u postgres pg_dumpall > /backup/pg-all-$(date +%F).sql

这里有个经常被忽略的点:tar 打出来的 /etc 只是"旧配置",并不能被整个塞回新系统——因为新版本的 nginx、sshd、PostgreSQL 配置文件结构可能已经变了。它的正确用法是对照修改,而不是覆盖还原。

三、do-release-upgrade 的正确执行姿势

推荐的标准执行序列如下。

# 第一步:先把当前版本打满补丁并重启,确保基线干净
sudo apt update
sudo apt full-upgrade -y
sudo apt autoremove --purge -y
sudo reboot

# 第二步:在可断线重连的会话里执行(tmux 或 screen 二选一)
tmux new -s upgrade
# screen -S upgrade

# 第三步:发起发行版升级
sudo do-release-upgrade

# 非交互前端(适合已在测试机验证过、无待回答问题的场景)
# sudo do-release-upgrade -f DistUpgradeViewNonInteractive

几个关键提示:

  • 必须用 tmux / screen。SSH 会话断开会中断 dpkg 配置阶段,后果是系统卡在半升级状态,修复成本远高于重做。(注意 tmux 在升级后通常仍在,但别指望它。)
  • 升级器会临时开一个救援 sshd。当通过 SSH 操作时,它通常会在额外端口(惯例是 1022)启动一个备用 sshd 以防网络中断。这意味着执行前你的防火墙/安全组必须放行该端口,否则等于自断退路。云厂商的安全组规则要提前改。
  • 不要加 -d。-d 是升级到尚未正式发布的开发版本,生产环境禁用。想要 LTS→LTS 就把 Prompt=lts 配好。
  • 遇到配置文件冲突询问时,默认保留本地版本(keep the local version currently installed)通常更安全;选择替换前先确认你没在该文件里做过自定义改动。

3.1 升级后的第一批复核

# 版本是否真的跳过去了
lsb_release -a
uname -r

# 有没有起不来的服务
systemctl --failed
systemctl list-units --state=failed

# 本次启动以来的错误日志
journalctl -p err -b --no-pager | tail -50

# 对外端口是否都回来了(和升级前存档对比)
ss -lntp

# 清理残留配置包的痕迹(输出为 0 才好)
dpkg -l | grep -c '^rc'

四、版本差异速查:20.04 / 22.04 / 24.04 到底变了什么

升级失败的绝大多数翻车现场,都源于"某个运行时悄悄换了大版本"。下面这张表建议直接贴到变更单里(数值为各版本 GA 之后的常见基线,实际以目标机 apt policy 输出为准):

组件20.04 LTS (focal)22.04 LTS (jammy)24.04 LTS (noble)跨版本升级影响
Linux 内核(通用/HWE)5.4 GA / 5.15 HWE5.15 GA / 6.8 HWE6.8 GA驱动需重编译,DKMS 模块重点核查
systemd245249255单元文件参数废弃项会被报 warning
glibc2.312.352.39手工编译的二进制需重新验证
OpenSSL1.1.13.03.0.x1.1 API 代码无法直接链接
Python 33.83.103.12最高频翻车项:vEnv 需重建,distutils 移除
PHP7.48.18.37.x 语法与废弃函数大面积报错
PostgreSQL121416数据目录需 pg_upgradecluster,不能原地用
OpenSSH8.2p18.9p19.6p1ssh-rsa 算法被弃用,旧客户端/脚本可能连不上
nginx1.181.181.24配置基本兼容,第三方模块需重编
gcc/g++ 默认版本91113老项目编译告警升级为错误

三个必须单独排期的技术债:

  • Python 虚拟环境重建:/usr/bin/python3 换了解释器版本后,指向它的 virtualenv 会直接失效。正确做法是在目标版本上创建新 venv,用原来的 requirements.txt 重装依赖:python3 -m venv /opt/app/venv && /opt/app/venv/bin/pip install -r requirements.txt。另外 24.04 起 pip install 系统级安装会被 PEP 668 标记的外部管理限制挡住,务必走 venv 或加 --break-system-packages(不推荐)。
  • PostgreSQL 集群升级:升级会并存安装新版 database cluster,需要用 pg_upgradecluster 把旧集群迁到新集群,并由 dpkg 提示是否删除旧集群数据。这一步必须先有完整逻辑备份。
  • SSH 算法兼容:从 20.04 升到 22.04/24.04 后,老旧的 ssh-rsa(SHA-1)签名算法不再被默认接受。表现为某些自动化脚本、老交换机备份任务、嵌入式设备突然连不上。验证方式是在客户端执行 ssh -o PubkeyAcceptedAlgorithms=+ssh-rsa user@host 判断,若确定是算法问题,应升级客户端而非在服务端放宽策略。

五、常见失败场景与应急处理

场景一:升级中途 apt 报依赖关系断裂。不要强行 apt -f install -y 乱试,先看清楚它要删谁。执行 apt -f install --dry-run 或 apt full-upgrade -s 模拟一遍,如果它试图删除关键业务包,停下来,先处理与之冲突的第三方源。

场景二:服务起来了但端口没监听。第一时间看该服务的日志与 systemd 状态,而不是急着改配置:

systemctl status nginx --no-pager -l
journalctl -u nginx -n 100 --no-pager
nginx -t                      # 语法自检,多数情况是配置里用了已废弃指令
ss -lntp | grep -E ':80|:443'

nginx 的典型情况是老配置里保留了在新版中被合并或重命名的指令,nginx -t 会直接给出行号。

场景三:SSH 连不上服务器了。优先级最高。走云厂商的 VNC/串口控制台,检查 ssh.socket 与 ssh.service 的状态。部分新版本默认采用 socket 激活方式,端口由 socket 单元管理,改端口要在 socket 单元里改:

systemctl is-enabled ssh.socket     # 判断是否为 socket 激活模式
systemctl status ssh.socket
systemctl edit ssh.socket           # 改 ListenStream 后 daemon-reload 并重启

场景四:GRUB 引导异常、进不了系统。用安装介质进 Live/救援模式,挂载原根分区后重装引导:

sudo mount /dev/ubuntu-vg/ubuntu-lv /mnt
sudo mount /dev/sda2 /mnt/boot       # 若 /boot 独立分区
sudo mount --bind /dev  /mnt/dev
sudo mount --bind /proc /mnt/proc
sudo mount --bind /sys  /mnt/sys
sudo chroot /mnt
grub-install /dev/sda
update-grub
exit

六、回滚方案:三档可靠性对比

回滚能力必须在升级之前就建好,而不是失败之后再想办法。按可靠性排序:

方案前置条件回滚耗时能否回到"运行中"状态适用评价
LVM / Btrfs / ZFS 快照安装时就选了 LVM 或 Btrfs、ZFS 布局分钟级能,连同系统状态完整回退首选,成本低、成功率高
整机镜像 / 云盘快照云平台支持云盘快照十分钟到半小时能,但 IP 与数据需处理云平台上的务实选择
配置+包状态备份(apt-clone 等)无数十分钟到数小时部分,只能还原包集合与配置兜底手段,不能替代快照
重装旧版本 + 恢复数据完整数据备份小时级能,但业务中断长最后的退路

6.1 快照实操示例

如果安装时用了 LVM(Ubuntu 默认桌面/服务器安装的"使用整块磁盘并配置 LVM"选项会创建 ubuntu-vg/ubuntu-lv),快照命令如下:

# 建快照:-s 表示 snapshot,-L 指定快照可用空间
sudo lvcreate -L 20G -s -n root-preup /dev/ubuntu-vg/ubuntu-lv

# 查看快照及其使用率(snap_percent 接近 100% 会作废)
sudo lvs -a -o name,lv_size,origin,snap_percent

# 确认升级没问题后销毁快照,避免持续写入放大
sudo lvremove /dev/ubuntu-vg/root-preup

# 需要回滚时:合并快照回原卷
sudo lvconvert --merge /dev/ubuntu-vg/root-preup
sudo reboot

注意两点:第一,快照空间不足会让快照自动失效,因此 -L 20G 要按你预计的升级写入量(通常几 GB 到十几 GB 的包解压量)来估;第二,lvconvert --merge 的操作影响面很大,务必先在同样布局的测试机上演练一次,不同版本的 LVM 工具对"合并正在使用的卷"的处理方式存在差异。

Btrfs 布局(Ubuntu 安装器可选)更轻量:

sudo btrfs subvolume snapshot / /snapshots/root-$(date +%F)
sudo btrfs subvolume list /

若是 Ubuntu 安装器的 ZFS 布局,快照在 zfs list -t snapshot 里(部分桌面/服务器安装会自动在 apt 操作前创建 zsys 快照),回滚用 zfs rollback,同样先在测试环境验证。

6.2 只剩备份时的最小复原动作

没有快照、只做了备份的机器,能做的是"包集合还原 + 配置对照":

# 还原 dpkg 包选择状态(不能还原软件版本本身)
sudo dpkg --set-selections < /backup/dpkg-selections-2024-05-01.txt
sudo apt-get dselect-upgrade

# 用 apt-clone 还原第三方源与包名单
sudo apt-clone restore /backup/apt-clone-2024-05-01.apt-clone.tar.gz

# 从备份里提取单个配置文件做对照(不要整目录覆盖)
sudo tar tzf /backup/etc-2024-05-01.tar.gz | grep sshd_config
sudo tar xzf /backup/etc-2024-05-01.tar.gz -C /tmp ./etc/ssh/sshd_config

小结

Ubuntu 服务器版本升级这件事,技术难度不在那条命令,而在"有没有退路"和"知不知道会变什么"。把本文的流程压缩成一句话:先补齐当前版本的补丁,再在 tmux 里跑 do-release-upgrade,之前必须有一份能用的快照或云盘镜像。

避坑清单,建议直接照抄到变更单:

  1. 升级前务必 apt full-upgrade + 重启,别带着未处理的补丁状态跨版本。
  2. 必须进 tmux / screen,并且提前放行救援 sshd 端口(默认 1022)的安全组与防火墙规则。
  3. 先跑 do-release-upgrade -c 确认目标存在;Prompt=lts 写进 /etc/update-manager/release-upgrades。
  4. /etc/apt/sources.list.d/ 下的第三方源,逐个确认目标版本支持情况。
  5. 按上文的组件版本表,逐个排查 Python venv、PHP、PostgreSQL、Java、自定义 DKMS 模块。
  6. 回滚手段在升级前就要具备并演练过,快照使用率要盯住 snap_percent。
  7. 升级后的复核清单不能省:systemctl --failed、journalctl -p err -b、ss -lntp 与业务健康检查。
  8. 不要在同一窗口做两件事——先把版本升级跑通观察 1~2 天,再做机房搬迁、中间件升级等其他变更。

一句话收尾:能把升级做成"可逆操作"的团队,才有资格谈"平滑升级"。