systemd升级造成的centos-bootc系统的内核故障

centos-bootc 是bootc系统的一个发行版,可以基于它做bootc的原子更新系统。

在这个系统里,安装包 kernel与systemd 一起升级,会触发一个bug。

复现

我们先来复现一下,首先创建一个测试的Dockerfile:

复制代码
FROM quay.io/centos-bootc/centos-bootc@sha256:49befe588c9c49049bcb63919  
caf27a48a25842aee5e2083281604da2483b9a4  

RUN dnf upgrade -y systemd kernel

执行构建:

复制代码
podman build 

我们看到,在执行kernel 包的post脚本的时候,报了一个错误:

复制代码
 Cleanup          : kernel-core-5.14.0-598.el9.x86_64                                                                                     9/10    
 Running scriptlet: kernel-core-5.14.0-598.el9.x86_64                                                                                     9/10    
 Cleanup          : shim-x64-15-15.el8_2.x86_64                                                                                          10/10    
 Running scriptlet: kernel-modules-core-5.14.0-661.el9.x86_64                                                                            10/10    
 Running scriptlet: kernel-core-5.14.0-661.el9.x86_64                                                                                    10/10    
grub2-probe: error: failed to get canonical path of `overlay'.  
No path or device is specified.  
Usage: grub2-probe [OPTION...] [OPTION]... [PATH|DEVICE]  
Try 'grub2-probe --help' or 'grub2-probe --usage' for more information.  
  
 Running scriptlet: kernel-modules-5.14.0-661.el9.x86_64                                                                                 10/10    
 Running scriptlet: shim-x64-15-15.el8_2.x86_64

构建成功以后,注意:构建成功了,我们以为一切正常。

但是,我们进入刚刚构建出来的镜像查看,就会发现/lib/modules 下面有两个目录。

旧内核目录里只有一个initramfs.img,而新内核目录里面没有:

复制代码
bash-5.1# cd /lib/modules  
bash-5.1# ls  
5.14.0-598.el9.x86_64  5.14.0-661.el9.x86_64
bash-5.1# ls 5.14.0-598.el9.x86_64/  
initramfs.img  
bash-5.1#

当我们把这个镜像作为完整系统使用的时候,比如做成iso安装,或者使用bootc switch 切换,就会直接触发内核panic。(因为找不到当前内核的initramfs.img)

触发这个bug 的条件非常简单,就是在旧版的镜像里,同时执行systemd与kernel的升级。

避免

这个bug 如何避免呢?更简单,在升级kernel 这个包之前,不要升级systemd即可。或者如果要升级,就在kernel之后。

比如Dockerfile 写成:

复制代码
FROM quay.io/centos-bootc/centos-bootc@sha256:49befe588c9c49049bcb63919  
caf27a48a25842aee5e2083281604da2483b9a4  
  
RUN dnf upgrade -y kernel 
  
RUN dnf upgrade -y systemd

执行构建,我们看到在同样的位置的输出:

复制代码
 Cleanup          : kernel-core-5.14.0-598.el9.x86_64                     9/10    
 Running scriptlet: kernel-core-5.14.0-598.el9.x86_64                     9/10    
 Cleanup          : shim-x64-15-15.el8_2.x86_64                          10/10    
 Running scriptlet: kernel-modules-core-5.14.0-661.el9.x86_64            10/10    
 Running scriptlet: kernel-core-5.14.0-661.el9.x86_64                    10/10    
Generating initramfs  
Running depmod  
  
 Running scriptlet: kernel-modules-5.14.0-661.el9.x86_64                 10/10    
 Running scriptlet: shim-x64-15-15.el8_2.x86_64                          10/10

即,如果之前没有升级过systemd,则kernel-core 的post脚本会正常创建出来initramfs 。

等构建完成之后,我们再进入系统查看:

复制代码
bash-5.1# cd /lib/modules  
bash-5.1# ls  
5.14.0-661.el9.x86_64  
bash-5.1# ls 5.14.0-661.el9.x86_64/  
System.map         modules.builtin            modules.modesetting  systemtap  
build              modules.builtin.alias.bin  modules.networking   updates  
config             modules.builtin.bin        modules.order        vdso  
initramfs.img      modules.builtin.modinfo    modules.softdep      vmlinuz  
kernel             modules.dep                modules.symbols      weak-updates  
modules.alias      modules.dep.bin            modules.symbols.bin  
modules.alias.bin  modules.devname            source  
modules.block      modules.drm                symvers.gz

总结一下就是,如果我们在升级内核之前,升级过systemd,内核生成initramfs的过程中就会报错, 而且报错之后不会退出,而是生成一个错误的initramfs。

原因

我们知道,kernel 安装包里的post脚本,会调用到/usr/bin/kernel-install 这个程序,所以我们给这个脚本打开调试开关,来追踪一下。

即,在/usr/bin/kernel-install开头,加上set -x,然后在不同的两个镜像中(一个先升级了systemd一个没有),执行dnf upgrade -y kernel

我们发现,在先升级了systemd 的镜像中,执行到这里:

复制代码
+ KERNEL_INSTALL_PLUGINS='/usr/lib/kernel/install.d/05-rpmostree.instal  
l  
/usr/lib/kernel/install.d/20-grub.install  
/usr/lib/kernel/install.d/20-grubby.install  
/usr/lib/kernel/install.d/50-depmod.install  
/usr/lib/kernel/install.d/50-dracut.install  
/usr/lib/kernel/install.d/60-kdump.install  
/usr/lib/kernel/install.d/90-loaderentry.install  
/usr/lib/kernel/install.d/90-uki-copy.install  
/usr/lib/kernel/install.d/92-crashkernel.install  
/usr/lib/kernel/install.d/99-grub-mkconfig.install'  
+ '[' 0 -gt 0 ']'  
+ case "$COMMAND" in  
+ '[' 1 -lt 1 ']'  
+ '[' -f /lib/modules/5.14.0-665.el9.x86_64/vmlinuz ']'  
+ '[' 1 -eq 0 ']'  
+ for f in $KERNEL_INSTALL_PLUGINS  
+ log_verbose '+/usr/lib/kernel/install.d/05-rpmostree.install add 5.14  
.0-665.el9.x86_64 /boot/1a7e6d7082ed462fa335b4b961d25d09/5.14.0-665.el9  
.x86_64' /lib/modules/5.14.0-665.el9.x86_64/vmlinuz  
+ :  
+ err=0  
+ /usr/lib/kernel/install.d/05-rpmostree.install add 5.14.0-665.el9.x86  
_64 /boot/1a7e6d7082ed462fa335b4b961d25d09/5.14.0-665.el9.x86_64 /lib/m  
odules/5.14.0-665.el9.x86_64/vmlinuz  
+ '[' 0 -eq 77 ']'  
+ '[' 0 -ne 0 ']'  
+ for f in $KERNEL_INSTALL_PLUGINS  
+ log_verbose '+/usr/lib/kernel/install.d/20-grub.install add 5.14.0-66  
5.el9.x86_64 /boot/1a7e6d7082ed462fa335b4b961d25d09/5.14.0-665.el9.x86_  
64' /lib/modules/5.14.0-665.el9.x86_64/vmlinuz  
+ :  
+ err=0  
+ /usr/lib/kernel/install.d/20-grub.install add 5.14.0-665.el9.x86_64 /  
boot/1a7e6d7082ed462fa335b4b961d25d09/5.14.0-665.el9.x86_64 /lib/module  
s/5.14.0-665.el9.x86_64/vmlinuz  
grub2-probe: error: failed to get canonical path of `overlay'.  
No path or device is specified.  
Usage: grub2-probe [OPTION...] [OPTION]... [PATH|DEVICE]  
Try 'grub2-probe --help' or 'grub2-probe --usage' for more information.  
+ '[' 0 -eq 77 ']'  
+ '[' 0 -ne 0 ']'  
+ for f in $KERNEL_INSTALL_PLUGINS  
+ log_verbose '+/usr/lib/kernel/install.d/20-grubby.install add 5.14.0-  
665.el9.x86_64 /boot/1a7e6d7082ed462fa335b4b961d25d09/5.14.0-665.el9.x8  
6_64' /lib/modules/5.14.0-665.el9.x86_64/vmlinuz  
+ :  
+ err=0

