前言
因为之前笔者所在公司的一款虚拟化平台产品在数据中心服务器上部署时出现不兼容现象,怀疑是安装介质中我们所定制的Linux内核与新服务器硬件不兼容导致,于是就牵涉到升级安装介质中Linux内核的工作。由于这款虚拟化平台产品是在CentOS6.5的基础上定制得到,所以本质上相当于直接更新CentOS6安装介质中Linux内核。关于如何定制一张Linux发行版光盘,以及如何在一个完整现有的Linux系统上升级内核,网上各种文章铺天盖地几乎已成大路货。然而直接升级发行版介质中的内核却少有提及,为此我将整个工作过程记录下来,所用方式方法不一定最优,但意在抛砖引玉。
在我进行这项工作的时候,发现kernel.org上的内核最新版本已悄然变为4.8.6,这是一个stable版本,于是乎决定就用它了。
安装光盘目录结构分析
CentOS6.5安装光盘目录结构如下图所示:
图1 CentOS发行版ISO目录树
(1) EFI目录主要用于64位的基于EFI的系统引导。其中的BOOT目录下的BOOTX64.conf为grub的配置文件,用于显示引导菜单。
(2) images目录:包含有各种引导镜像。最重要的是引导第二阶段安装程序需要用到的镜像文件install.img(CentOS7安装盘中该文件名称是squashfs.img),该镜像文件内部文件系统类型是squashfs,未经压缩,可以直接挂载(只读),anaconda程序就在这个镜像文件中。该目录中还包含一个pxeboot目录,主要用于制作PXE安装方式引导介质。
(3) isolinux目录:有开机引导系统安装的内核(vmlinuz)及临时文件系统(initrd.img),在引导系统时会载入内存。
(4) Packages目录:包含安装所需的所有二进制RPM包。
(5) repodata目录:一个位于光盘介质上的yum源,内部包含了软件仓库所有的配置文件。
(6) TRANS.TBL文件:记录当前目录的列表,用mkisofs的-T参数重新生成,主要是为了支持长文件名称。
(7) .discinfo文件是安装介质的识别信息。.treeinfo文件记录不同安装方式安装程序所在的目录结构,如PXE方式时,内核kernel=images/pxeboot/vmlinuz,根文件系统initrd=images/pxeboot/initrd.img。
CentOS安装光盘是一张引导盘,启动时,引导程序会分别将vmlinuz和initrd.img载入内存,待内核初始化完成后,会执行initrd中的/sbin/init,/sbin/init加载/sbin/loader,最终加载运行install.img中的anaconda安装程序。anaconda会根据配置和用户操作分别安装Packages文件夹下的rpm包,操作系统内核也以rpm包的形式存在其中。因此,需要更新的文件主要是光盘中isolinux、image/pxeboot下的vmlinuz和initrd.img文件,Packages目录下的内核RPM包。
编译内核及模块
第一步,从https://www.kernel.org/上下载新版本的内核源码,linux内核版本号中的第二位(即次版本号)为偶数的版本为稳定版,为奇数的版本是处于开发中的非稳定版,本文考虑到更新后的安装程序需要用于生产环境,因此,选择的版本号为4.8.6,即稳定版。下载后直接用tar将源码解压到/usr/src/kernels路径下。
第二步,配置和编译内核。在配置和编译内核前,需先准备好相关工具环境,先执行yum –y install gcc ncurses-devel openssl createrepo,在源码目录下分别执行下述命令即可生成内核二进制文件:
# make menuconfig //启动一个图形化内核配置界面,该配置工具会将当前系统内核配置作为默认配置,配置好后选择<save>按钮,会再内核源码目录中生成一个.config文件保存配置。
# make bzImage //编译内核源码,得到内核压缩文件vmlinuz
# make modules //编译内核模块
# make modules_install //安装内核模块
# make install //安装内核
另外,还可能用到的make目标有:
# make clean //清除构建过程中生成的中间文件和目标文件,但保留内核配置及构建版本号
# make mrproper //清除构建过程中的中间文件和目标文件,同时,清除内核配置及构建版本号
生成内核RPM包
执行:
# make rpm-pkg
可以生成内核RPM包,该命令生成的内核RPM包可在现有完整系统上直接安装,执行:
# rpm –ivh kernel-4.8.6-1.el6.x86_64.rpm < --force > < --nodeps>
但是,如果将新生成的内核RPM包替换掉Packages目录中原有的内核RPM包,则系统在安装后启动时可能会抛出KernelPanic,原因是内核RPM包在执行过程中需要依赖其它一些第三方工具来最终完成安装流程,这些依赖关系需要配置到RPM包中,使得在安装内核前,先安装这些被依赖的工具包,否则就会导致安装不完整。下图是原有2.6版本内核RPM包和新生成的4.8.6版本的RPM包依赖关系对比图:
图2 2.6.32版本内核包默认依赖
图3 4.8.6版本内核包默认依赖
从上述两张图中可以看出明显的差别,完成内核安装需执行module-init-tools、initscripts、grubby、dracut等几个包中的脚本,因此这几个包必须先于内核安装。为了实现这一目的,我所采用的办法是修改/usr/src/kernels/linux-4.8.6/scripts/package下的mkspec脚本,这是因为在make rpm-pkg时,会调用到该脚本来生成内核RPM的SPEC文件。最终在mkspec文件中增加如下内容:
echo "PreReq: fileutils, module-init-tools, initscripts, grubby >= 7.0.4-1, dracut-kernel, \
/sbin/new-kernel-pkg, device-mapper-event-libs, device-mapper-libs, \
device-mapper-multipath, device-mapper-multipath-libs, NetworkManager, \
NetworkManager-glib, crypto-utils, cryptsetup-luks, cryptsetup-luks-libs, lvm2, \
dmraid, dmraid-events, dracut, file, python, python-cryptsetup, sysvinit-tools"
其中,“PreReq”表示依赖类型为install前。保存mkspec文件并重新makerpm-pkg,之后生成的RPM就可以直接放入到Packages目录中用于系统全新安装了,检查新生成的内核RPM可以看到如下景象:
图4中的这些依赖项是我初步整理之后的一个结果,可能有个别依赖不是必要的,但由于编译构建内核花费时间较长,为了减少失败次数,保险起见将其加到PreReq中了。具体依赖关系还有待进一步深入分析。
更新initrd.img
CentOS6.5安装镜像中的initrd.img文件是一个CPIO包,采用LZMA压缩算法进行了压缩,可以直接用lzma和cpio进行解压解包后得到一个完整的目录树。这个文件本质上是一个ramfs,它的地位和作用与系统中/boot/initramfs-<version>.<EL>.<arch>.img文件是一致的,即:为安装程序内核提供了一个带有驱动模块的临时内存文件系统,同时initrd.img中还包含了安装程序第一阶段所需要的初始化程序/init、/sbin/loader。
更新initrd就是要将新编译出来的模块文件添加到它内部/modules中,但是,我将更新后的操作系统/lib/modules/下的内容直接添加进去却失败了,内核启动时同样出现panic,而且这种方式也导致initrd.img文件尺寸暴增,后来我又将编译后生成的initramfs文件中的modules添加进去,也失败了,内核启动时识别不了网络设备,因为initramfs中的modules不完整。看来,只能通过dracut重新生成一份带完整驱动模块的initramfs文件,并从该文件中得到modules。
#dracut -f -v --hostonly -k "/lib/modules/4.8.6/" /boot/initramfs-4.8.6.img4.8.6
上述命令利用dracut生成一份用于本地使用的initramfs文件,该文件包含了当前宿主机上已经被加载的模块文件。如果想将生成的initramfs文件放到别的主机上使用,则去掉“-- hostonly”即可,执行:
#dracut -f -v -k "/lib/modules/4.8.6/" /boot/initramfs-4.8.6.img4.8.6
这样可以让initramfs内的驱动模块相对完整一些。但CentOS安装镜像需要在不同硬件环境下使用,因此需要将尽可能多的驱动模块包含进去,dracut提供了一种配置机制让我们可以定制模块或驱动。在/etc/dracut.conf中可以看到如下内容:
图5 dracut.conf文件
可以将需要添加和滤除的模块驱动追加到“dracutmodules”、“omit_dracutmodules”、“add_dracutmodules”、“add_drivers”等变量中,关于这些变量的具体含义在其上方都有注释说明。不难看出,为了实现将尽可能完整的驱动模块打包进initramfs文件这一目的,只需要将所有待定制的驱动模块文件的文件名(包含路径且不含后缀)添加到dracutmodules这个变量中,在进行具体操作前,还有两件事情需要完成:
第一,dracut.conf是老版本dracut所使用的配置文件,根据要求新版本的配置文件须放入到/etc/dracut.conf.d/目录下,文件名可自定义,但后缀必须是conf,因此,事先将dracut.conf拷贝一份进dracut.conf.d并重命名为mydracut.conf。
第二,由于待添加的驱动模块文件数量众多,一个个手动加到add_driver中是件苦差。我采用的办法是写一个脚本,把这些文件路径和文件名都提出来导出为一个环境变量,然后将环境变量加到mydracut.conf中,具体地,可以创建一个expmod.sh文件并添加如下内容:
#!/bin/bash
ALL_DRVS=
all_drvs_l="$(find /lib/modules/4.8.6/kernel/ -name '*.ko')" #找到所有后缀为ko的模块文件
for i in $all_drvs_l; do
ALL_DRVS=${ALL_DRVS}${i//.ko/ } #dracut要求滤掉文件后缀名ko
done
export ALL_DRVS
接着,修改/etc/dracut.conf.d/mydracut.conf中第13行为:
add_drivers+="$ALL_DRVS"
之后开始生成一个initramfs,可暂时命名为tmpinitramfs.img。
# chmod u+x expmod.sh
# source expmod.sh
# dracut -f -v -k "/lib/modules/4.8.6/" /tmp/tmpinitramfs.img
这时,生成的tmpinitramfs.img包含的驱动模块会非常完整,生成这个文件的目的当然是为了得到其中/lib/modules的内容。下面开始正式升级光盘中的initrd.img文件。
第1步,创建两个临时目录
# mkdir -pv /tmp/initrd.d /tmp/initrd.d.tmp
将原来CentOS光盘镜像中的initrd.img拷贝到/tmp/initrd.d中,将新生成的tmpinitramfs.img拷贝到/tmp/initrd.d.tmp中。
第2步,将原有的initrd.img解压解包
# cd /tmp/initrd.d
# mv initrd.img initrd.img.lzma
# lzma -d initrd.img.lzma
# cpio -ivmd< initrd.img
第3步,提取新版本(4.8.6)的modules,并将其中所有文件拷贝至第2步操作后的目录结构中
# cd /tmp/initrd.d.tmp
# mv tmpinitramfs.img tmpinitramfs.img.gz
# gunzip tmpinitramfs.img.gz
# cpio -ivmd<tmpinitramfs.img
# cp -r lib/modules/4.8.6 /tmp/initrd.d/modules
第4步,生成新的initrd.img
# cd /tmp/initrd.d
# rm -f initrd.img
# find . | cpio -o -H newc>initrd.img
至此,新的initrd.img生成了。
更新光盘介质中软件仓库
更新Packages目录中的内核及相关工具,并生成新的yum仓库。
第1步,用过yum工具更新内核工具包module-init-tools,initscripts,grubby,sysvinit-tools,事先确保系统已经安装了yum-utils
# yum installyum-utils
然后,下载上述工具及其依赖包
# yum install –y module-init-tools,initscripts,grubby,sysvinit-tools--downloadonly --downloaddir=./
第2步,在/tmp内建立一个临时目录作为新软件仓库base目录
# cd /tmp && cd $(mktemp -d Packages.XXXXX)
挂载原有光盘镜像,将光盘镜像中Packages目录拷贝到/tmp/ Packages.XXXXX中。
# mount –t iso9660 /dev/sr0 /mnt
#cp -r /mnt/Packages ./
第3步,将第1步下载的RPM包连同kernel-4.8.6-1.x86_64.rpm一并替换到/tmp/ Packages.XXXXX中,并将原来的内核kernel-2.6.32-279.el6.x86_64.rpm、kernel-firmware-2.6.32-279.el6.x86_64.rpm,同时还应把原来的TRANS.TBL删掉。
第4步,生成新的TRANS.TBL文件
# mkisofs -v -r -T -J -o ./Packages.iso ./Packages
这一步生成Packages.iso是为了得到其中的TRANS.TBL文件,挂载Packages.iso,将其中的TRANS.TBL拷贝进/tmp/ Packages.XXXXX中
第5步,生成新的yum仓库
# cp /mnt/repodata/XXXXXXXXXXXXX-comps.xml ./ comps.xml
# createrepo –g comps.xml ./
上述comps.xml文件是Packages中关于软件包分组的配置文件,安装程序Anaconda也会解析该文件,由于不涉及到对分组的修改,所以直接使用原来的分组配置就可以了。执行完上述命令后可以看到在当前目录下生成了新的repodata目录,里面包含了新的yum仓库所有的配置信息。
更新光盘镜像
将第三节编译后得到的vmlinuz、第五节生成的initrd.img以及第六节更新后的Packages、repodata替换原来的光盘。
利用mkisofs生成新的可引导光盘镜像。
总结
这次升级基本上是用手动操作的方式一步步完成的,其实也试图希望寻找到一些现有的工具来完成,但很遗憾没有找到。如果各路大侠有更好的方式方法,希望能得到指点。