What’s in a controller?
Controllers are the core of Kubernetes, and of any operator.
It’s a controller’s job to ensure that, for any given object, the actualstate of the world (both the cluster state, and potentially external statelike running containers for Kubelet or loadbalancers for a cloud provider)matches the desired state in the object. Each controller focuses on oneroot Kind, but may interact with other Kinds.
We call this process reconciling.
In controller-runtime, the logic that implements the reconciling fora specific kind is called a Reconciler. A reconcilertakes the name of an object, and returns whether or not we need to tryagain (e.g. in case of errors or periodic controllers, like theHorizontalPodAutoscaler).
Apache License
Licensed under the Apache License, Version 2.0 (the “License”);you may not use this file except in compliance with the License.You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, softwaredistributed under the License is distributed on an “AS IS” BASIS,WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.See the License for the specific language governing permissions andlimitations under the License. First, we start out with some standard imports.As before, we need the core controller-runtime library, as well asthe client package, and the package for our API types.
package controllers
import (
"context"
"github.com/go-logr/logr"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
batchv1 "tutorial.kubebuilder.io/project/api/v1"
)
Next, kubebuilder has scaffold out a basic reconciler struct for us.Pretty much every reconciler needs to log, and needs to be able to fetchobjects, so these are added out of the box.
// CronJobReconciler reconciles a CronJob object
type CronJobReconciler struct {
client.Client
Log logr.Logger
}
Most controllers eventually end up running on the cluster, so they need RBAC permissions.These are the bare minimum permissions needed to run. As we add more functionality, we’llneed to revisit these.
// +kubebuilder:rbac:groups=batch.tutorial.kubebuilder.io,resources=cronjobs,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=batch.tutorial.kubebuilder.io,resources=cronjobs/status,verbs=get;update;patch
Reconcile
actually performs the reconciling for a single named object.Our Request just has a name, but we can use the client to fetchthat object from the cache.
We return an empty result and no error, which indicates to controller-runtime thatwe’ve succesfully reconciled this object and don’t need to try again until there’ssome changes.
Most controllers need a logging handle and a context, so we set them up here.
The context is used to allow cancelation ofrequests, and potentially things like tracing. It’s the first argument to allclient methods. The Background
context is just a basic context without anyextra data or timing restrictions.
The logging handle lets us log. controller-runtime uses structured logging through alibrary called logr. As we’ll see shortly,logging works by attaching key-value pairs to a static message. We can pre-assignsome pairs at the top of our reconcile method to have those attached to all loglines in this reconciler.
func (r *CronJobReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
_ = context.Background()
_ = r.Log.WithValues("cronjob", req.NamespacedName)
// your logic here
return ctrl.Result{}, nil
}
Finally, we add this reconciler to the manager, so that it gets startedwhen the manager is started.
For now, we just note that this reconciler operates on CronJob
s. Later,we’ll use this to mark that we care about related objects as well.
TODO: jump back to main?
func (r *CronJobReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&batchv1.CronJob{}).
Complete(r)
}
Now that we’ve seen the basic structure of a reconciler, let’s fill outthe logic for CronJob
s.