而没有升级systemd 的系统中,则是:

复制代码
+ KERNEL_INSTALL_PLUGINS='/usr/lib/kernel/install.d/05-rpmostree.instal  
l  
/usr/lib/kernel/install.d/20-grub.install  
/usr/lib/kernel/install.d/20-grubby.install  
/usr/lib/kernel/install.d/50-depmod.install  
/usr/lib/kernel/install.d/50-dracut.install  
/usr/lib/kernel/install.d/60-kdump.install  
/usr/lib/kernel/install.d/90-loaderentry.install  
/usr/lib/kernel/install.d/90-uki-copy.install  
/usr/lib/kernel/install.d/92-crashkernel.install  
/usr/lib/kernel/install.d/99-grub-mkconfig.install'  
+ '[' 0 -gt 0 ']'  
+ case "$COMMAND" in  
+ '[' 1 -lt 1 ']'  
+ '[' -f /lib/modules/5.14.0-665.el9.x86_64/vmlinuz ']'  
+ '[' 1 -eq 0 ']'  
+ for f in $KERNEL_INSTALL_PLUGINS  
+ log_verbose '+/usr/lib/kernel/install.d/05-rpmostree.install add 5.14  
.0-665.el9.x86_64 /boot/bd492504d7314b1b99cf93272e21d572/5.14.0-665.el9  
.x86_64' /lib/modules/5.14.0-665.el9.x86_64/vmlinuz  
+ :  
+ err=0  
+ /usr/lib/kernel/install.d/05-rpmostree.install add 5.14.0-665.el9.x86  
_64 /boot/bd492504d7314b1b99cf93272e21d572/5.14.0-665.el9.x86_64 /lib/m  
odules/5.14.0-665.el9.x86_64/vmlinuz  
Generating initramfs  
Running depmod  
+ err=77  
+ '[' 77 -eq 77 ']'  
+ break  
+ exit 0

