cri-o 技术探秘 2
1. resourceStore 在 crio 中的使用
我们知道,k8s scheduler 如果发现当前的系统状态和定义不一致的话,就会启用对应的策略让当前系统状态变得和定义一致。拿创建一个新 pod 为例,如果发现 pod name 没有,就会调用 CNI 接口,来创建一个新的 pod。但是我们知道, pod 也不是一下子就能够起来的,在某些情况下, 需要花费一些时间来完成。这个使用 CNI 的 RunPodSandbox 接口返回 pod_id(sandbox.id)的时候, pod 未必已经成功创建出来。那么接下来 k8s scheduler 可能仍然会再一次触发 RunPodSandbox 接口,代码再次走到 ReservePodName 的时候,由于 pod name 已经存在了, 代码再次走到 ReservePodName 的时候就会失败(虽然我们知道,失败是预期的),没有引入 resourceStore 前,cri-o 会打印出大量的 log "Kubelet may be retrying requests that are timing out in CRI-O due to system load".直至第一次 RunPodSandbox 成功。引入 resourceStore 后,我们就可以等,直到 pod 创建成功。这是我们通过调用 getResourceOrWait 函数来实现的。
为什么只有在 context.Canceled 或者 context.DeadlineExceeded 的时候才把把 pod 信息放入到 resourceStore 呢?k8s 调用 RunPodSandbox 接口的时候,在某些情况下(如 system load 特别高),第一次 RunPodSandbox 可能会超时,继而触发 DeadlineExceeded,这个时候,cri-o 并不会停止处理 RunPodSandbox,而是继续处理,并且把创建的 pod 放入到 resourceStore 中,这样下一次 k8s scheduler 再次调用 RunPodSandbox 的时候,就会直接从 resourceStore 中找到新创建的 pod。这样就能有效 k8s scheduler 重复触发 RunPodSandbox 接口。
container create 的流程中也是使用的 resourceStore 来解决类似问题,流程类似,不在赘述。
在 runPodSandbox 尾部代码,我们可以看到调用了 sb.SetCreated()讲 pod 设置为 create 完毕状态,这个时候 getResourceOrWait 函数中设置的 watcher 就会发现 pod 已经创建完成了, 继而处于等待状态 rpc 请求返回 pod.id(cachedID)给 k8s scheduler。
我们再来看看 resourceStore 的实现。
resources 用来存储所有的 resource,在 cro-o 中包括 container 和 pod 两种类型。timeout 用来设置 resource 过期时间,用一个 goroutine 上跑 cleanupStaleResources 来实现的。然后就是 Put -> WatcherForResource -> Get 这三个函数。WatcherForResource 可能发生在 Put 前面。 Resource 要满足 IdentifiableCreatable 接口。
2. wipe
wipe 是指在 node 意外 reboot 的时候,crio 的有一些清尾的工作没有来的急完成,一般来说就是删除 container 和 image。如果不 wipe 掉这些文件夹文件, 再次启动的时候就会出问题。所以我们需要通过 wipe 操作来避免这些问题。
我们先来看看 wipe 相关配置。
首先我们有一个完整的机制来判断 node 是否重启过,crio 是不是升级了新的版本这两个事情。
如何确定一个 node 是不是 rebooted?在 cri-o 中,这是通过 version_file 文件来完成的。首先启动的时候我们会在特定目录下(默认是/var/run/crio/version)生成 version_file,内容包含了版本信息。因为/var/run 使用的是 tmpfs,在 reboot 后,这个文件夹下由 crio 写入的内容就会被清空如果启动后没有这个文件,则肯定是 rebooted 了。
如何判断系统是不是升级了新版本?这是通过 version_file_persist 文件来完成的。首先我们启动的时候会在特定目录下(默认是/var/lib/crio/version)下生成 version_file_persist, 文件内容包含了版本信息。这个文件夹下 persist 到 disk 里面的,因此如果 crio 在启动后发现内部的内容不一致,那么肯定是这次启动的 crio 的版本和上次启动的版本不一致了。
如何解决由于意外停机导致有一些清尾工作没有做?这是通过 clean_shutdown_file 配置来完成的,首先启动的时候如果设置有 clean_shutdown_file 路径, 那么会 remove /var/lib/crio/clean.shutdown 文件,并且会在这个目录下(默认是/var/lib/crio/)生成 clean.shutdown.supported 文件。 然后在 shutdown 过程中,如果启动的时候设置有 clean_shutdown_file 路径,则会在/var/lib/crio/下创建/var/lib/crio/clean.shutdown 文件,代表 shutdown 操作完成。
这样意外停机重启 node 后,当我们调用 crio wipe 命令的时候, 如果 clean.shutdown.supported 存在并且 /var/lib/crio/clean.shutdown 文件不存在,我们就知道,上次 shutdown 操作可能没有及时完成(也就是 shutdownWasUnclean),这个时候我们就需要继续执行 shutdown 没有完成的工作。如果 version_file 版本有改动,那么我们知道 crio 版本变了,以前的 container 和 image 可能会和新版本不兼容了,这个时候我们就会彻底的删除历史遗留的文件等信息(handleCleanShutdown)。
如果 internal-wipe config 设为 true 并且不是强制(force)wipe 的时候,这个时候会在重启 node 后,再次启动 crio 的时候,判断是不是需要清理。具体逻辑在 wipeIfAppropriate 函数中。
如果是 force wipe 或者不是 internal-wipe,那么这个时候我们会删掉 container 和 images。
在 wipeIfAppropriate 内,我们只有在 version_file_persist 内的 version 有改变的时候,才会删除 images。如果要删删除 images,则必然会先删除掉 container。如果是 node reboot 了, 我们只会删除所有的 container。
版权声明: 本文为 InfoQ 作者【xumc】的原创文章。
原文链接:【http://xie.infoq.cn/article/9d3b28916f3768c02cebca8c9】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。
评论