一台跑了两年的 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-upgrade | LTS 跨大版本升级,同架构 x86_64/arm64 | 30 分钟到 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
逐项说明判断标准:
do-release-upgrade -c会明确打印是否有新版本可用。若毫无输出,多半是/etc/update-manager/release-upgrades里的Prompt=不是lts(还有never、normal两个取值),或者目标 LTS 的首个点版本尚未发布。Ubuntu 的策略是:LTS 到 LTS 的升级通道,要等目标 LTS 的 .1 点版本放出后才开放——这一点决定了你的升级窗口能不能排在发版当月。apt-mark showhold有输出时,逐个判断是人为冻结还是误操作。解除用apt-mark unhold 包名,升级完成后再恢复apt-mark hold 包名。ls -1 /etc/apt/sources.list.d/列出的第三方源是对do-release-upgrade最大的干扰源。升级器会把不认识或不被支持的源注释掉,相关软件(如 Docker CE、某些监控 agent)会随之停在旧版本甚至丢失。建议提前确认这些源是否提供目标版本代号的支持。- 独立估算一版"如果回滚要多久"的时间预算,并写进变更单里。
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 HWE | 5.15 GA / 6.8 HWE | 6.8 GA | 驱动需重编译,DKMS 模块重点核查 |
| systemd | 245 | 249 | 255 | 单元文件参数废弃项会被报 warning |
| glibc | 2.31 | 2.35 | 2.39 | 手工编译的二进制需重新验证 |
| OpenSSL | 1.1.1 | 3.0 | 3.0.x | 1.1 API 代码无法直接链接 |
| Python 3 | 3.8 | 3.10 | 3.12 | 最高频翻车项:vEnv 需重建,distutils 移除 |
| PHP | 7.4 | 8.1 | 8.3 | 7.x 语法与废弃函数大面积报错 |
| PostgreSQL | 12 | 14 | 16 | 数据目录需 pg_upgradecluster,不能原地用 |
| OpenSSH | 8.2p1 | 8.9p1 | 9.6p1 | ssh-rsa 算法被弃用,旧客户端/脚本可能连不上 |
| nginx | 1.18 | 1.18 | 1.24 | 配置基本兼容,第三方模块需重编 |
| gcc/g++ 默认版本 | 9 | 11 | 13 | 老项目编译告警升级为错误 |
三个必须单独排期的技术债:
- 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,之前必须有一份能用的快照或云盘镜像。
避坑清单,建议直接照抄到变更单:
- 升级前务必
apt full-upgrade+ 重启,别带着未处理的补丁状态跨版本。 - 必须进
tmux/screen,并且提前放行救援 sshd 端口(默认 1022)的安全组与防火墙规则。 - 先跑
do-release-upgrade -c确认目标存在;Prompt=lts写进/etc/update-manager/release-upgrades。 - /etc/apt/sources.list.d/ 下的第三方源,逐个确认目标版本支持情况。
- 按上文的组件版本表,逐个排查 Python venv、PHP、PostgreSQL、Java、自定义 DKMS 模块。
- 回滚手段在升级前就要具备并演练过,快照使用率要盯住
snap_percent。 - 升级后的复核清单不能省:
systemctl --failed、journalctl -p err -b、ss -lntp与业务健康检查。 - 不要在同一窗口做两件事——先把版本升级跑通观察 1~2 天,再做机房搬迁、中间件升级等其他变更。
一句话收尾:能把升级做成"可逆操作"的团队,才有资格谈"平滑升级"。
企业QQ咨询