即,正常执行应该是到达05-rpmostreee.install 之后就跳出循环结束。但是在升级过systemd 的系统中,会执行完05-rpmostreee.install 之后,继续执行20-grub.install 。

我们看一下05-rpmostree.install 的源码:

复制代码
#!/usr/bin/bash  
# Check if install.conf is missing or does not include layout=ostree  
if [ ! -f /usr/lib/kernel/install.conf ] || ! grep -q layout=ostree /us  
r/lib/kernel/install.conf; then  
   exit 0  
fi  
# This is the hook that has kernel-install call into rpm-ostree kernel-  
install  
if test -x /usr/bin/rpm-ostree; then  
   exec /usr/bin/rpm-ostree kernel-install "$@"  
fi

它会读取/usr/lib/kernel/install.conf,判断其中layout是否等于ostree。等于ostree的,脚本直接exec到rpm-ostree kernel-install "$@",而没有这个的,则是直接返回0。(当然对于外层循环就会继续执行20-grub.install等......)

我们分别查看升级过systemd 与没有升级过systemd的系统里这个install.conf。

升级过systemd:

复制代码
bash-5.1# cat /usr/lib/kernel/install.conf    
#  This file is part of systemd.  
#  
#  systemd is free software; you can redistribute it and/or modify it u  
nder the  
#  terms of the GNU Lesser General Public License as published by the F  
ree  
#  Software Foundation; either version 2.1 of the License, or (at your  
option)  
#  any later version.  
#  
# See kernel-install(8) for details.  
  
#layout=bls|other|...  
#initrd_generator=dracut|...

没有升级过systemd:

复制代码
bash-5.1# cat /usr/lib/kernel/install.conf    
# kernel-install will not try to run dracut and allow rpm-ostree to  
# take over. Rpm-ostree will use this to know that it is responsible  
# to run dracut and ensure that there is only one kernel in the image  
layout=ostree

我们查询一下,发现这个文件属于systemd-udev。即我们升级systemd的时候,间接升级了systemd-udev,把layout 置空了。

解决

我们改一下Dockerfile:

复制代码
FROM quay.io/centos-bootc/centos-bootc@sha256:49befe588c9c49049bcb63919  
caf27a48a25842aee5e2083281604da2483b9a4  

RUN dnf upgrade -y systemd  
  
RUN echo "layout=ostree" >> /usr/lib/kernel/install.conf  
  
