From 5e58077464f161b5d3bbd96a451c1f53b3b4c234 Mon Sep 17 00:00:00 2001 From: Ankit Mahajan Date: Tue, 23 Jun 2026 08:12:27 +0530 Subject: [PATCH 1/2] kubelet-config: register MCP informer to trigger reconciliation on pool changes The kubelet config controller registers event handlers for KubeletConfig, FeatureGate, NodeConfig, and APIServer informers but not for MachineConfigPool. When a new MCP is created, the controller is never notified and fails to generate the per-pool kubelet MachineConfigs (97--generated-kubelet, 98--generated-kubelet). Users must restart the machine-config-controller pod for the configs to appear. Register Add/Update/Delete event handlers on the MCP informer so that pool additions and label changes trigger reconciliation of all KubeletConfigs, FeatureGates, and NodeConfigs, ensuring the generated MachineConfigs are created without requiring a controller restart. Fixes: https://github.com/openshift/machine-config-operator/issues/5521 Co-authored-by: Cursor Signed-off-by: Ankit Mahajan --- .../kubelet_config_controller.go | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/pkg/controller/kubelet-config/kubelet_config_controller.go b/pkg/controller/kubelet-config/kubelet_config_controller.go index b824a8d490..27831bc37b 100644 --- a/pkg/controller/kubelet-config/kubelet_config_controller.go +++ b/pkg/controller/kubelet-config/kubelet_config_controller.go @@ -138,6 +138,12 @@ func New( fgHandler: fgHandler, } + mcpInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ + AddFunc: ctrl.addMachineConfigPool, + UpdateFunc: ctrl.updateMachineConfigPool, + DeleteFunc: ctrl.deleteMachineConfigPool, + }) + mkuInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ AddFunc: ctrl.addKubeletConfig, UpdateFunc: ctrl.updateKubeletConfig, @@ -317,6 +323,62 @@ func (ctrl *Controller) deleteKubeletConfig(obj interface{}) { } } +func (ctrl *Controller) addMachineConfigPool(obj interface{}) { + pool := obj.(*mcfgv1.MachineConfigPool) + klog.V(4).Infof("MachineConfigPool %s added, re-syncing kubelet config controller", pool.Name) + ctrl.requeueKubeletConfigsForPool() +} + +func (ctrl *Controller) updateMachineConfigPool(old, cur interface{}) { + oldPool := old.(*mcfgv1.MachineConfigPool) + curPool := cur.(*mcfgv1.MachineConfigPool) + if !reflect.DeepEqual(oldPool.Labels, curPool.Labels) { + klog.V(4).Infof("MachineConfigPool %s labels changed, re-syncing kubelet config controller", curPool.Name) + ctrl.requeueKubeletConfigsForPool() + } +} + +func (ctrl *Controller) deleteMachineConfigPool(obj interface{}) { + pool, ok := obj.(*mcfgv1.MachineConfigPool) + if !ok { + tombstone, ok := obj.(cache.DeletedFinalStateUnknown) + if !ok { + utilruntime.HandleError(fmt.Errorf("couldn't get object from tombstone %#v", obj)) + return + } + pool, ok = tombstone.Obj.(*mcfgv1.MachineConfigPool) + if !ok { + utilruntime.HandleError(fmt.Errorf("tombstone contained object that is not a MachineConfigPool %#v", obj)) + return + } + } + klog.V(4).Infof("MachineConfigPool %s deleted", pool.Name) +} + +// requeueKubeletConfigsForPool triggers reconciliation of all kubelet-related +// controllers so that generated MachineConfigs are created for any new or +// updated MachineConfigPool. +func (ctrl *Controller) requeueKubeletConfigsForPool() { + kcs, err := ctrl.mckLister.List(labels.Everything()) + if err != nil { + utilruntime.HandleError(fmt.Errorf("could not list KubeletConfigs for MCP re-sync: %w", err)) + } else { + for _, kc := range kcs { + ctrl.enqueueKubeletConfig(kc) + } + } + + features, err := ctrl.featLister.Get(ctrlcommon.ClusterFeatureInstanceName) + if err == nil { + ctrl.enqueueFeature(features) + } + + nodeConfig, err := ctrl.nodeConfigLister.Get(ctrlcommon.ClusterNodeInstanceName) + if err == nil { + ctrl.enqueueNodeConfig(nodeConfig) + } +} + func (ctrl *Controller) cascadeDelete(cfg *mcfgv1.KubeletConfig) error { if len(cfg.GetFinalizers()) == 0 { return nil From 135ef5266524df3fbc9835b9530814d86658bbaf Mon Sep 17 00:00:00 2001 From: Ankit Mahajan Date: Tue, 23 Jun 2026 08:40:42 +0530 Subject: [PATCH 2/2] kubelet-config: handle non-NotFound errors in MCP re-sync Log transient errors from FeatureGate and NodeConfig lister lookups in requeueKubeletConfigsForPool instead of silently ignoring them. NotFound errors remain acceptable and are skipped. Co-authored-by: Cursor Signed-off-by: Ankit Mahajan --- pkg/controller/kubelet-config/kubelet_config_controller.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/controller/kubelet-config/kubelet_config_controller.go b/pkg/controller/kubelet-config/kubelet_config_controller.go index 27831bc37b..a4d6535acf 100644 --- a/pkg/controller/kubelet-config/kubelet_config_controller.go +++ b/pkg/controller/kubelet-config/kubelet_config_controller.go @@ -371,11 +371,15 @@ func (ctrl *Controller) requeueKubeletConfigsForPool() { features, err := ctrl.featLister.Get(ctrlcommon.ClusterFeatureInstanceName) if err == nil { ctrl.enqueueFeature(features) + } else if !macherrors.IsNotFound(err) { + utilruntime.HandleError(fmt.Errorf("could not get FeatureGate for MCP re-sync: %w", err)) } nodeConfig, err := ctrl.nodeConfigLister.Get(ctrlcommon.ClusterNodeInstanceName) if err == nil { ctrl.enqueueNodeConfig(nodeConfig) + } else if !macherrors.IsNotFound(err) { + utilruntime.HandleError(fmt.Errorf("could not get NodeConfig for MCP re-sync: %w", err)) } }