Skip to content

MemcachedReconciler Scaffold and Watch Setup

Reference documentation for the MemcachedReconciler struct, its watch configuration via SetupWithManager, and the RBAC markers that generate the operator's ClusterRole.

Source: internal/controller/memcached_controller.go

MemcachedReconciler Struct

The reconciler struct embeds the controller-runtime client and carries the runtime scheme and an event recorder:

go
type MemcachedReconciler struct {
    client.Client
    Scheme   *runtime.Scheme
    Recorder events.EventRecorder
}
FieldTypeDescription
Clientclient.ClientEmbedded controller-runtime client for reading/writing Kubernetes objects
Scheme*runtime.SchemeRuntime scheme for GVK resolution (serialization, deserialization, watches)
Recorderevents.EventRecorderKubernetes event recorder for emitting events on Memcached CRs

The struct satisfies the reconcile.Reconciler interface by implementing the Reconcile(ctx, ctrl.Request) (ctrl.Result, error) method.

Initialization in cmd/main.go

The reconciler is created and registered in the operator's main function:

go
if err = (&controller.MemcachedReconciler{
    Client:   mgr.GetClient(),
    Scheme:   mgr.GetScheme(),
    Recorder: mgr.GetEventRecorder("memcached-controller"),
}).SetupWithManager(mgr); err != nil {
    setupLog.Error(err, "unable to create controller", "controller", "Memcached")
    os.Exit(1)
}
  • mgr.GetClient() returns a cached client that reads from the informer cache and writes directly to the API server.
  • mgr.GetScheme() returns the scheme with both core Kubernetes types and memcached.c5c3.io/v1alpha1 types registered.
  • mgr.GetEventRecorder("memcached-controller") creates an event recorder that tags events with source component memcached-controller.

Reconcile Method

The Reconcile method is the entry point for each reconciliation cycle. In the current scaffold, it fetches the Memcached CR and handles the not-found case:

go
func (r *MemcachedReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    logger := log.FromContext(ctx)

    memcached := &memcachedv1alpha1.Memcached{}
    if err := r.Get(ctx, req.NamespacedName, memcached); err != nil {
        if apierrors.IsNotFound(err) {
            logger.Info("Memcached resource not found; ignoring since it must have been deleted")
            return ctrl.Result{}, nil
        }
        logger.Error(err, "Failed to get Memcached resource")
        return ctrl.Result{}, err
    }

    logger.Info("Reconciling Memcached", "name", memcached.Name, "namespace", memcached.Namespace)
    return ctrl.Result{}, nil
}

Behavior

ScenarioReturn ValueEffect
CR not found (deleted)ctrl.Result{}, nilNo requeue, no error logged
CR fetch fails (non-NotFound)ctrl.Result{}, errRequeue with exponential backoff
CR fetched successfullyctrl.Result{}, nilLog reconciliation, no requeue

The scaffold returns an empty result after a successful fetch. Subsequent features (MO-0005 through MO-0014) add reconciliation logic for Deployments, Services, PDBs, NetworkPolicies, status updates, and ServiceMonitors.


Watch Configuration

SetupWithManager registers the controller with the manager and configures which Kubernetes resources trigger reconciliation:

go
func (r *MemcachedReconciler) SetupWithManager(mgr ctrl.Manager) error {
    return ctrl.NewControllerManagedBy(mgr).
        For(&memcachedv1alpha1.Memcached{}).
        Owns(&appsv1.Deployment{}).
        Owns(&corev1.Service{}).
        Owns(&policyv1.PodDisruptionBudget{}).
        Owns(&networkingv1.NetworkPolicy{}).
        Named("memcached").
        Complete(r)
}

Watch Types

MethodResource TypeAPI GroupTrigger Condition
ForMemcachedmemcached.c5c3.ioAny create, update, or delete of a Memcached CR
OwnsDeploymentappsChanges to Deployments with an owner reference pointing to a Memcached CR
OwnsService(core)Changes to Services with an owner reference pointing to a Memcached CR
OwnsPodDisruptionBudgetpolicyChanges to PDBs with an owner reference pointing to a Memcached CR
OwnsNetworkPolicynetworking.k8s.ioChanges to NetworkPolicies with an owner reference pointing to a Memcached CR

How For Works

For(&memcachedv1alpha1.Memcached{}) registers the Memcached CRD as the primary watched resource. Any create, update, or delete event on a Memcached CR enqueues a ctrl.Request with the CR's NamespacedName for reconciliation.