RUN dnf upgrade -y kernel

,验证一下:

复制代码
 Running scriptlet: kernel-modules-core-5.14.0-665.el9.x86_64           
  10/10    
 Running scriptlet: kernel-core-5.14.0-665.el9.x86_64                   
  10/10    
Generating initramfs  
Running depmod  
  
 Running scriptlet: kernel-modules-5.14.0-665.el9.x86_64

果然正常。

那么,既然systemd中会更改这个值,为什么05-rpmostree.install 却还在依赖这个值呢?

我们查询一下05-rpmostree.install 这个文件,发现它属于rpm-ostree 包。我们升级一下最新的rpm-ostree软件的版本,看看是不是最新的rpm-ostree 已经不再依赖systemd-udev的这个文件:

复制代码
bash-5.1# dnf upgrade -y rpm-ostree
...

bash-5.1# rpm -qf /usr/lib/kernel/install.d/05-rpmostree.install    
rpm-ostree-2025.11-1.el9.x86_64  
bash-5.1# cat /usr/lib/kernel/install.d/05-rpmostree.install    
#!/usr/bin/bash  
# Check if install.conf is missing or does not include layout=ostree  
if [ ! -f /usr/lib/kernel/install.conf ] || ! grep -q layout=ostree /us  
r/lib/kernel/install.conf; then  
   exit 0  
fi  
# This is the hook that has kernel-install call into rpm-ostree kernel-  
install  
if test -x /usr/bin/rpm-ostree; then  
   exec /usr/bin/rpm-ostree kernel-install "$@"  
fi

看来不行。最新的rpm-ostree 依然是使用systemd-udev 中的layout 的值。rpm-ostree就是需要这个值,但是还避免不了systemd-udev改变这个文件。

总结

所以,这个bug 产生的根本原因是:

rpm-ostree 包依赖了systemd-udev包中的layout值,而升级systemd-udev,会删掉这个值。所以升级systemd-udev之后,就会导致rpm-ostree 这个包构建内核initramfs.img的错误。

所以,bootc (或者systemd)官方可以有两个方向的改进:

一、systemd-udev软件包,把这个install.conf 文件设置为配置类型。这样用户在进行升级的时候,rpm就会把文件命名为install.conf.rpmnew,用户就知道配置文件进行了更新,由用户决定是否同步。

二、bootc 的构建文档应该明确提示,说明在构建镜像过程中,升级systemd等程序会改变这个文件,而系统的关键模块(如内核)会依赖这个文件,需要用户在升级systemd等程序后,手动修正,再进行其它操作。

方案

而对于我们用户来说,为了让程序更加健壮,我们需要在ostree类系统的构建脚本中,明确地在升级systemd之后,修正这个值。避免其它部分也因为这个值的错误,而导致bug。

即,升级后重置:

复制代码
RUN dnf upgrade -y systemd-udev && echo "layout=ostree" > /usr/lib/kernel/install.conf

或者干脆先保存,再升级,最后再恢复:

复制代码
RUN cp /usr/lib/kernel/install.conf /usr/lib/kernel/install.conf.bak && dnf upgrade -y systemd-udev && cp /usr/lib/kernel/install.conf.bak /usr/lib/kernel/install.conf
相关推荐
若风的雨2 小时前
HIP Runtime资源分配相关的核心API分类总结
linux
老友@2 小时前
JMeter 在 Linux 环境下进行生产级性能压测的完整实战指南
java·linux·jmeter·性能优化·系统架构·压测·性能瓶颈
加强洁西卡2 小时前
【RISC-V】riscv64-linux-gnu工具链都有哪些工具
linux·gnu·risc-v
hunter14502 小时前
docker 在centos和ubuntu的安装
linux·docker·centos
wypywyp2 小时前
6.linux环境优化——vscdoe ssh mobaxterm
linux·运维·ssh
Cher ~2 小时前
【linux】零拷贝技术
linux·服务器·网络
方便面不加香菜2 小时前
Linux基础开发工具--yum和vim
linux·运维·服务器
铁手飞鹰2 小时前
[Linux笔记]内核裁剪
linux·笔记·linux内核裁剪
夏乌_Wx2 小时前
fork、内存管理与虚拟内存总结
linux