internal/controller/pdb_to_evictionautoscaler_controller.go (134 lines of code) (raw):

package controllers import ( "context" "fmt" types "github.com/azure/eviction-autoscaler/api/v1" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" policyv1 "k8s.io/api/policy/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" k8s_types "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/tools/record" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/event" "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/predicate" "sigs.k8s.io/controller-runtime/pkg/reconcile" ) var errOwnerNotFound error = fmt.Errorf("owner not found") // PDBToEvictionAutoScalerReconciler reconciles a PodDisruptionBudget object. type PDBToEvictionAutoScalerReconciler struct { client.Client Scheme *runtime.Scheme Recorder record.EventRecorder } // +kubebuilder:rbac:groups=policy,resources=poddisruptionbudgets,verbs=get;list;create;watch;update // +kubebuilder:rbac:groups=apps,resources=replicasets,verbs=get;list;update;watch // +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;update;watch // Reconcile reads the state of the cluster for a PDB and creates/deletes EvictionAutoScalers accordingly. func (r *PDBToEvictionAutoScalerReconciler) Reconcile(ctx context.Context, req reconcile.Request) (reconcile.Result, error) { logger := log.FromContext(ctx) logger.WithValues("pdb", req.Name, "namespace", req.Namespace) ctx = log.IntoContext(ctx, logger) // Fetch the PodDisruptionBudget object based on the reconcile request var pdb policyv1.PodDisruptionBudget err := r.Get(ctx, req.NamespacedName, &pdb) if err != nil { return reconcile.Result{}, err } // If the PDB exists, create a corresponding EvictionAutoScaler if it does not exist var EvictionAutoScaler types.EvictionAutoScaler err = r.Get(ctx, req.NamespacedName, &EvictionAutoScaler) if err != nil { if !apierrors.IsNotFound(err) { return ctrl.Result{}, err } deploymentName, e := r.discoverDeployment(ctx, &pdb) if e != nil { if e == errOwnerNotFound { return reconcile.Result{}, nil } return reconcile.Result{}, e } //variables controller := true blockOwnerDeletion := true // Create a new EvictionAutoScaler EvictionAutoScaler = types.EvictionAutoScaler{ TypeMeta: metav1.TypeMeta{ Kind: "EvictionAutoScaler", APIVersion: "eviction-autoscaler.azure.com/v1", }, ObjectMeta: metav1.ObjectMeta{ Name: pdb.Name, Namespace: pdb.Namespace, Annotations: map[string]string{ "createdBy": "PDBToEvictionAutoScalerController", "target": deploymentName, }, OwnerReferences: []metav1.OwnerReference{ { APIVersion: "policy/v1", Kind: "PodDisruptionBudget", Name: pdb.Name, UID: pdb.UID, Controller: &controller, // Mark as managed by this controller BlockOwnerDeletion: &blockOwnerDeletion, // Prevent deletion of the EvictionAutoScaler until the controller is deleted }, }, }, Spec: types.EvictionAutoScalerSpec{ TargetName: deploymentName, TargetKind: deploymentKind, }, } err := r.Create(ctx, &EvictionAutoScaler) if err != nil { return reconcile.Result{}, fmt.Errorf("unable to create EvictionAutoScaler: %v", err) } logger.Info("Created EvictionAutoScaler") } // Return no error and no requeue return reconcile.Result{}, nil } // SetupWithManager sets up the controller with the Manager. func (r *PDBToEvictionAutoScalerReconciler) SetupWithManager(mgr ctrl.Manager) error { // Set up the controller to watch Deployments and trigger the reconcile function return ctrl.NewControllerManagedBy(mgr). For(&policyv1.PodDisruptionBudget{}). WithEventFilter(predicate.Funcs{ // Only trigger for Create and Delete events UpdateFunc: func(e event.UpdateEvent) bool { //ToDo: theoretically you could have a pdb update and change // its label selectors in which case you might need to update the deployment target? return false }, }). Owns(&types.EvictionAutoScaler{}). // Watch EvictionAutoScalers for ownership Complete(r) } func (r *PDBToEvictionAutoScalerReconciler) discoverDeployment(ctx context.Context, pdb *policyv1.PodDisruptionBudget) (string, error) { logger := log.FromContext(ctx) // Convert PDB label selector to Kubernetes selector selector, err := metav1.LabelSelectorAsSelector(pdb.Spec.Selector) if err != nil { return "", fmt.Errorf("error converting label selector: %v", err) } logger.Info("PDB Selector", "selector", pdb.Spec.Selector) podList := &corev1.PodList{} err = r.List(ctx, podList, &client.ListOptions{Namespace: pdb.Namespace, LabelSelector: selector}) if err != nil { return "", fmt.Errorf("error listing pods: %v", err) } logger.Info("Number of pods found", "count", len(podList.Items)) if len(podList.Items) == 0 { return "", fmt.Errorf("no pods found matching the PDB selector %s; leaky pdb(?!)", pdb.Name) } // Iterate through each pod for _, pod := range podList.Items { // Check the OwnerReferences of each pod for _, ownerRef := range pod.OwnerReferences { if ownerRef.Kind == "ReplicaSet" { replicaSet := &appsv1.ReplicaSet{} err = r.Get(ctx, k8s_types.NamespacedName{Name: ownerRef.Name, Namespace: pdb.Namespace}, replicaSet) if apierrors.IsNotFound(err) { return "", fmt.Errorf("error fetching ReplicaSet: %v", err) } // Log ReplicaSet details logger.Info("Found ReplicaSet", "replicaSet", replicaSet.Name) // Look for the Deployment owner of the ReplicaSet for _, rsOwnerRef := range replicaSet.OwnerReferences { if rsOwnerRef.Kind == "Deployment" { logger.Info("Found Deployment owner", "deployment", rsOwnerRef.Name) return rsOwnerRef.Name, nil } } // no replicaset owner just move on and see if any other pods have have something. } //// Optional: Handle StatefulSets if necessary //if ownerRef.Kind == "StatefulSet" { // statefulSet := &appsv1.StatefulSet{} // err = r.Get(ctx, k8s_types.NamespacedName{Name: ownerRef.Name, Namespace: pdb.Namespace}, statefulSet) // if apierrors.IsNotFound(err) { // return "", fmt.Errorf("error fetching StatefulSet: %v", err) // } // logger.Info("Found StatefulSet owner", "statefulSet", statefulSet.Name) // // Handle StatefulSet logic if required //} } } logger.Info("No Deployment owner found") return "", errOwnerNotFound }