如何为任意对象触发 Kubernetes 控制器协调器?

Ada*_*der 9 kubernetes kubebuilder kubernetes-operator

概述

我正在为 VerticalScaler CRD 编写一个 Kubernetes 控制器,它可以垂直扩展集群中的 Deployment。我的规范引用了集群中现有的 Deployment 对象。如果引用的部署被修改或删除,我想将 VerticalScaler 的协调请求排入队列。

// VerticalScalerSpec defines the desired state of VerticalScaler.
type VerticalScalerSpec struct {
    // Name of the Deployment object which will be auto-scaled.
    DeploymentName string `json:"deploymentName"`
}
Run Code Online (Sandbox Code Playgroud)

问题

当资源不属于控制器并且该资源不持有对其资源由控制器管理的对象的引用时,是否有一种好方法来监视该资源?

我发现了什么

我认为这应该在控制器的 Kubebuilder 标准SetupWithManager函数中进行配置,尽管手表可能可以在其他地方设置。

// SetupWithManager sets up the controller with the Manager.
func (r *VerticalScalerReconciler) SetupWithManager(mgr ctrl.Manager) error {
    return ctrl.NewControllerManagedBy(mgr).
        For(&v1beta1.VerticalScaler{}).
        Complete(r)
}
Run Code Online (Sandbox Code Playgroud)

我一直在controller-runtime/pkg/builder和 Kubebuilder 文档中寻找一种好的方法。我发现的最接近的例子是关于手表的 kubebuilder-v1 文档中的“观看任意资源”部分:

控制器可以监视任意资源并将它们映射到控制器管理的资源的键。控制器甚至可以将一个事件映射到多个键,从而触发每个键的协调。

示例:为了响应集群扩展事件(例如节点的删除或添加),控制器将监视节点并将监视事件映射到控制器管理的对象的键。

我的挑战是如何将部署映射到依赖的 VerticalScaler,因为部署上不存在此信息。我可以在 VerticalScaler 上创建一个索引,并使用字段选择器从MapFunc查找依赖的 VerticalScalers ,但似乎我不应该在 MapFunc 内执行 I/O。如果列表部署操作失败,我将无法重试或重新排队更改。

我有这个代码使用这种不完美的方法工作:

const deploymentNameIndexField = ".metadata.deploymentName"

// SetupWithManager sets up the controller with the Manager.
func (r *VerticalScalerReconciler) SetupWithManager(mgr ctrl.Manager) error {
    if err := r.createIndices(mgr); err != nil {
        return err
    }

    return ctrl.NewControllerManagedBy(mgr).
        For(&v1beta1.VerticalScaler{}).
        Watches(
            &source.Kind{Type: &appsv1.Deployment{}},
            handler.EnqueueRequestsFromMapFunc(r.mapDeploymentToRequests)).
        Complete(r)
}

func (r *VerticalScalerReconciler) createIndices(mgr ctrl.Manager) error {
    return mgr.GetFieldIndexer().IndexField(
        context.Background(),
        &v1beta1.VerticalScaler{},
        deploymentNameIndexField,
        func(object client.Object) []string {
            vs := object.(*v1beta1.VerticalScaler)

            if vs.Spec.DeploymentName == "" {
                return nil
            }

            return []string{vs.Spec.DeploymentName}
        })
}

func (r *VerticalScalerReconciler) mapDeploymentToRequests(object client.Object) []reconcile.Request {
    deployment := object.(*appsv1.Deployment)

    ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
    defer cancel()

    var vsList v1beta1.VerticalScalerList

    if err := r.List(ctx, &vsList,
        client.InNamespace(deployment.Namespace),
        client.MatchingFields{deploymentNameIndexField: deployment.Name},
    ); err != nil {
        r.Log.Error(err, "could not list VerticalScalers. " +
            "change to Deployment %s.%s will not be reconciled.",
            deployment.Name, deployment.Namespace)
        return nil
    }

    requests := make([]reconcile.Request, len(vsList.Items))

    for i, vs := range vsList.Items {
        requests[i] = reconcile.Request{
            NamespacedName: client.ObjectKeyFromObject(&vs),
        }
    }

    return requests
}
Run Code Online (Sandbox Code Playgroud)

其他考虑的方法

为了涵盖我的基础,我应该提到我不想将 VerticalScaler 设置为部署的所有者,因为我不想在删除 VerticalScaler 时对部署进行垃圾收集。即使是非控制器的ownerReference也会导致垃圾收集。

我还考虑过使用通道观察器,但文档说这是针对源自集群外部的事件,但事实并非如此。

我还可以为部署创建一个单独的控制器,并从该控制器的协调函数更新依赖的 VerticalScaler 上的某些字段,但随后我还需要一个终结器来处理删除部署时触发 VerticalScaler 协调,并且似乎有点矫枉过正。

我可以让我的 VerticalScaler 协调器向部署添加注释,但如果由 Helm 等管理,则部署注释有可能被覆盖。如果在部署之前创建 VerticalScaler,这也不会导致协调请求。

use*_*308 6

作为 的替代方案EnqueueRequestsFromMapFunc,您可以使用:

ctrl.NewControllerManagedBy(mgr).
    For(&v1beta1.VerticalScaler{}).
    Watches(
        &source.Kind{Type: &appsv1.Deployment{}},
        handler.Funcs{CreateFunc: r.CreateFunc})...
Run Code Online (Sandbox Code Playgroud)

处理程序的回调函数(例如CreateFunc您定义的上述函数)具有签名func(event.CreateEvent, workqueue.RateLimitingInterface),使您可以直接访问工作队列。默认情况下,如果您不调用Done()工作队列,它将以指数退避重新排队。这应该允许您处理 io 操作的错误。


cod*_*ger 4

您确实使用了地图功能和普通手表。https://github.com/coderanger/migrations-operator/blob/088a3b832f0acab4bfe02c03a4404628c5ddfd97/components/migrations.go#L64-L91显示了一个示例。你确实经常不得不在map函数中进行I/O来找出这个东西对应的根对象,但我同意这有点糟糕,如果这些调用除了记录或恐慌之外没有其他办法做。失败。

您还可以使用非控制器所有者引用或注释作为存储给定部署的映射目标的方式,这使得映射功能更加简单,但通常响应速度也较差。总体而言,这取决于需要的动态程度。请随时访问 #kubebuilder Slack 频道寻求帮助。