背景
之前写过一篇 《Kubernetes 实战-平滑移除节点》 讲如何从 K8s 集群中移除节点的,今天来看看 kubectl drain
命令具体做了什么,怎么实现的。
kubectl
drain
相关命令都属于 kubectl
的自命令,因此需要先看下 kubectl
的入口,K8s 使用 cobra 作为命令行构建组建(我自己使用 cobra 觉得不怎么好用,而且文档也不清晰。。),统一入口在 cmd/kubectl/kubectl.go
,实际的处理逻辑在 pkg/kubectl/cmd/cmd.go
中
1 | ... |
可以看到在 kubectl
所有子命令的入口,我们今天要看的 drain
命令都属于集群管理命令,包含了:
- cordon
- uncordon
- drain
Cordon
先来看看 cordon
命令,这条命令的用途是标记节点为不可调度状态,防止在进行节点维护时 K8s 仍调度资源到该节点上。
1 | func NewCmdCordon(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command { |
直接看 Run
中的内容,先忽略 cmdutil.CheckErr
,这里主要执行了两个方法:o.Complete
和 o.RunCordonOrUncordon
。这里就必须提一下 kubectl
的实现方式,kubectl
的根本目的是发送对应的 HTTP 请求到 APIServer,kubectl
通过 Builder
和 Visitor
来实现了一层封装,使每个子命令的实现方式统一、简洁。
1 | // Builder provides convenience functions for taking arguments and parameters |
在 o.Complete
中构建了对应的 builder
:
1 | ... |
来看看 builder.Do()
接下来是怎么做的来返回了 Result 类型资源:
1 | func (b *Builder) Do() *Result { |
看到了如何获取 Result 类型对象,我们再来看 o.Complete
如何处理的,传入一个 VisitorFunc,Result 的 visitor 都实现了 Visit
接口,Visit
接口的作用是接收 VisitorFunc
并执行。 :
1 | return r.Visit(func(info *resource.Info, err error) error { |
接下来看看 o.RunCordonOrUncordon
做了什么:
1 | func (o *DrainCmdOptions) RunCordonOrUncordon(desired bool) error { |
Drain
看完了 Cordon,再来看 Drain:
1 | func NewCmdDrain(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command { |
直接看 o.RunDrain
,我们会看到第一件事情就就是执行 o.RunCordonOrUncordon
,就是标记节点为不可调度,所以我之前写的那篇博客其实说法不正确,如果是想将节点下线,那么直接执行 kubectl drain
就好:
1 | func (o *DrainCmdOptions) RunDrain() error { |
在 deleteOrEvictPodsSimple
中,先通过 Node 名称获取对应的 Pod 信息,然后进行驱逐动作:
1 | func (o *DrainCmdOptions) deleteOrEvictPodsSimple(nodeInfo *resource.Info) error { |
这里的 GetPodsForDeletion
会进行一个过滤,包含以下几种场景的过滤,需要注意的是,这里的过滤场景是有严格的顺序的:
1 | func (d *Helper) makeFilters() []podFilter { |
获取到过滤后的 Pod 列表后,就开始执行驱逐动作,每个 Pod 起一个 goroutine ,提交驱逐动作后会等待,直到 Pod 驱逐完成:
1 | func (d *Helper) evictPods(pods []corev1.Pod, policyGroupVersion string, getPodFn func(namespace, name string) (*corev1.Pod, error)) error { |
waitForDelete
如果没有立即完成,会将 ConditionFunc
传入 WaitFor
循环检测,其中 ConditionFunc
的检测依据是 Pod 存在且 ObjectMeta UID发生了改变:
1 | func WaitFor(wait WaitFunc, fn ConditionFunc, done <-chan struct{}) error { |
总结
kubectl drain
相关命令的实现还是很简单的,没有特别负责的逻辑,K8s 能够做到这种效果一个重要的原因是所有动作都是声明式的,声明之后等待执行完成就好,不需要主动的去做某些很脏的动作。在驱逐 Pod 的行为中,并不是所有的 Pod 都会被驱逐到其他节点,这里需要格外的注意,在节点下线前需要检查是否有单纯的 Pod 资源仍在节点上运行,是否有使用本地存储的 Pod等类似情况。
平时写代码很糙,见到这种多种设计模式组合的形式看了半天,找机会重新学习下设计模式。