Kubebuilder提示和技巧
#go #kubernetes #kubebuilder

最近,我花了很多时间使用go operator-sdk编写Kubernetes操作员,该操作员是在Kubebuilder框架之上构建的。这是我过去几个月使用这些框架的一些技巧和技巧的列表。

日志格式

Kubebuilder与K8S生态系统的许多大多数一样,都利用zap进行记录。开箱即用,KubeBuilder ZAP配置为每个日志输出时间戳,该日志使用科学符号进行了格式化。这使我很难通过瞥了一眼活动的时间来阅读活动的时间。就我个人而言,我更喜欢ISO 8601,所以让我们更改它!

在脚手架的main.go中,您可以通过修改zap.Options struct并调用ctrl.SetLogger来配置当前的记录器格式。

opts := zap.Options{
    Development: true,
    TimeEncoder: zapcore.ISO8601TimeEncoder,
}

ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts)))

在这种情况下,我添加了zapcore.ISO8601TimeEncoder,该zapcore.ISO8601TimeEncoder将时间戳编码为可读的ISO 8601构造字符串。挖掘了一些挖掘,并从库伯尼特(Kubernetes Slack Org)那里得到了一些帮助,才弄清楚了这一点。但是,在调试复杂的和解循环时,尤其是在多线程环境中,这是一个巨大的生活质量改善。

maxConcurrentReconciles

说到多线程环境,默认情况下,操作员只能运行单个和解循环。但是,在实践中,尤其是在运行全球范围的控制器时,运行多个并发和解循环以同时处理许多资源更改很有用。幸运的是,操作员SDK使用MaxConcurrentReconciles设置使它变得非常容易。我们可以在新的控制器的SetupWithManager func中进行设置:

func (r *CustomReconciler) SetupWithManager(mgr ctrl.Manager) error {

    return ctrl.NewControllerManagedBy(mgr).
        WithOptions(controller.Options{MaxConcurrentReconciles: 10}).
        ...
        Complete(r)
}

我已经在我的main.go文件中创建了一个命令行arg,允许用户将此值设置为任何整数值,因为这可能会随着时间的推移而进行调整,具体取决于控制器在生产群集中的执行方式。

亲子关系

控制器的基本功能之一是充当Kubernetes资源的父母。这允许控制器“拥有”
这些对象使其被删除后,所有子对象都是由kubernetes运行时自动收集的。

我喜欢这个小功能,可以要求任何client.Object添加父级引用
你在写。

func (r *CustomReconciler) ownObject(ctx context.Context, cr *myapiv1alpha1.CustomResource, obj client.Object) error {

    err := ctrl.SetControllerReference(cr, obj, r.Scheme)
    if err != nil {
        return err
    }
    return r.Update(ctx, obj)
}

然后,您可以在SetupWithManager func中添加这些资源的Owns手表。这些将指示您的控制器聆听指定类型的儿童资源的更改,从而在每个更改上触发和解循环。

func (r *CustomReconciler) SetupWithManager(mgr ctrl.Manager) error {

    return ctrl.NewControllerManagedBy(mgr).
        Owns(&v1apps.Deployment{}).
        Owns(&v1core.ConfigMap{}).
        Owns(&v1core.Service{}).
        Complete(r)
}

手表

您的控制器还可以观察其没有自己的资源。这对于您需要注意诸如PersistentVolumes或Nodes等全球资源的变化时很有用。这是您如何在SetupWithManager func中注册此手表的示例。

func (r *CustomReconciler) SetupWithManager(mgr ctrl.Manager) error {

    return ctrl.NewControllerManagedBy(mgr).
        Watches(
            &source.Kind{Type: &v1core.Node{}},
            handler.EnqueueRequestsFromMapFunc(myNodeFilterFunc),
            builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}),
        ).
        Complete(r)
}

在这种情况下,您需要实现myNodeFilterFunc接受
obj client.Object并返回[]reconcile.Request。使用
ResourceVersionChangedPredicate触发该资源类型上的每个更改的过滤功能,因此对
很重要 写出您的过滤功能以提高效率,因为有可能被称为很多,尤其是
如果您的控制器是全球范围的。

现场索引器

我遇到的一个陷阱发生在试图查询在特定节点上运行的POD列表时。此查询使用fieldSelector过滤器,如
在这里看到:

// Get a list of all pods on the node
err := c.List(ctx, &pods, &client.ListOptions{
    Namespace:     "",
    FieldSelector: fields.ParseSelectorOrDie(fmt.Sprintf("spec.nodeName=%s", node.Name)),
})

此编码器导致以下错误:Index with name field:spec.nodeName does not exist。经过一些谷歌搜索,我
找到引用的this GitHub issue
一个包含答案的Kubebuilder docs page

使用操作员SDK和KubeBuilder创建的控制器使用内置的缓存机制来存储API请求的结果。这是为了防止
垃圾邮件K8S API,并提高和解绩效。

使用FieldSelectors执行资源查找时,首先需要将所需的搜索字段添加到索引
缓存可以用于查找。这是一个示例,它将为POD的nodeName构建此索引:

if err := mgr.GetFieldIndexer().IndexField(context.TODO(), &v1core.Pod{}, "spec.nodeName", func(rawObj client.Object) []string {
    pod := rawObj.(*v1core.Pod)
    return []string{pod.Spec.NodeName}
}); err != nil {
    return err
}

现在,我们可以使用fieldSelector从上方运行List函数,没有任何问题。

在冲突中进行检验

如果您曾经编写过控制器,那么您可能非常熟悉错误Operation cannot be fulfilled on ...: the object has been modified; please apply your changes to the latest version and try again

这发生在您当前在控制器中的资源的版本与K8S群集状态的最新版本中的最新版本之外。如果您要在任何错误上重试和解循环,则您的控制器最终将调和资源,但这确实可以污染您的日志并难以发现更重要的错误。

阅读了K8S来源后,我找到了解决方案:RetryOnConflict。这是client-go软件包中的utility function,它运行一个函数并自动在冲突上重试,直到某个点。

现在,您可以将逻辑包装在此函数参数中,而不必再担心这个问题!额外的好处是,您只能到达return err而不是return ctrl.Result{}, err,这使您的代码更容易阅读。

有用的Kubebuilder标记

这是我在开发操作员时发现的一些有用的代码标记。

要在自定义资源的描述中添加自定义列(运行kubectl get),您可以将注释添加到API对象中:

//+kubebuilder:printcolumn:name="NodeReady",type="boolean",JSONPath=".status.nodeReady"
//+kubebuilder:printcolumn:name="NodeIp",type="string",JSONPath=".status.nodeIp"

要在您的自定义资源中添加短名称(例如pvc,例如PersistentVolumeClaim),您可以添加此注释:

//+kubebuilder:resource:shortName=mycr;mycrs

可以在此处找到有关KubeBuilder标记的更多文档:

https://book.kubebuilder.io/reference/markers/crd.html

最初发表在我的博客上:https://sklar.rocks/kubebuilder-tips/