1.Containerd容器管理机制
2.部署容器运行时未CRI-O,源码网络插件为kube-ovn的分析k8s集群
3.runc hang 导致 Kubernetes 节点 NotReady
Containerd容器管理机制
containerd是一个高级容器运行时,由Docker项目衍生,源码实现CRI规范,分析现为CNCF托管,源码提供新的分析stc指标公式源码容器解决方案的基础。k8s通过containerd创建容器时,源码containerd生成containerd-shim进程,分析此进程操作容器以避免containerd挂断导致所有容器退出的源码问题。containerd-shim用于执行命名空间、分析cgroups配置,源码挂载根文件系统等操作。分析标准化实现由OCI指定,源码runc为参考实现。分析
containerd-shim调用runc启动容器,源码而runc执行后立即退出,containerd-shim则成为容器的父进程,负责监控、状态收集和子进程清理,qt android源码确保无僵尸进程。
containerd初始化操作通过方法实现。具体创建过程包括容器对象内部处理的多项操作。初始化后,启动容器操作则由上述方法执行。
关于详细实现代码,请参考相关源码:github.com/containerd/c...
深入理解containerd创建、启动容器的代码实现,请访问进一步分析:qikqiak.com/post/contai...
更多讨论和细节分析见:colstuwjx.github.io/...
部署容器运行时未CRI-O,网络插件为kube-ovn的k8s集群
在部署不使用CRI-O作为容器运行时,而是采用kube-ovn网络插件的 Kubernetes 集群过程中,需要进行一系列的准备工作。首先,确保加载必要的内核模块并安装ipvsadm,接着安装依赖,包括配置yum源和go环境。
对于cri-o的安装,需要下载源码包并生成默认配置文件。vector ccp源码接着,安装conmon和CNI,可能需要从本地上传源码包。plugin的安装涉及获取源码并克隆github仓库。配置crio时,启动服务并确保crictl的安装,虽然crictl会随kubeadm自动下载,但建议单独安装以避免覆盖。安装完成后,可通过crictl验证crio的运行状态。
在部署kubernetes集群时,添加相应的yum源,安装必要的组件,如kubeadm,并配置kubeadm的配置文件,包括criSocket路径、imageRepository地址和podSubnet设置。需要检查和配置镜像,购买外国源码然后在所有节点上调整crio.conf,并利用kubeadm进行初始化集群操作。在node节点上,执行集群加入步骤。
安装kube-ovn时,需要修改install.sh脚本,执行安装,然后可能需要卸载和重新安装以解决特定问题。可能遇到的问题包括创建pod时的containercreating状态,这可能是runc版本需要升级;初始化集群时可能出现error getting node的错误,可以检查并修改crio.conf;以及可能遇到fs.may_detach_mounts相关的sysctl错误,通过调整系统设置来解决启动CRIO时的错误。
runc hang 导致 Kubernetes 节点 NotReady
Kubernetes 1..3 OS: CentOS 7.9. Kernel: 5.4.-1.el7.elrepo.x_ Docker: ..6
线上告警提示集群中存在 2-3 个 K8s 节点处于 NotReady 的状态,并且 NotReady 状态一直持续。问题的解决可以通过两种方法,我们先来看看 A 方案。
针对 docker hang 住这样的现象,通过搜索资料后发现了以下两篇文章里也遇到了相似的浪潮开源码问题。这两篇文章都提到了是由于 pipe 容量不够导致 runc init 往 pipe 写入卡住了,将 /proc/sys/fs/pipe-user-pages-soft 的限制放开,就能解决问题。查看问题主机上 /proc/sys/fs/pipe-user-pages-soft 设置的是 。所以将它放大 倍 echo > /proc/sys/fs/pipe-user-pages-soft,然而 kubelet 还是没有恢复正常,pleg 报错日志还在持续,runc init 程序也没有退出。考虑到 runc init 是 kubelet 调用 CRI 接口创建的,可能需要将 runc init 退出才能使 kubelet 退出。通过文章中的说明,只需要将对应的 pipe 中的内容读取掉,runc init 就能退出。尝试了几个后,runc init 果然退出了。再次检查,节点状态切换成 Ready,pleg 报错日志也消失了,观察一天也没有出现节点 NotReady 的情况,问题(临时)解决。
对解决方案 A 的疑问,虽然问题解决了,但是仔细读 /proc/sys/fs/pipe-user-pages-soft 参数的说明文档,发现这个参数跟本次问题的根本原因不太对得上。pipe-user-pages-soft 含义是对没有 CAP_SYS_RESOURCE CAP_SYS_ADMIN 权限的用户使用 pipe 容量大小做出限制,默认最多只能使用 个 pipe,一个 pipe 容量大小为 k。这里就有疑问:为什么容器 root 用户 pipe 容量会超过限制。
定位问题最直接的方法,就是阅读源码。先查看下 Linux 内核跟 pipe-user-pages-soft 相关的代码。线上内核版本为 5.4.-1,切换到对应的版本进行检索。在创建 pipe 时,内核会通过 too_many_pipe_buffers_soft 检查是否超过当前用户可使用 pipe 容量大小。如果发现已经超过,则将容量大小从 个 PAGE_SIZE 调整成 2 个 PAGE_SIZE。通过机器上执行 getconf PAGESIZE 可以获取到 PAGESIZE 是 字节,也就是说正常情况下 pipe 大小为 字节,但是由于超过限制,pipe 大小被调整成 字节,这就有可能出现数据无法一次性写入 pipe 的问题。
找到问题根本原因的第一步,往往是在线下环境复现问题。由于线上环境已经通过方案 A 做了紧急修复,因此,需要找到一种必现的手段。功夫不负有心人,在 issue 中找到了相同的问题,并且可以通过以下方法复现。执行命令之后,立刻就出现 runc init 卡住的情况。通过 lsof -p 查看 runc init 打开的文件句柄情况,可以看到 fd4、fd5、fd6 都是 pipe 类型,其中,fd4 和 fd6 编号都是 ,是同一个 pipe。如何来获取 pipe 大小来实际验证下「疑问 2」中的猜想呢?Linux 下没有现成的工具可以获取 pipe 大小,但是内核开放了系统调用 fcntl(fd, F_GETPIPE_SZ)可以获取到,代码如下。编译好之后,查看 pipe 大小情况如下。重点看下 fd4 和 fd6,两个句柄对应的是同一个 pipe,获取到的容量大小是 = 2 * PAGESIZE。所以的确是因为 pipe 超过软限制导致 pipe 容量被调整成了 2 * PAGESIZE。
对解决方案 A 疑问的探索,对解决方案 B 的考虑,线上应该如何做修复呢?是否需要把 docker 所有组件都升级呢?如果把 dockerd/containerd/runc 等组件都升级的话,就需要将业务切走然后才能升级,整个过程相对比较复杂,并且风险较高。因此考虑是否可以单独升级 runc?因为在 Kubernetes v1. 版本中还没有弃用 dockershim,因此运行容器整个调用链为:kubelet → dockerd → containerd → containerd-shim → runc → container。不同于 dockerd/containerd 是后台运行的服务端,containerd-shim 调用 runc,实际是调用了 runc 二进制来启动容器。因此,只需要升级 runc,对于新创建的容器,就会使用新版本的 runc 来运行容器。
通过测试环境验证,的确不会出现 runc init 卡住的情况了。最终,逐步将线上 runc 升级成 v1.1.1,并将 /proc/sys/fs/pipe-user-pages-soft 调整回原默认值。runc hang 住的问题圆满解决。
总结,本次故障的原因是,操作系统对 pipe-user-pages-soft 有软限制,但是由于容器 root 用户的 UID 与宿主机一致都是 0,内核统计 pipe 使用量时没有做区分,导致当 UID 为 0 的用户 pipe 使用量超过软限制后,新分配的 pipe 容量会变小。而 runc 1.0.0-rc 正好会因为 pipe 容量太小,导致数据无法完整写入,写入阻塞,进而 runc init 卡住,kubelet pleg 状态异常,节点 NotReady。修复方案是 runc 通过 goroutine 及时读取 pipe 内容,防止写入阻塞。