Ubuntu 24.04 LTS 升级到 26.04 LTS
本页记录把节点从 Ubuntu 24.04 LTS(Noble Numbat)就地升级到 26.04 LTS(Resolute Raccoon)的完整流程, 以及本次在 4 台 N100 节点上实际升级后踩到的坑 —— 通用教程不会提到这些,但升级后必须逐项检查。
通用步骤出处
通用流程参考 How to Upgrade from Ubuntu 24.04 LTS to 26.04 LTS。 本页在其基础上补充 Ubuntu Server + K3s + Rook Ceph 场景的集群侧准备与升级后修复。 官方发布说明见 Ubuntu 26.04 release notes。
升级路径
- Ubuntu 24.04 LTS 是 26.04 LTS 的直属升级基线,可以一步到位。
- 更早的版本(22.04、20.04…)必须先升到 24.04 LTS(或 25.10),不能直接跳到 26.04。
- 26.04 LTS 支持至 2031-04。
升级前
1. 确认升级通道
do-release-upgrade 只在 Prompt=lts 时才提供 LTS → LTS 的升级:
cat /etc/update-manager/release-upgrades | grep -i prompt
sudo sed -i 's/Prompt=.*/Prompt=lts/' /etc/update-manager/release-upgrades
若仍不提供升级(例如首个 point release 尚未进入常规通道),可用 do-release-upgrade -d。
2. 把 24.04 升到最新并重启
sudo apt update && sudo apt full-upgrade
sudo snap refresh # Server 上通常没有 snap,可跳过
sudo reboot
本仓库的节点不要随手 apt autoremove --purge
通用教程会建议升级前先清理不再需要的包。本仓库的节点例外:
为了裁掉用不到的固件,linux-firmware 的依赖关系被刻意改造过(装了空壳
linux-firmware-minimal),参见
裁剪 Linux 固件包。盲目 autoremove
有删掉 i915 / Wi-Fi 固件的风险。
确实要执行时,先确认保留的固件包已被标记为 manual:
apt-mark showmanual | grep linux-firmware
3. 集群侧准备
节点本身是可丢弃的(配置由 Ansible + GitOps 管理),真正需要保护的是 etcd 与集群数据:
# 在任一 master 上手工打一份 etcd 快照(本项目已配置定时快照到 S3,见备份文档)
sudo k3s etcd-snapshot save --name pre-26.04
# 集群当前状态存档
kubectl get nodes -o wide
kubectl -n rook-ceph exec deploy/rook-ceph-tools -- ceph -s
逐台滚动,不要 4 台并行:先 worker,再 control-plane 一台一台来,
每台升级完等节点 Ready、Pod 稳定后再动下一台。
升级窗口避开自动重启
本仓库的 unattended-upgrades 配了 Automatic-Reboot "True"(03:00),
升级完成后用 /var/run/reboot-required 判断是否还需要重启。
执行升级
sudo do-release-upgrade
交互项的选择建议:
| 提问 | 选择 | 原因 |
|---|---|---|
| 是否继续(下载/安装) | y |
— |
| 自动重启服务 | Yes | 否则每个服务都要单独确认,且容易漏 |
| 本地修改过的配置文件 | 保留当前版本(保守) | 但必须按下一节的「检查 3」逐个 diff |
| Remove obsolete packages | y |
清理 24.04 遗留包;注意仍有少量包删不掉(见检查 5) |
升级完成后重启。本次 4 台节点的升级日志位于 /var/log/dist-upgrade/,
它同时也是排查遗留问题的第一手证据(apt-term.log、main.log)。
升级后必查
以下 7 项是本次升级在 4 台节点上实际出现的问题,建议写成检查清单逐项过。
检查 1:/etc/sysctl.conf 被删除,内核参数全部回退
Ubuntu 26.04 的 procps 把 /etc/sysctl.conf 标记为 remove-on-upgrade,
升级时会直接删除它(即使你改过):
$ dpkg-query -W -f='${Conffiles}\n' procps | grep sysctl.conf
/etc/sysctl.conf 72b7c827a9636cda7b3b371091ff2dce remove-on-upgrade
$ sudo grep -i obsolete /var/log/dist-upgrade/apt-term.log
Obsolete conffile /etc/sysctl.conf has been modified by you.
任何把参数写进 /etc/sysctl.conf 的 Ansible 任务,升级后都会静默失效。
本仓库原先正是如此(见 prerequisites 角色),
后果是 fs.inotify.max_user_instances 从 8192 掉回内核默认 128:
fwupd.service/fwupd-refresh.service每小时失败:Could not initialize inotify, check /proc/sys/fs/inotify/max_user_instances- KubeVirt
virt-handler在 inotify 用量高的节点上 CrashLoopBackOff:Failed to create an inotify watcher ... too many open files net.ipv6.conf.all.forwarding从 1 掉回 0、accept_ra从 2 掉回 1
检查与修复:
sysctl fs.inotify.max_user_instances fs.inotify.max_user_watches \
net.ipv4.ip_forward net.ipv6.conf.all.forwarding net.ipv6.conf.all.accept_ra
# 默认值参考:16 GiB 节点 max_user_watches≈122102,32 GiB 节点≈253149
参数要落到 /etc/sysctl.d/ 下的独立文件,不要再用 module 默认的 /etc/sysctl.conf:
- name: Adjust kernel parameters
ansible.posix.sysctl:
name: "{{ item.name }}"
value: "{{ item.value }}"
sysctl_file: /etc/sysctl.d/90-homelab-prerequisites.conf
检查 2:第三方 apt 源被禁用
do-release-upgrade 无法把第三方的一行式 .list 迁移成 deb822 .sources,
于是把文件清空成一行注释,原内容挪到同名的 .disabled,并在里面留下提示。
本次 Tailscale 就是这样丢的,而且因为源没了,升级工具还把 tailscale 判成了「废弃包」:
$ cat /etc/apt/sources.list.d/tailscale.list
# Tailscale packages for ubuntu noble
$ cat /etc/apt/sources.list.d/tailscale.list.disabled
# This file could not be automatically migrated to a .sources file during the upgrade.
逐项检查并恢复(suite 用新代号 resolute,先确认上游确实发布了该 suite):
ls -l /etc/apt/sources.list.d/ # 找 *.disabled / *.migrate / *.distUpgrade
apt-cache policy <第三方包> # Version table 里没有第三方源就是丢了
# /etc/apt/sources.list.d/tailscale.list
# Tailscale packages for ubuntu resolute
deb [signed-by=/usr/share/keyrings/tailscale-archive-keyring.gpg] https://pkgs.tailscale.com/stable/ubuntu resolute main
检查 3:残留 conffile 让 logrotate 整体失败
选择「保留当前版本」之后,release 升级删掉的 conffile 会被留下, 两个包各留一份、glob 又相同,logrotate 直接判重复并让服务失败 (Launchpad #2152107,Noble → Resolute 已知问题):
$ systemctl --failed
● logrotate.service loaded failed failed Rotate log files
$ journalctl -u logrotate -n 3
logrotate[52282]: error: cloud-init-base:1 duplicate log entry for /var/log/cloud-init.log
处理(rm 也可以,改名便于回退;logrotate 会忽略 .disabled):
sudo mv /etc/logrotate.d/cloud-init /etc/logrotate.d/cloud-init.disabled
sudo systemctl start logrotate && sudo logrotate -d /etc/logrotate.conf 2>&1 | grep -i error
顺带把升级留下的 diff 备份逐个看过再决定去留:
sudo find /etc \( -name '*.dpkg-dist' -o -name '*.dpkg-old' -o -name '*.ucf-dist' -o -name '*.ucf-old' \) -print
本次 4 台节点的结论:nut(MODE=netclient 是刻意保留的旧值,正确)、
grub(只差注释与 GRUB_DISTRIBUTOR 写法)、50unattended-upgrades
(本仓库自己管理该文件,.ucf-dist 只是上游新样本)、ca-certificates.conf.dpkg-old(新文件已生效)——
都不需要改动。
检查 4:24.04 的内核包与孤儿模块目录
旧内核不会全被自动清掉。本次每台节点剩 117–120 个 rc 状态包 + 约 40 个不再属于任何包的
/usr/lib/modules/6.8.0-* 目录(约 48 MB/台)。
dpkg -l 'linux-image-*' 'linux-modules-*' 'linux-headers-*' | awk '/^rc/{print $2}'
清理时必须保住还能引导的内核。注意老包把文件登记在 /lib/modules/...、
新包登记在 /usr/lib/modules/...(/lib 是符号链接),
所以 dpkg -S <目录> 查不到属主,别据此删目录:
# 属主来自已安装内核包的文件列表,两种前缀都要归一化
owned=$(dpkg-query -L $(dpkg-query -W -f='${db:Status-Abbrev}${Package}\n' \
'linux-modules-*' 'linux-image-*' | awk '$1=="ii"{print $2}') 2>/dev/null |
sed -n -e 's|^/lib/modules/\([^/]*\)/.*|/usr/lib/modules/\1|p' \
-e 's|^/usr/lib/modules/\([^/]*\)/.*|/usr/lib/modules/\1|p' | sort -u)
# 断言:/boot 能引导的内核,其模块目录必须在 owned 里
for v in /boot/vmlinuz-*; do
printf '%s\n' "$owned" | grep -qxF "/usr/lib/modules/${v#/boot/vmlinuz-}" || echo "ABORT: $v"
done
确认后再 apt-get purge 那些 rc 包、删除确认无属主的目录;
保留运行时内核与一个回退内核(本次是 7.0.0-38 与 6.8.0-146)。
检查 5:仍处于「废弃」但装着的包
升级工具最后会列出 Obsolete: 包(/var/log/dist-upgrade/main.log),
其中一部分删不掉,会一直留着:
$ sudo grep -i 'obsolete package' /var/log/dist-upgrade/main.log
obsolete package 'libpython3.12-minimal' could not be removed
obsolete package 'linux-tools-6.8.0-146' could not be removed
本次留下的主要是 libpython3.12{,-stdlib,-minimal}(仅因为 linux-tools-6.8.0-146
还依赖它),以及 rc 状态的 keyboxd。它们体积小、不可达,
但属于已停止维护的代码,可按需清理 —— 清理前先用
apt-cache rdepends --installed 和 apt-mark showauto 确认没有别的依赖。
检查 6:netplan gateway4 已弃用
netplan.io 1.2 仍接受 gateway4,但每次 netplan generate 都会告警:
$ sudo netplan generate
WARNING **: `gateway4` has been deprecated, use default routes instead.
新装机模板已经改用 routes:(见 user-data.j2)。
已在跑且网络正常的节点不要为了消这个警告去改 cloud-init 生成的 netplan。
检查 7:集群组件逐项确认
systemctl --failed # 期望:空
systemctl is-active k3s
kubectl get nodes -o wide # 期望:全部 Ready
kubectl get pods -A | grep -vE 'Running|Completed'
kubectl -n rook-ceph exec deploy/rook-ceph-tools -- ceph -s
kubectl -n kubevirt get pods -l kubevirt.io=virt-handler
Ceph OSD 可能不会被 Rook 自动接管
本次升级后 n100-cheshi-0 的 osd.1 一直 down+out:它的 bluestore 分区
(/dev/nvme0n1p3)完好、fsid 也正是本集群的 fsid,但没有
rook-ceph-osd-1,osd-prepare 反复报
skipping osd.1: ... belonging to a different ceph cluster(Rook v1.20.7)。
重启 operator 无效。
根因是 rook-ceph-mon Secret 里的 fsid 过期(operator 拿它当期望值去比对磁盘),
而不是磁盘或数据损坏。修复只需把该字段改成 ceph fsid 的真实值并重启 operator,
OSD 会被自动接管并恢复原 crush weight:
FSID=$(kubectl -n rook-ceph exec deploy/rook-ceph-tools -- ceph fsid | tr -d '\r\n')
kubectl -n rook-ceph patch secret rook-ceph-mon --type merge \
-p "{\"data\":{\"fsid\":\"$(printf '%s' "$FSID" | base64 -w0)\"}}"
kubectl -n rook-ceph delete pod -l app=rook-ceph-operator --wait=false
不要对该 OSD 执行 ceph osd purge 或抹掉分区:那会永久销毁一个完好副本,
且在单副本仅剩 3 个 OSD 时毫无必要。完整证据、验证与预防措施见
Rook-Ceph OSD down/out 修复:mon secret FSID 失配。
只要 pool 是 size=2/min_size=1 且 PG 全 active+clean,数据是安全的,
但冗余已经降级 —— 升级后务必确认 ceph -s 里 4 个 OSD 全 up/in。
KubeVirt 的 virt-handler 会跟着 inotify 一起挂
inotify 实例被耗尽时 virt-handler 会在启动阶段 fatal 退出,
表现为 CrashLoopBackOff(Failed to create an inotify watcher)。
修好检查 1 之后删除该 Pod 即可自行恢复。
本仓库需要同步的适配点
升级到 26.04.1 后,仓库里这些位置需要跟上(本次已更新):
metal/roles/pxe_server/defaults/main.yml:ISO 与 netboot.xyz squash 资源的 tag/校验和metal/roles/pxe_server/templates/ubuntu.ipxe.j2:新增resolute菜单项与:resolute_amd64/:resolute_arm64分支metal/roles/pxe_server/templates/user-data.j2:- 包列表:
libpcre3/libpcre3-dev在 26.04 已移除(PCRE1 被放弃),dnsutils过渡包也没了 → 改用bind9-dnsutils。 不改这里,全新 PXE 装机在packages:阶段就会失败。 - netplan:
gateway4→routes
- 包列表:
metal/roles/prerequisites/tasks/main.yml:sysctl 落到/etc/sysctl.d/scripts/firmware-slim、文档中的 24.04 版本描述
验证本次改动的命令:
yamllint -c .yamllint.yaml metal/roles/prerequisites/tasks/main.yml
ansible-playbook --syntax-check --check --limit n100-jumper-0 \
-i metal/inventories/prod.yml metal/boot.yml
.venv/bin/mkdocs build --strict
速查表
| 现象 | 根因 | 处理 |
|---|---|---|
fwupd/fwupd-refresh 失败、virt-handler CrashLoop |
/etc/sysctl.conf 被删除,inotify 限制回退 |
检查 1 |
| 第三方包升不了级 / 被判「废弃」 | .list 被升级工具禁用 |
检查 2 |
logrotate.service failed |
残留 conffile 重复 | 检查 3 |
| 磁盘被旧内核占满 | rc 包 + 孤儿模块目录 |
检查 4 |
ceph -s 里 OSD 少一个 |
Rook 拒绝接管旧 OSD | 检查 7 |
netplan generate 告警 |
gateway4 弃用 |
检查 6 |