注:本文已发布超过一年,请注意您所使用工具的相关版本是否适用
在前两天的文章当中我们搭建好了本地的 K8s 开发环境,并且了解了 kubebuilder 的基本使用方法,今天就从我之前遇到的一个真实需求出发完整的写一个 Operator
在 K8s 运行的过程当中我们发现总是存在一些业务由于安全,可用性等各种各样的原因需要跑在一些独立的节点池上,这些节点池里面可能再划分一些小的节点池。
虽然我们可以使用 Taint,Label对节点进行划分,使用 nodeSelector 和 tolerations让 Pod 跑在指定的节点上,但是这样主要会有两个问题:
v1.16 之后也可以使用 RuntimeClass来简化 pod 的配置,但是 RuntimClass 并不和节点进行关联[1]MVP 版本支持标签、污点即可节点池资源如下
1 | |
节点和节点池之间的映射如何建立?
我们可以利用 node-role.kubernetes.io/xxx=""标签和节点池建立映射
xxx 和节点池的name相对应
使用这个标签的好处是,使用 kubectl get no可以很方便的看到节点属于哪个节点池
1 | |
Pod 和节点池之间的映射如何建立?
RuntimeClass对象,当创建一个 NodePool 对象的时候我们就创建一个对应的 RuntimeClass 对象,然后在 Pod 中只需要加上 runtimeClassName: myclass 就可以了注: 对于 MVP 版本来说其实我们不需要使用自定义资源,只需要通过标签和 RuntimeClass 结合就能满足需求,但是这里为了展示一个完整的流程,我们使用了自定义资源
1 | |
1 | |
我们实现 Reconcile 函数,req会返回当前变更的对象的 Namespace和Name信息,有这两个信息,我们就可以获取到这个对象了,所以我们的操作就是
NodePool 对象NodePool 对象生成对应的 Label 查找是否已经存在对应的 Label 的 NodeNode 加上对应的 Taint 和 LabelNodePool 生成对应的 RuntimeClass ,查找是否已经存在对应的 RuntimeClass1 | |
相信聪明的你已经发现上面的创建逻辑存在很多的问题
NodePool 对象更新,Node 是否更新对应的 Taint 和LabelNodePool 删除了一个 Label 或Taint对应 Node 的Label或Taint 是否需要删除,怎么删除?NodePool 对象更新,RuntimeClass是否更新,如何更新我们 MVP 版本实现可以简单一些,我们约定,所有属于 NodePool 的节点 Tanit 和Label信息都应该由 NodePool管理,key 包含 kubernetes 标签污点除外
1 | |
ApplyNode 方法如下所示,主要是修改节点的标签和污点信息
1 | |
我们使用 make run将服务跑起来测试一下
首先我们准备一份 NodePool 的 CRD,使用 kubectl apply -f config/samples/ 部署一下
1 | |
部署之后可以获取到节点的标签
1 | |
以及 RuntimeClass
1 | |
我们更新一下 NodePool
1 | |
可以看到 RuntimeClass
1 | |
和节点对应的标签信息都有了相应的变化
1 | |
我们可以直接使用 kubectl delete NodePool name删除对应的对象,但是这样可以发现一个问题,就是 NodePool 创建的 RuntimeClass 以及其维护的 Node Taint Labels 等信息都没有被清理。
当我们想要再删除一个对象的时候,清理一写想要清理的信息时,我们就可以使用 Finalizers 特性,执行预删除的操作。
k8s 的资源对象当中存在一个 Finalizers字段,这个字段是一个字符串列表,当执行删除资源对象操作的时候,k8s 会先更新 DeletionTimestamp 时间戳,然后会去检查 Finalizers 是否为空,如果为空才会执行删除逻辑。所以我们就可以利用这个特性执行一些预删除的操作。注意:预删除必须是幂等的
1 | |
预删除的逻辑如下
1 | |
我们执行 kubectl delete NodePool master 然后再获取节点信息可以发现,除了 kubernetes 的标签其他 NodePool 附加的标签都已经被删除掉了
1 | |
我们上面使用 Finalizer 的时候只处理了 Node 的相关数据,没有处理 RuntimeClass,能不能用相同的方式进行处理呢?当然是可以的,但是不够优雅。
对于这种一一映射或者是附带创建出来的资源,更好的方式是在子资源的 OwnerReference 上加上对应的 id,这样我们删除对应的 NodePool 的时候所有 OwnerReference 是这个对象的对象都会被删除掉,就不用我们自己对这些逻辑进行处理了。
1 | |
在创建的时候使用 controllerutil.SetOwnerReference 设置一下 OwnerReference 即可,然后我们再试试删除就可以发现 RuntimeClass 也一并被删除了。
注意,RuntimeClass 是一个集群级别的资源,我们最开始创建的 NodePool 是 Namespace 级别的,直接运行会报错,因为 Cluster 级别的 OwnerReference 不允许是 Namespace 的资源。
这个需要在 api/v1/nodepool_types.go 添加一行注释,指定为 Cluster 级别
1 | |
修改之后我们需要先执行 make uninstall 然后再执行 make install
回顾一下,这篇文章我们实现了一个 NodePool 的 Operator 用来控制节点以及对应的 RuntimeClass,除了基本的 CURD 之外我们还学习了预删除和 OwnerReference 的使用方式。之前在 kubectl delete 某个资源的时候有时候会卡住,这个其实是因为在执行预删除的操作,可能本来也比较慢,也有可能是预删除的时候返回了错误导致的。
下一篇我们一起来为我们的 Operator 加上 Event 和 Status。
注:本文已发布超过一年,请注意您所使用工具的相关版本是否适用
在前两天的文章当中我们搭建好了本地的 K8s 开发环境,并且了解了 kubebuilder 的基本使用方法,今天就从我之前遇到的一个真实需求出发完整的写一个 Operator
在 K8s 运行的过程当中我们发现总是存在一些业务由于安全,可用性等各种各样的原因需要跑在一些独立的节点池上,这些节点池里面可能再划分一些小的节点池。
虽然我们可以使用 Taint,Label对节点进行划分,使用 nodeSelector 和 tolerations让 Pod 跑在指定的节点上,但是这样主要会有两个问题:
v1.16 之后也可以使用 RuntimeClass来简化 pod 的配置,但是 RuntimClass 并不和节点进行关联[1]MVP 版本支持标签、污点即可节点池资源如下
1 | |
节点和节点池之间的映射如何建立?
我们可以利用 node-role.kubernetes.io/xxx=""标签和节点池建立映射
xxx 和节点池的name相对应
使用这个标签的好处是,使用 kubectl get no可以很方便的看到节点属于哪个节点池
1 | |
Pod 和节点池之间的映射如何建立?
RuntimeClass对象,当创建一个 NodePool 对象的时候我们就创建一个对应的 RuntimeClass 对象,然后在 Pod 中只需要加上 runtimeClassName: myclass 就可以了注: 对于 MVP 版本来说其实我们不需要使用自定义资源,只需要通过标签和 RuntimeClass 结合就能满足需求,但是这里为了展示一个完整的流程,我们使用了自定义资源
1 | |
1 | |
我们实现 Reconcile 函数,req会返回当前变更的对象的 Namespace和Name信息,有这两个信息,我们就可以获取到这个对象了,所以我们的操作就是
NodePool 对象NodePool 对象生成对应的 Label 查找是否已经存在对应的 Label 的 NodeNode 加上对应的 Taint 和 LabelNodePool 生成对应的 RuntimeClass ,查找是否已经存在对应的 RuntimeClass1 | |
相信聪明的你已经发现上面的创建逻辑存在很多的问题
NodePool 对象更新,Node 是否更新对应的 Taint 和LabelNodePool 删除了一个 Label 或Taint对应 Node 的Label或Taint 是否需要删除,怎么删除?NodePool 对象更新,RuntimeClass是否更新,如何更新我们 MVP 版本实现可以简单一些,我们约定,所有属于 NodePool 的节点 Tanit 和Label信息都应该由 NodePool管理,key 包含 kubernetes 标签污点除外
1 | |
ApplyNode 方法如下所示,主要是修改节点的标签和污点信息
1 | |
我们使用 make run将服务跑起来测试一下
首先我们准备一份 NodePool 的 CRD,使用 kubectl apply -f config/samples/ 部署一下
1 | |
部署之后可以获取到节点的标签
1 | |
以及 RuntimeClass
1 | |
我们更新一下 NodePool
1 | |
可以看到 RuntimeClass
1 | |
和节点对应的标签信息都有了相应的变化
1 | |
我们可以直接使用 kubectl delete NodePool name删除对应的对象,但是这样可以发现一个问题,就是 NodePool 创建的 RuntimeClass 以及其维护的 Node Taint Labels 等信息都没有被清理。
当我们想要再删除一个对象的时候,清理一写想要清理的信息时,我们就可以使用 Finalizers 特性,执行预删除的操作。
k8s 的资源对象当中存在一个 Finalizers字段,这个字段是一个字符串列表,当执行删除资源对象操作的时候,k8s 会先更新 DeletionTimestamp 时间戳,然后会去检查 Finalizers 是否为空,如果为空才会执行删除逻辑。所以我们就可以利用这个特性执行一些预删除的操作。注意:预删除必须是幂等的
1 | |
预删除的逻辑如下
1 | |
我们执行 kubectl delete NodePool master 然后再获取节点信息可以发现,除了 kubernetes 的标签其他 NodePool 附加的标签都已经被删除掉了
1 | |
我们上面使用 Finalizer 的时候只处理了 Node 的相关数据,没有处理 RuntimeClass,能不能用相同的方式进行处理呢?当然是可以的,但是不够优雅。
对于这种一一映射或者是附带创建出来的资源,更好的方式是在子资源的 OwnerReference 上加上对应的 id,这样我们删除对应的 NodePool 的时候所有 OwnerReference 是这个对象的对象都会被删除掉,就不用我们自己对这些逻辑进行处理了。
1 | |
在创建的时候使用 controllerutil.SetOwnerReference 设置一下 OwnerReference 即可,然后我们再试试删除就可以发现 RuntimeClass 也一并被删除了。
注意,RuntimeClass 是一个集群级别的资源,我们最开始创建的 NodePool 是 Namespace 级别的,直接运行会报错,因为 Cluster 级别的 OwnerReference 不允许是 Namespace 的资源。
这个需要在 api/v1/nodepool_types.go 添加一行注释,指定为 Cluster 级别
1 | |
修改之后我们需要先执行 make uninstall 然后再执行 make install
回顾一下,这篇文章我们实现了一个 NodePool 的 Operator 用来控制节点以及对应的 RuntimeClass,除了基本的 CURD 之外我们还学习了预删除和 OwnerReference 的使用方式。之前在 kubectl delete 某个资源的时候有时候会卡住,这个其实是因为在执行预删除的操作,可能本来也比较慢,也有可能是预删除的时候返回了错误导致的。
下一篇我们一起来为我们的 Operator 加上 Event 和 Status。