How Owns Works

Owns registers a watch on a secondary resource type and automatically:

  1. Filters events to only those resources that have an ownerReference pointing to a Memcached CR.
  2. Maps the event to the owning Memcached CR's NamespacedName, so Reconcile receives the owner's name — not the owned resource's name.

This means:

  • A Deployment created by the reconciler with controllerutil.SetControllerReference will trigger reconciliation of the owning Memcached CR if modified externally.
  • A Deployment without a Memcached owner reference is ignored entirely.

ServiceMonitor Watch

The ServiceMonitor type is intentionally not included in Owns() in this scaffold. The ServiceMonitor CRD (monitoring.coreos.com) may not be installed on all clusters, and creating an Owns watch for a non-existent CRD would cause SetupWithManager to fail. The ServiceMonitor watch is added in MO-0014 with conditional CRD detection.


RBAC Markers

Kubebuilder RBAC markers on the reconciler generate the ClusterRole rules in config/rbac/role.yaml when make manifests is run.

Marker Definitions

go
// +kubebuilder:rbac:groups=memcached.c5c3.io,resources=memcacheds,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=memcached.c5c3.io,resources=memcacheds/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=memcached.c5c3.io,resources=memcacheds/finalizers,verbs=update
// +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups="",resources=services,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=policy,resources=poddisruptionbudgets,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=networking.k8s.io,resources=networkpolicies,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=monitoring.coreos.com,resources=servicemonitors,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups="",resources=events,verbs=create;patch

Generated ClusterRole Rules

The markers produce the following rules in config/rbac/role.yaml:

API GroupResourceVerbsPurpose
memcached.c5c3.iomemcachedsget, list, watch, create, update, patch, deleteFull CRUD on Memcached CRs
memcached.c5c3.iomemcacheds/statusget, update, patchStatus subresource updates
memcached.c5c3.iomemcacheds/finalizersupdateFinalizer management
appsdeploymentsget, list, watch, create, update, patch, deleteManage Memcached Deployments
(core)servicesget, list, watch, create, update, patch, deleteManage headless Services
policypoddisruptionbudgetsget, list, watch, create, update, patch, deleteManage PodDisruptionBudgets
networking.k8s.ionetworkpoliciesget, list, watch, create, update, patch, deleteManage NetworkPolicies
monitoring.coreos.comservicemonitorsget, list, watch, create, update, patch, deleteManage Prometheus ServiceMonitors
(core)eventscreate, patchEmit Kubernetes events

Why RBAC Markers Are Added Now

RBAC markers for all resource types (including ServiceMonitors, which are reconciled in MO-0014) are declared in this scaffold because:

  1. The ClusterRole must be complete before the operator is deployed. Adding permissions incrementally would require redeploying the operator after each feature.
  2. Missing RBAC permissions cause runtime Forbidden errors that are difficult to debug.
  3. The markers serve as documentation of the full set of resources the operator will manage.

Reconciliation Flow Diagram

text
                     ┌──────────────────────────────┐
                     │     Kubernetes API Server     │
                     └──────┬───────────┬───────────┘
                            │           │
              Watch events  │           │  Watch events
              (Memcached)   │           │  (Owned resources)
                            ▼           ▼
                     ┌──────────────────────────────┐
                     │    controller-runtime cache   │
                     │    (informer per GVK)         │
                     └──────────────┬───────────────┘

                                    │  Enqueue ctrl.Request
                                    │  {Namespace, Name}

                     ┌──────────────────────────────┐
                     │        Work Queue            │
                     └──────────────┬───────────────┘

                                    │  Dequeue

                     ┌──────────────────────────────┐
                     │  MemcachedReconciler.Reconcile│
                     │                              │
                     │  1. Fetch Memcached CR        │
                     │  2. If NotFound → return nil  │
                     │  3. If error → return error   │
                     │  4. (future: reconcile owned  │
                     │      resources)               │
                     └──────────────────────────────┘

Event-to-reconcile mapping:

Event SourceEvent TypeReconciled Object
Memcached CRCreate/Update/DeleteThe Memcached CR itself
Owned DeploymentCreate/Update/DeleteThe owning Memcached CR
Owned ServiceCreate/Update/DeleteThe owning Memcached CR
Owned PDBCreate/Update/DeleteThe owning Memcached CR
Owned NetworkPolicyCreate/Update/DeleteThe owning Memcached CR
Unowned DeploymentAny(not reconciled)