查看控制器“拥有“的二级资源

在 Kubernetes 控制器中,通常会同时管理 主资源次资源主资源 是控制器负责的主要资源,而 次资源 是由控制器创建和管理,以支持 主资源

在本节中,我们将解释如何管理由控制器“拥有“的 二级资源。这个例子将展示如何:

  • 在主要资源(Busybox)和次要资源(Deployment)之间设置所有者引用,以确保正确的生命周期管理。
  • 将控制器配置为在 SetupWithManager() 中使用 Owns() 监视次要资源。可以看到 DeploymentBusybox 控制器拥有,因为它将由该控制器创建和管理。

设置拥有者引用

要将次级资源(Deployment)的生命周期与主资源(Busybox)连接起来,我们需要在次级资源上设置一个 所有者引用。这确保了 Kubernetes 会自动处理层叠删除:如果主资源被删除,次级资源也会被删除。

Controller-runtime 提供了 controllerutil.SetControllerReference 函数,您可以使用它来设置资源之间的关系。

设置拥有者引用

下面,我们创建 Deployment 并使用 controllerutil.SetControllerReference()Busybox 自定义资源和 Deployment 之间设置 Owner 引用。

// deploymentForBusybox 返回一个 Busybox 的 Deployment 对象
func (r *BusyboxReconciler) deploymentForBusybox(busybox *examplecomv1alpha1.Busybox) *appsv1.Deployment {
    replicas := busybox.Spec.Size

    dep := &appsv1.Deployment{
        ObjectMeta: metav1.ObjectMeta{
            Name:      busybox.Name,
            Namespace: busybox.Namespace,
        },
        Spec: appsv1.DeploymentSpec{
            Replicas: &replicas,
            Selector: &metav1.LabelSelector{
                MatchLabels: map[string]string{"app": busybox.Name},
            },
            Template: metav1.PodTemplateSpec{
                ObjectMeta: metav1.ObjectMeta{
                    Labels: map[string]string{"app": busybox.Name},
                },
                Spec: corev1.PodSpec{
                    Containers: []corev1.Container{
                        {
                            Name:  "busybox",
                            Image: "busybox:latest",
                        },
                    },
                },
            },
        },
    }

    // 设置 Deployment 的 ownerRef,确保在删除 Busybox CR 时,Deployment 也会被删除。    controllerutil.SetControllerReference(busybox, dep, r.Scheme)
    return dep
}

Explanation

通过设置 OwnerReference,如果 Busybox 资源被删除,Kubernetes 会自动删除 Deployment。这还允许控制器监视 Deployment 的变化,并确保维护所需的状态(例如副本数)。

例如,如果有人修改了 Deployment,将副本数更改为 3,而 Busybox CR 定义的期望状态为 1 个副本,控制器将进行调整,确保 Deployment 缩减回 1 个副本。

对账函数示例

// Reconcile 处理 Busybox 和 Deployment 的主要调整循环。func (r *BusyboxReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    log := logf.FromContext(ctx)

    // 获取 Busybox 实例    busybox := &examplecomv1alpha1.Busybox{}
    if err := r.Get(ctx, req.NamespacedName, busybox); err != nil {
        if apierrors.IsNotFound(err) {
            log.Info("Busybox resource not found. Ignoring since it must be deleted")
            return ctrl.Result{}, nil
        }
        log.Error(err, "Failed to get Busybox")
        return ctrl.Result{}, err
    }

    // 检查部署是否已经存在,如果不存在则创建一个新的。    found := &appsv1.Deployment{}
    err := r.Get(ctx, types.NamespacedName{Name: busybox.Name, Namespace: busybox.Namespace}, found)
    if err != nil && apierrors.IsNotFound(err) {
        // 定义一个新的部署        dep := r.deploymentForBusybox(busybox)
        log.Info("Creating a new Deployment", "Deployment.Namespace", dep.Namespace, "Deployment.Name", dep.Name)
        if err := r.Create(ctx, dep); err != nil {
            log.Error(err, "无法创建新的部署", "Deployment.Namespace", dep.Namespace, "Deployment.Name", dep.Name)
            return ctrl.Result{}, err
        }
        // 重新排队请求以确保创建部署        return ctrl.Result{RequeueAfter: time.Minute}, nil
    } else if err != nil {
        log.Error(err, "Failed to get Deployment")
        return ctrl.Result{}, err
    }

    // 确保部署大小与期望状态匹配
    size := busybox.Spec.Size
    if *found.Spec.Replicas != size {
        found.Spec.Replicas = &size
        if err := r.Update(ctx, found); err != nil {
            log.Error(err, "Failed to update Deployment size", "Deployment.Namespace", found.Namespace, "Deployment.Name", found.Name)
            return ctrl.Result{}, err
        }
        // 重新排队请求以确保达到正确的状态
        return ctrl.Result{Requeue: true}, nil
    }

    // 更新 Busybox 状态以反映该部署可用    busybox.Status.AvailableReplicas = found.Status.AvailableReplicas
    if err := r.Status().Update(ctx, busybox); err != nil {
        log.Error(err, "Failed to update Busybox status")
        return ctrl.Result{}, err
    }

    return ctrl.Result{}, nil
}

观看辅助资源

为了确保对次要资源(例如 Deployment)的更改能够触发对主要资源(Busybox)的对账,我们配置控制器以监视这两个资源。

Owns() 方法允许您指定控制器应该监控的辅助资源。这样,控制器将在辅助资源发生变化(例如,被更新或删除)时自动调整主资源。

示例:配置 SetupWithManager 以监视辅助资源

// SetupWithManager 使用 Manager 设置控制器。
// 控制器将同时监视 Busybox 主资源和 Deployment 次资源。func (r *BusyboxReconciler) SetupWithManager(mgr ctrl.Manager) error {
    return ctrl.NewControllerManagedBy(mgr).
        For(&examplecomv1alpha1.Busybox{}).  // 观看主要资源        Owns(&appsv1.Deployment{}).          // 监视次要资源(部署)
        Complete(r)
}

确保正确的权限

Kubebuilder 使用 标记 来定义控制器所需的 RBAC 权限。为了使控制器能够正确监视和管理主资源(Busybox)和次资源(Deployment),必须授予其适当的权限;即对这些资源具有 watchgetlistcreateupdatedelete 权限。

示例:RBAC 标记

Reconcile 方法之前,我们需要定义适当的 RBAC 标记。这些标记将由 controller-gen 在您运行 make manifests 时,用于生成所需的角色和权限。

// +kubebuilder:rbac:groups=example.com,resources=busyboxes,verbs=获取;列表;观察;创建;更新;补丁;删除
// +kubebuilder:rbac:groups=apps,resources=deployments,verbs=获取;列表;观察;创建;更新;补丁;删除
  • 第一个标记授予控制器管理 Busybox 自定义资源(主要资源)的权限。
  • 第二个标记授予控制器管理 Deployment 资源(次要资源)的权限。

请注意,我们正在授予对资源的 watch 权限。