CentOS 7 从 3.10 升级 Linux 内核:Kubernetes 1.36 + containerd 2.3.3 最低内核兼容性实测
在做 Kubernetes 二进制部署时,很多问题看起来像是配置问题,最后却可能落在更底层的 Linux 内核能力上。
这次环境比较典型:
OS CentOS Linux 7.9.2009
Kernel 3.10.0-1160.71.1.el7.x86_64
Kubernetes v1.36.4
containerd v2.3.3
最初 kubelet 启动失败,先遇到了 cgroup v1 的限制,继续处理后又出现了 CRI v1 runtime 错误:
failed to run Kubelet:
validate service connection:
validate CRI v1 runtime API for endpoint
"unix:///run/containerd/containerd.sock":
rpc error: code = Unimplemented
desc = unknown service runtime.v1.RuntimeService
检查 containerd 插件状态:
ctr -a /run/containerd/containerd.sock plugins ls | grep -E 'cri|CRI'
得到:
io.containerd.cri.v1 images - ok
io.containerd.cri.v1 runtime linux/amd64 error
io.containerd.grpc.v1 cri - error
这意味着 containerd 本身已经正常运行,socket 也可以访问,但是 CRI runtime 插件没有初始化成功。
由于当前系统仍然使用 CentOS 7 自带的 3.10 内核,因此决定先不直接升级到最新系统,而是通过逐级升级 Linux 内核的方式,验证 containerd 2.3.3 在这套环境中的实际最低内核边界。
这篇文章记录的是兼容性实验和排障方法,不代表推荐在生产环境继续使用 CentOS 7。
一、为什么怀疑 Linux 3.10 内核
containerd 2.3 官方文档对 Linux Runtime Requirements 的描述比较谨慎:containerd 核心本身的要求并不高,但部分 core 和 snapshotter 特性依赖较新的 Linux 内核;对于 Linux,官方认为 4.x 内核是一个合理的起点。
containerd 默认使用 overlayfs snapshotter,而 overlayfs 所依赖的一些能力是在 Linux 4.x 系列逐步完善的。
因此:
Linux 3.10
↓
containerd 2.3.3 本体能启动
↓
CRI images 插件正常
↓
CRI runtime 插件 error
这种现象值得优先怀疑内核能力。
但这里一定要注意:
runtime error并不能仅凭现象断言就是内核问题。
它也可能来自:
- containerd
config.toml配置错误; - runc 版本或路径错误;
- overlayfs / snapshotter 初始化失败;
- CNI 或 runtime 插件依赖失败;
- 内核缺失某些 namespace、cgroup、seccomp 等能力。
因此升级内核在这里首先是一次 A/B 验证实验。
二、Kubernetes 1.36 对旧内核还有另一个限制:cgroup v1
在当前环境中执行:
stat -fc %T /sys/fs/cgroup/
CentOS 7 默认通常返回:
tmpfs
这说明当前使用的是:
cgroup v1
而 Kubernetes 1.36 的 kubelet 默认会拒绝在 cgroup v1 主机上运行,日志类似:
failed to validate kubelet configuration:
kubelet is configured to not run on a host using cgroup v1
如果只是为了临时验证,可以在 kubelet-config.yaml 中配置:
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
cgroupDriver: systemd
failCgroupV1: false
这可以让 kubelet 暂时继续运行在 cgroup v1 上。
但 Kubernetes 官方已经推荐使用 cgroup v2,并给出了更明确的推荐基线:
Linux Kernel >= 5.8
containerd >= 1.4
kubelet cgroupDriver = systemd
container runtime 使用 systemd cgroup driver
所以 4.x 内核只适合做兼容性实验,不是 Kubernetes 1.36 的长期推荐环境。
三、为什么不能直接给 CentOS 7 安装 CentOS 8 的 4.18 内核
CentOS 7 自带:
3.10.x
而 RHEL / CentOS 8 的基础内核是:
4.18.x
但不能直接把 CentOS 8 / RHEL 8 的 kernel RPM 安装到 CentOS 7,因为发行版 ABI、依赖关系、内核模块和用户态组件都不是同一套体系。
如果要在 CentOS 7 上测试较新的内核,应使用:
为 EL7 构建的 kernel-ml / kernel-lt RPM
历史上 ELRepo 为 EL7 提供过大量这样的内核包,而且包名特意设计成 kernel-ml / kernel-lt,可以和发行版原生 kernel 并存。
四、实验方案:逐级测试内核版本
如果目的是找最低兼容边界,不建议一下从 3.10 跳到 5.x。
推荐测试阶梯:
3.10
↓
4.4
↓
4.9
↓
4.14
↓
4.18
↓
5.2 / 5.4
实际排障时,可以先从:
3.10 → 4.14
开始。
如果 4.14 仍然失败,再测试 4.18。这样可以比较快地确定问题是否和 Linux 4.x 内核能力有关。
五、升级前先记录当前环境
任何内核升级前,都应该先留存当前状态。
cat /etc/redhat-release
uname -a
uname -r
当前环境:
CentOS Linux release 7.9.2009 (Core)
3.10.0-1160.71.1.el7.x86_64
检查已安装内核:
rpm -qa | grep '^kernel' | sort
查看 /boot:
ls -lh /boot/vmlinuz-*
查看 GRUB 当前默认内核:
grubby --default-kernel
备份 GRUB:
cp -a /etc/default/grub \
/etc/default/grub.bak.$(date +%Y%m%d-%H%M%S)
cp -a /boot/grub2/grub.cfg \
/boot/grub2/grub.cfg.bak.$(date +%Y%m%d-%H%M%S)
不要删除 CentOS 7 原来的 3.10 内核。旧内核是最重要的回滚入口。
六、在线安装 EL7 4.14 内核进行验证
由于 ELRepo 当前主要维护 EL8 / EL9 / EL10,EL7 已经属于历史版本。
实验环境可以使用仍然保留历史 EL7 kernel-ml RPM 的镜像。下面以:
kernel-ml-4.14.3-1.el7.elrepo.x86_64.rpm
为例。
进入临时目录:
cd /tmp
下载:
wget -O kernel-ml-4.14.3-1.el7.elrepo.x86_64.rpm \
"https://sourceforge.net/projects/ggo5343/files/kernel/el7/x86_64/kernel-ml-4.14.3-1.el7.elrepo.x86_64.rpm/download"
检查下载结果:
file kernel-ml-4.14.3-1.el7.elrepo.x86_64.rpm
确认它真的是 RPM,而不是一个 HTML 下载页。
继续检查:
rpm -K kernel-ml-4.14.3-1.el7.elrepo.x86_64.rpm
如果来源和签名无法确认,不应该把这种历史包用于生产环境。
安装:
yum localinstall -y \
kernel-ml-4.14.3-1.el7.elrepo.x86_64.rpm
仅仅为了运行新内核的话,kernel-ml-devel 和 kernel-ml-headers 都不是必须的。
七、确认新内核已经安装
执行:
rpm -qa | grep kernel-ml
以及:
ls -lh /boot/vmlinuz-*
理想情况下会同时看到:
/boot/vmlinuz-3.10.0-1160.71.1.el7.x86_64
/boot/vmlinuz-4.14.3-1.el7.elrepo.x86_64
这就是我们想要的状态:
旧内核保留
+
新内核并存
不要覆盖旧版本。
八、将 4.14 设置为默认启动内核
先查看所有内核:
grubby --info=ALL | grep -E 'index=|kernel=|title='
设置:
grubby --set-default \
/boot/vmlinuz-4.14.3-1.el7.elrepo.x86_64
确认:
grubby --default-kernel
预期:
/boot/vmlinuz-4.14.3-1.el7.elrepo.x86_64
只有确认默认启动项正确后,再重启:
reboot
九、启动后确认真正进入新内核
机器重新上线:
uname -r
预期:
4.14.3-1.el7.elrepo.x86_64
如果还是:
3.10.0-1160...
说明 GRUB 默认项没有切换成功,不要继续后面的 containerd 测试。
十、重新验证 containerd CRI
内核变化后,先重启 containerd:
systemctl restart containerd
检查:
systemctl status containerd
然后执行本文最关键的一条命令:
ctr -a /run/containerd/containerd.sock \
plugins ls | grep -E 'cri|CRI'
升级前状态:
io.containerd.cri.v1 images - ok
io.containerd.cri.v1 runtime linux/amd64 error
io.containerd.grpc.v1 cri - error
如果 4.14 后变为:
io.containerd.cri.v1 images - ok
io.containerd.cri.v1 runtime linux/amd64 ok
io.containerd.grpc.v1 cri - ok
说明 3.10 → 4.14 之间的内核能力变化确实解决了 containerd CRI runtime 初始化问题。
这时候再:
systemctl restart kubelet
观察:
systemctl status kubelet
journalctl -u kubelet -n 50 --no-pager
十一、如果 4.14 仍然失败,再测试 4.18
历史镜像中也保留了:
kernel-ml-4.18.3-1.el7.elrepo.x86_64.rpm
下载:
cd /tmp
wget -O kernel-ml-4.18.3-1.el7.elrepo.x86_64.rpm \
"https://sourceforge.net/projects/ggo5343/files/kernel/el7/x86_64/kernel-ml-4.18.3-1.el7.elrepo.x86_64.rpm/download"
检查:
file kernel-ml-4.18.3-1.el7.elrepo.x86_64.rpm
rpm -K kernel-ml-4.18.3-1.el7.elrepo.x86_64.rpm
安装:
yum localinstall -y \
kernel-ml-4.18.3-1.el7.elrepo.x86_64.rpm
设置默认:
grubby --set-default \
/boot/vmlinuz-4.18.3-1.el7.elrepo.x86_64
确认:
grubby --default-kernel
然后:
reboot
启动后:
uname -r
再次测试:
systemctl restart containerd
ctr -a /run/containerd/containerd.sock \
plugins ls | grep -E 'cri|CRI'
十二、如果 runtime 仍然是 error,必须看真正错误
不能继续只看:
runtime error
应该展开插件详情:
ctr -a /run/containerd/containerd.sock \
plugins ls -d id==runtime
再检查 containerd 日志:
journalctl -u containerd -n 100 --no-pager
重点寻找:
failed
error
snapshotter
overlay
runc
cgroup
seccomp
CRI
可以直接过滤:
journalctl -u containerd -b --no-pager | \
grep -iE 'error|failed|runtime|cri|overlay|snapshot|runc|cgroup'
如果 4.14、4.18 都失败,那么应该把注意力重新放回:
containerd config.toml
runc
overlayfs
snapshotter
CNI
CRI runtime plugin
而不是继续假设一定是内核版本。
十三、containerd 2.3.3 配置也要同步检查
当前 containerd:
containerd --version
例如:
containerd github.com/containerd/containerd/v2 v2.3.3
检查配置版本:
grep '^version' /etc/containerd/config.toml
containerd 2.x 推荐:
version = 3
检查 CRI:
grep -nE \
'disabled_plugins|SystemdCgroup|runtime_type|bin_dirs' \
/etc/containerd/config.toml
不能存在:
disabled_plugins = ["cri"]
运行时应该类似:
[plugins.'io.containerd.cri.v1.runtime'.containerd]
default_runtime_name = 'runc'
[plugins.'io.containerd.cri.v1.runtime'.containerd.runtimes.runc]
runtime_type = 'io.containerd.runc.v2'
[plugins.'io.containerd.cri.v1.runtime'.containerd.runtimes.runc.options]
SystemdCgroup = true
确认 runc:
which runc
runc --version
十四、4.x 内核并不代表获得 cgroup v2 推荐环境
这是整个实验中最容易混淆的一点。
即使:
3.10 → 4.14 → 4.18
containerd CRI 成功了,也不意味着这已经成为 Kubernetes 1.36 的推荐环境。
Kubernetes 官方对 cgroup v2 推荐:
Linux Kernel >= 5.8
containerd >= 1.4
kubelet: cgroupDriver=systemd
container runtime: systemd cgroup
而 Kubernetes 的 Linux 内核要求页面也指出,runc 不推荐运行在低于 Linux 5.2 的内核上。
因此 4.14 / 4.18 在本文中的角色主要是:
验证最低兼容边界。
长期运行环境仍然应该考虑:
Rocky Linux 9
+
cgroup v2
+
现代 5.x / 6.x 内核
+
containerd 2.x
+
Kubernetes 1.36
十五、如何回滚到 CentOS 7 原来的 3.10 内核
内核升级实验最重要的原则之一:
永远保留一个已知可以正常启动的旧内核。
查看:
grubby --info=ALL | grep -E 'index=|kernel='
将原来的 3.10 设置回默认:
grubby --set-default \
/boot/vmlinuz-3.10.0-1160.71.1.el7.x86_64
确认:
grubby --default-kernel
然后:
reboot
启动后:
uname -r
应该恢复:
3.10.0-1160.71.1.el7.x86_64
确认系统完全恢复后,再考虑删除实验内核。
不建议在测试过程中执行:
yum remove kernel
更不要直接删除:
/boot/vmlinuz-3.10...
十六、测试记录建议
如果真正想确定最低版本,不要凭印象。
建议做一张表:
| Kernel | containerd | CRI images | CRI runtime | CRI grpc | kubelet | 结论 |
|---|---|---|---|---|---|---|
| 3.10.0 | 2.3.3 | OK | ERROR | ERROR | FAIL | 当前基线 |
| 4.4.x | 2.3.3 | 待测 | 待测 | 待测 | 待测 | |
| 4.9.x | 2.3.3 | 待测 | 待测 | 待测 | 待测 | |
| 4.14.3 | 2.3.3 | 待测 | 待测 | 待测 | 待测 | |
| 4.18.3 | 2.3.3 | 待测 | 待测 | 待测 | 待测 | |
| 5.2.x+ | 2.3.3 | 待测 | 待测 | 待测 | 待测 |
每次只改变:
Linux Kernel
其他条件尽量保持:
同一台机器
同一 containerd
同一 runc
同一 config.toml
同一 kubelet
这才是一组有效的兼容性实验。
十七、常用检查命令汇总
查看系统:
cat /etc/redhat-release
uname -a
uname -r
查看 cgroup:
stat -fc %T /sys/fs/cgroup/
查看内核:
rpm -qa | grep '^kernel' | sort
ls -lh /boot/vmlinuz-*
查看默认启动内核:
grubby --default-kernel
查看所有 GRUB 内核:
grubby --info=ALL | grep -E 'index=|kernel=|title='
containerd:
containerd --version
systemctl status containerd
CRI:
ctr -a /run/containerd/containerd.sock \
plugins ls | grep -E 'cri|CRI'
CRI runtime 详细错误:
ctr -a /run/containerd/containerd.sock \
plugins ls -d id==runtime
containerd 日志:
journalctl -u containerd -n 100 --no-pager
kubelet:
systemctl restart kubelet
systemctl status kubelet
journalctl -u kubelet -n 100 --no-pager
十八、这次排障得到的一个经验
在 Kubernetes 二进制部署中,看到:
unknown service runtime.v1.RuntimeService
第一反应很容易变成:
containerd 配错了
但更完整的排查顺序应该是:
kubelet
↓
CRI endpoint 是否正确
↓
containerd 是否真的是预期版本
↓
CRI images/runtime/grpc 插件状态
↓
runtime 插件的详细 error
↓
containerd config.toml
↓
runc
↓
snapshotter / overlayfs
↓
Linux kernel
尤其是在:
CentOS 7
Linux 3.10
这样的旧系统上安装:
Kubernetes 1.36
containerd 2.3
时,不能只把注意力放在 Kubernetes 配置文件上。
现代 Kubernetes、containerd、runc 都在逐渐把系统基线向:
cgroup v2
较新的 Linux Kernel
systemd cgroup driver
推进。
所以最终的工程建议仍然是:
可以通过升级历史内核验证兼容边界,但新建 Kubernetes 1.36 集群不应继续把 CentOS 7 + Linux 3.10/4.x 作为长期生产基线。
参考资料
-
containerd 2.3 Runtime Requirements
https://containerd.io/docs/2.3/ -
Kubernetes:Linux Kernel Version Requirements
https://kubernetes.io/docs/reference/node/kernel-version-requirements/ -
Kubernetes:About cgroup v2
https://kubernetes.io/docs/concepts/architecture/cgroups/ -
CentOS Linux 生命周期说明
https://www.centos.org/centos-linux/ -
ELRepo kernel-ml 说明
https://elrepo.org/wiki/doku.php?id=kernel-ml -
ELRepo kernel-lt 说明
https://elrepo.org/wiki/doku.php?id=kernel-lt -
历史 EL7 kernel-ml RPM 镜像(用于兼容性实验,不建议作为生产软件源)
https://sourceforge.net/projects/ggo5343/files/kernel/el7/x86_64/