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 作为长期生产基线。


参考资料

  1. containerd 2.3 Runtime Requirements
    https://containerd.io/docs/2.3/

  2. Kubernetes:Linux Kernel Version Requirements
    https://kubernetes.io/docs/reference/node/kernel-version-requirements/

  3. Kubernetes:About cgroup v2
    https://kubernetes.io/docs/concepts/architecture/cgroups/

  4. CentOS Linux 生命周期说明
    https://www.centos.org/centos-linux/

  5. ELRepo kernel-ml 说明
    https://elrepo.org/wiki/doku.php?id=kernel-ml

  6. ELRepo kernel-lt 说明
    https://elrepo.org/wiki/doku.php?id=kernel-lt

  7. 历史 EL7 kernel-ml RPM 镜像(用于兼容性实验,不建议作为生产软件源)
    https://sourceforge.net/projects/ggo5343/files/kernel/el7/x86_64/