这篇文档先说明编译安装内核的背景和原因,然后以 4.14 内核为例,介绍详细步骤。
<!-- TOC -->
- [为何要自编译内核?](#为何要自编译内核)
- [选择哪个内核版本?](#选择哪个内核版本)
- [选择 4.14 还是 4.19内核 ?](#选择-414-还是-419内核-)
- [编译内核](#编译内核)
- [准备编译环境](#准备编译环境)
- [下载 kernel.org 内核](#下载-kernelorg-内核)
- [拷贝内核配置文件](#拷贝内核配置文件)
- [设置自定义内核版本字符串](#设置自定义内核版本字符串)
- [并发编译内核](#并发编译内核)
- [安装内核模块](#安装内核模块)
- [查看安装的设备驱动文件信息](#查看安装的设备驱动文件信息)
- [安装内核文档](#安装内核文档)
- [安装内核](#安装内核)
- [安装内核配置文件](#安装内核配置文件)
- [安装内核符号表文件](#安装内核符号表文件)
- [创建 initramfs 文件](#创建-initramfs-文件)
- [更新 Bootloader 配置文件](#更新-bootloader-配置文件)
- [设置从新内核启动](#设置从新内核启动)
- [重启机器,确认使用了新版内核](#重启机器确认使用了新版内核)
- [TODO](#todo)
- [参考](#参考)
<!-- /TOC -->
## 为何要自编译内核?
CentOS 7.x 自带的 3.10.x 内核存在诸多 Bugs(见文档尾部的链接列表),导致运行的 Docker 或 Kubernetes 不稳定,进而影响上层业务的可用性。解决的版本是升级到对容器支持更好的高版本内核。
RedHat/CentOS 官方只维护 OS 自带的 3.10.x 内核,对于高版本内核,需要自己获取安装包,常见的两种方式是:
1. 社区编译版本,如 elrepo 发布的 lt 和 ml 内核 RPM 包;
2. 自己配置、编译;
社区编译发布的内核不满足我们的需求,如 elrepo 发布的两个版本:
1. 4.4.x lt:部分设备驱动程序版本较老,场内安装后出现找不到磁盘的情况,导致系统启动失败(更多场内 cases,参考文末的链接列表);
2. 5.x ml:当前主线版本内核,不稳定,而且不是 kernel.org 长期支持版本;
所以,我们只能自己配置、编译内核。
需要注意的是,RedHat 官方只支持 OS 自带和后续发布的 3.10 内核,不支持社区和自编译版本内核。
## 选择哪个内核版本?
我们当前使用的是 4.4 LTS 内核,在场内升级部署后,出现网卡驱动 Bugs、重启时不识别 RAID 卡、启动失败的情况。
所以选择内核版本的原则是:kernel.org 发布的、长期支持的、4.4 以上版本。
目前 kernel.org 发布的内核版本如下:
![](image/2019-04-04-11-50-50.png)
可见,候选的 LTS 内核是:
1. 4.9.167
2. 4.14.110
3. 4.19.33
与 4.4 一样,4.9.167 包含的 megaraid_sas RAID 卡驱动版本是 "06.811.02.00-rc1",不满足需求。
4.14.110 和 4.14.110 对应的驱动版本较新("07.702.06.00-rc1","07.706.03.00-rc1"),可以满足需求。
所以,我们可用的 LTS 内核缩减为两个:
1. 4.14.110
2. 4.19.33
## 选择 4.14 还是 4.19内核 ?
我们做了一些调研,结果如下:
1. 4.19.x 不稳定,特别是 iptables conntrack 相关功能,被吐槽为 [badest release in the last 6 years](https://lists.fedoraproject.org/archives/list/kernel@lists.fedoraproject.org/thread/7GXLO5GLD7H4UUFDORLZE4SHQUBFQQUK/?sort=date):
- [randomly crashes iptables connlimit module](https://bugzilla.kernel.org/show_bug.cgi?id=202013)
- [connlimit is broken in whole 4.19.x series and 4.20.0](https://bugzilla.kernel.org/show_bug.cgi?id=202065)
- [system fails to boot with内核 4.19.8-300.fc29.x86_64](https://bugzilla.redhat.com/show_bug.cgi?id=1658623)
2. 4.14.x 发布时间(2017-12-12)较 4.19.x 发布时间(2018-10-22)早,更稳当;
3. Google Cloud 使用 4.14.x 作为自己公有云 VM 缺省内核;
所以,我们更倾向于满足需求的、更稳定的 4.14.x LTS 版本。
## 编译内核
### 准备编译环境
编译内核需要 gcc、bc、bison 等 GNU 编译工具链,以及一些库头文件。 这里安装相关软件包:
``` bash
[root@m7-uiauto-ssd001 ~]# yum groupinstall 'Development Tools'
[root@m7-uiauto-ssd001 ~]# yum install bc elfutils-libelf-devel openssl-devel ncurses ncurses-devel bison
```
### 下载 kernel.org 内核
从官方下载 4.14 系列最新版内核源码,解压到内核源码目录 /usr/src/kernels:
``` bash
[root@m7-uiauto-ssd001 kernels]# cd /usr/src/kernels
[root@m7-uiauto-ssd001 kernels]# wget https://cdn.kernel.org/pub/linux/kernel/v4.x/linux-4.14.110.tar.xz
[root@m7-uiauto-ssd001 kernels]# tar -xvf linux-4.14.110.tar.xz
```
### 拷贝内核配置文件
内核的配置参数非常多,稍有不慎就可能引起核心功能、驱动的缺失。所以,一般我们使用一个验证过的、版本相近的内核配置文件来配置内核,而不是自己从头一步步配置。
elrepo 发布的内核被广泛使用,可以使用它的内核配置文件作为我们配置的基础。
对于 elrepo 而言,4.14 是历史版本,不是当前的 lt 和 ml 版本,所以需要到 elrep archive repo 下载归档的 RPM,然后使用 cpio 解压 RPM,获得内核配置文件:
``` bash
[root@m7-uiauto-ssd001 kernels]# mkdir elrepo && cd elrepo
[root@m7-uiauto-ssd001 kernels]# wget http://mirror.rc.usf.edu/compute_lock/elrepo/kernel/el7/x86_64/RPMS/kernel-ml-4.14.15-1.el7.elrepo.x86_64.rpm
[root@m7-uiauto-ssd001 elrepo]# rpm2cpio kernel-ml-4.14.15-1.el7.elrepo.x86_64.rpm | cpio -idmv &>/dev/null
[root@m7-uiauto-ssd001 elrepo]# ls -l boot/
total 11100
-rw------- 1 root root 3704096 Dec 22 01:37 System.map-4.14.15-1.el7.elrepo.x86_64
-rw-r--r-- 1 root root 192761 Dec 22 01:37 config-4.14.15-1.el7.elrepo.x86_64
-rw-r--r-- 1 root root 361642 Dec 22 01:41 symvers-4.14.15-1.el7.elrepo.x86_64.gz
-rwxr-xr-x 1 root root 7095680 Dec 22 01:37 vmlinuz-4.14.15-1.el7.elrepo.x86_64
```
使用 eprepo RPM 内核配置文件配置内核:
``` bash
[root@m7-uiauto-ssd001 elrepo]# cd ../kernels/linux-4.14.110
[root@m7-uiauto-ssd001 linux-4.14.110]# make clean && make mrproper # 保证内核树的绝对干净;
[root@m7-uiauto-ssd001 linux-4.14.110]# cp ../elrepo/boot/config-4.14.15-1.el7.elrepo.x86_64 .config
```
### 设置自定义内核版本字符串
社区发布的内核包一般有一定的命名惯例,如 3.10.0-862.el7.x86_64、4.4.166-1.el7.elrepo.x86_64 等,用于区分来源和版本。
所以我们也需要给自编译的内核加上类似的版本字符串。 这里设置为 .0-el7.4pd.x86_64,对应的内核配置项为 General Setup -> Local version - append to内核 release:
``` bash
[root@m7-uiauto-ssd001 linux-4.14.110]# make menuconfig # 编辑后保存
[root@m7-uiauto-ssd001 linux-4.14.110]# diff ../elrepo/boot/config-4.14.15-1.el7.elrepo.x86_64 .config
3c3
< # Linux/x86_64 4.14.15-1.el7.elrepo.x86_64 kernel Configuration
---
> # Linux/x86 4.14.110 kernel Configuration
21c21
< CONFIG_LOCALVERSION=""
---
> CONFIG_LOCALVERSION=".0-el7.4pd.x86_64"
5231a5232
> # CONFIG_DRM_FBDEV_LEAK_PHYS_SMEM is not set
6178a6180
> # CONFIG_USB_ROLE_SWITCH is not set
```
### 并发编译内核
``` bash
[root@m7-uiauto-ssd001 linux-4.14.110]# make -j 24
```
### 安装内核模块
``` bash
[root@m7-uiauto-ssd001 linux-4.14.110]# make modules_install
```
内核模块被安装到系统的 /lib/modules/<kernel-version> 目录下,目录名是内核版本和上面配置的自定义版本字符串的组合:
``` bash
[root@m7-uiauto-ssd001 linux-4.14.110]# ls -l /lib/modules/ | grep 4pd
drwxr-xr-x 3 root root 4096 Apr 3 17:49 4.14.110.0-el7.4pd.x86_64
drwxr-xr-x 3 root root 4096 Apr 3 22:49 4.14.110.0-el7.4pd.x86_64
[root@m7-uiauto-ssd001 linux-4.14.110]# ls -l /lib/modules/4.14.110.0-el7.4pd.x86_64/
total 4084
lrwxrwxrwx 1 root root 30 Apr 3 22:49 build -> /usr/src/kernels/linux-4.14.110
drwxr-xr-x 13 root root 4096 Apr 3 22:49 kernel
-rw-r--r-- 1 root root 1106323 Apr 3 22:49 modules.alias
-rw-r--r-- 1 root root 1071287 Apr 3 22:49 modules.alias.bin
-rw-r--r-- 1 root root 7058 Apr 3 22:49 modules.builtin
-rw-r--r-- 1 root root 9279 Apr 3 22:49 modules.builtin.bin
-rw-r--r-- 1 root root 341508 Apr 3 22:49 modules.dep
-rw-r--r-- 1 root root 500405 Apr 3 22:49 modules.dep.bin
-rw-r--r-- 1 root root 411 Apr 3 22:49 modules.devname
-rw-r--r-- 1 root root 136971 Apr 3 22:49 modules.order
-rw-r--r-- 1 root root 534 Apr 3 22:49 modules.softdep
-rw-r--r-- 1 root root 437869 Apr 3 22:49 modules.symbols
-rw-r--r-- 1 root root 537188 Apr 3 22:49 modules.symbols.bin
lrwxrwxrwx 1 root root 30 Apr 3 22:49 source -> /usr/src/kernels/linux-4.14.110
```
+ 内核模块目录中的 build 和 source 目录链接到内核源码目录,后续编译内核模块时需要这两个目录。
### 查看安装的设备驱动文件信息
``` bash
[root@m7-uiauto-ssd001 linux-4.14.110]# modinfo /lib/modules/4.14.110.0-el7.4pd.x86_64/kernel/drivers/scsi/megaraid/megaraid_sas.ko
filename: /lib/modules/4.14.110.0-el7.4pd.x86_64/kernel/drivers/scsi/megaraid/megaraid_sas.ko
description: Avago MegaRAID SAS Driver
author: megaraidlinux.pdl@avagotech.com
version: 07.706.03.00-rc1
license: GPL
srcversion: 86E71DF0BD7853161D6991C
alias: pci:v00001000d0000001Csv*sd*bc*sc*i*
alias: pci:v00001000d0000001Bsv*sd*bc*sc*i*
alias: pci:v00001000d00000017sv*sd*bc*sc*i*
alias: pci:v00001000d00000016sv*sd*bc*sc*i*
alias: pci:v00001000d00000015sv*sd*bc*sc*i*
alias: pci:v00001000d00000014sv*sd*bc*sc*i*
alias: pci:v00001000d00000053sv*sd*bc*sc*i*
alias: pci:v00001000d00000052sv*sd*bc*sc*i*
alias: pci:v00001000d000000CFsv*sd*bc*sc*i*
alias: pci:v00001000d000000CEsv*sd*bc*sc*i*
alias: pci:v00001000d0000005Fsv*sd*bc*sc*i*
alias: pci:v00001000d0000005Dsv*sd*bc*sc*i*
alias: pci:v00001000d0000002Fsv*sd*bc*sc*i*
alias: pci:v00001000d0000005Bsv*sd*bc*sc*i*
alias: pci:v00001028d00000015sv*sd*bc*sc*i*
alias: pci:v00001000d00000413sv*sd*bc*sc*i*
alias: pci:v00001000d00000071sv*sd*bc*sc*i*
alias: pci:v00001000d00000073sv*sd*bc*sc*i*
alias: pci:v00001000d00000079sv*sd*bc*sc*i*
alias: pci:v00001000d00000078sv*sd*bc*sc*i*
alias: pci:v00001000d0000007Csv*sd*bc*sc*i*
alias: pci:v00001000d00000060sv*sd*bc*sc*i*
alias: pci:v00001000d00000411sv*sd*bc*sc*i*
depends:
retpoline: Y
intree: Y
name: megaraid_sas
vermagic: 4.14.110.0-el7.4pd.x86_64 SMP mod_unload modversions
parm: lb_pending_cmds:Change raid-1 load balancing outstanding threshold. Valid Values are 1-128. Default: 4 (int)
parm: max_sectors:Maximum number of sectors per IO command (int)
parm: msix_disable:Disable MSI-X interrupt handling. Default: 0 (int)
parm: msix_vectors:MSI-X max vector count. Default: Set by FW (int)
parm: allow_vf_ioctls:Allow ioctls in SR-IOV VF mode. Default: 0 (int)
parm: throttlequeuedepth:Adapter queue depth when throttled due to I/O timeout. Default: 16 (int)
parm: resetwaittime:Wait time in seconds after I/O timeout before resetting adapter. Default: 180 (int)
parm: smp_affinity_enable:SMP affinity feature enable/disable Default: enable(1) (int)
parm: rdpq_enable: Allocate reply queue in chunks for large queue depth enable/disable Default: disable(0) (int)
parm: dual_qdepth_disable:Disable dual queue depth feature. Default: 0 (int)
parm: scmd_timeout:scsi command timeout (10-90s), default 90s. See megasas_reset_timer. (int)
```
### 安装内核文档
``` bash
[root@m7-uiauto-ssd001 linux-4.14.110]# install -d /usr/share/doc/linux-4.14.110.0-el7.4pd.x86_64
[root@m7-uiauto-ssd001 linux-4.14.110]# cp -r Documentation/* /usr/share/doc/linux-4.14.110.0-el7.4pd.x86_64
```
### 安装内核
``` bash
[root@m7-uiauto-ssd001 linux-4.14.110]# cp -iv arch/x86/boot/bzImage /boot/vmlinuz-4.14.110.0-el7.4pd.x86_64
'arch/x86/boot/bzImage' -> '/boot/vmlinuz-4.14.110.0-el7.4pd.x86_64'
```
### 安装内核配置文件
``` bash
[root@m7-uiauto-ssd001 linux-4.14.110]# cp -iv .config /boot/config-4.14.110.0-el7.4pd.x86_64
'.config' -> '/boot/config-4.14.110.0-el7.4pd.x86_64'
```
### 安装内核符号表文件
``` bash
[root@m7-uiauto-ssd001 linux-4.14.110]# cp -iv System.map /boot/System.map-4.14.110.0-el7.4pd.x86_64
'System.map' -> '/boot/System.map-4.14.110.0-el7.4pd.x86_64'
```
### 创建 initramfs 文件
创建 Booloader 加载的、启动时必须的 initramfs 文件:
``` bash
[root@m7-uiauto-ssd001 linux-4.14.110]# time mkinitrd /boot/initramfs-4.14.110.0-el7.4pd.x86_64.img 4.14.110.0-el7.4pd.x86_64
real 0m55.735s
user 0m59.108s
sys 0m55.645s
```
### 更新 Bootloader 配置文件
更新 GRUB Bootloader 配置文件:
``` bash
[root@m7-uiauto-ssd001 linux-4.14.110]# grub2-mkconfig -o /tmp/grub2.cfg
Generating grub configuration file ...
Found linux image: /boot/vmlinuz-4.14.110.0-el7.4pd.x86_64
Found initrd image: /boot/initramfs-4.14.110.0-el7.4pd.x86_64.img
Found linux image: /boot/vmlinuz-4.14.110.0-el7.4pd.x86_64
Found initrd image: /boot/initramfs-4.14.110.0-el7.4pd.x86_64.img
Found linux image: /boot/vmlinuz-4.4.166-1.el7.elrepo.x86_64
Found initrd image: /boot/initramfs-4.4.166-1.el7.elrepo.x86_64.img
Found linux image: /boot/vmlinuz-3.10.0-862.el7.x86_64
Found initrd image: /boot/initramfs-3.10.0-862.el7.x86_64.img
Found linux image: /boot/vmlinuz-0-rescue-d1db6964e41c45e19a373462a1b2e789
Found initrd image: /boot/initramfs-0-rescue-d1db6964e41c45e19a373462a1b2e789.img
done
[root@m7-uiauto-ssd001 linux-4.14.110]# cd /boot/grub2
[root@m7-uiauto-ssd001 grub2]# cp grub.cfg grub.cfg.bak.201904032254
[root@m7-uiauto-ssd001 grub2]# cp /tmp/grub2.cfg grub.cfg
```
### 设置从新内核启动
``` bash
[root@m7-uiauto-ssd001 grub2]# grub2-set-default 0
```
+ 第二个参数值 0 为内核 menuentry 在 grub.cfg 中的序号。
### 重启机器,确认使用了新版内核
``` bash
zhangjun:~ zhangjun$ ssh root@m7-uiauto-ssd001
Last login: Wed Apr 3 22:18:02 2019 from 172.27.153.58
[root@m7-uiauto-ssd001 ~]# uname -a
Linux m7-uiauto-ssd001 4.14.110.0-el7.4pd.x86_64 #1 SMP Wed Apr 3 22:41:54 CST 2019 x86_64 x86_64 x86_64 GNU/Linux
```
## TODO
1. 将编译后的内核打包成 RPM 包,便于后续安装;
2. grub 配置支持 UEFI;
3. 支持 NVIDIA GPU;
## 参考
1. [Kernel/Traditional compilation](https://wiki.archlinux.org/index.php/Kernel/Traditional_compilation)
2. [Redhat/CentOS 3.10 内核 Bugs 导致K8S和Docker异常的情况汇总](https://wiki.4paradigm.com/pages/viewpage.action?pageId=44415151)
3. [华夏升级4.4内核后启动hang](https://wiki.4paradigm.com/pages/viewpage.action?pageId=50566372)
4. [交行数据上传下载慢](https://wiki.4paradigm.com/pages/viewpage.action?pageId=47120044)
5. [Kubernetes And Docker Troubleshooting](https://wiki.4paradigm.com/display/PROD/Kubernetes+And+Docker+Troubleshooting)