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
相关推荐
HLC++10 小时前
Linux的进程间通信
android·linux·服务器
二宝哥11 小时前
CentOS 7.9 系统下 Redis 5.0.4 单机安装与源码编译安装指南
redis·centos
华清远见IT开放实验室11 小时前
实验室建设案例 | 石家庄科技信息职业学院嵌入式实验室——从底层硬件到系统应用,一所应用型高校的嵌入式人才培养这样落地
linux·arm开发·stm32·嵌入式硬件·高校·实验室建设
groundhappy14 小时前
idalib安装和codex ida-mcp配置
linux·开发语言·python
通信小小昕15 小时前
Ubuntu 26.04 中文输入法安装
linux·运维·ubuntu
张小姐的猫16 小时前
【Linux】网络编程 —— HTTP协议(上)
linux·运维·服务器·网络·http·单例模式·策略模式
栩栩云生16 小时前
AI 写代码犯的错,早被写进了错题集
linux·安全·ai编程
imc.1117 小时前
linux基础IO
linux·运维·服务器
BelongPanda19 小时前
Linux Nginx 纯手动 Let‘s Encrypt 泛域名证书配置教程
linux·nginx
酷可达拉斯19 小时前
Linux操作系统-shell编程(0)
linux·运维·服务器·python·云计算