Skip to content

Commit b6c210d

Browse files
committed
kubelet config: write back minimum kubelet version status
When a cluster admin rolls out a minimum kubelet verison, the cluster-config-operator (eventually) will render a new set of featuregates based on the new minimum version (enabling ones that are safe given the new minimum). However, on a rollback, we need to make sure the MCS won't return a rendered config using the old features, in the very odd case a cluster admin creates a newly old node and somehow overrides the osImage (unlikely). Thus, we use the similar condition as MCS to serve the config--once one node has updated, we treat the MCP as using the new config. Then, we write the status to the node config object so the node authorization plugin can allow older nodes that are now deemed safe. Signed-off-by: Peter Hunt <[email protected]>
1 parent a35fb27 commit b6c210d

5 files changed

+84
-28
lines changed

pkg/controller/kubelet-config/kubelet_config_bootstrap.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ func RunKubeletBootstrap(templateDir string, kubeletConfigs []*mcfgv1.KubeletCon
2727
nodeConfig = createNewDefaultNodeconfig()
2828
}
2929

30-
featureGates, err := generateFeatureMap(featureGateAccess, openshiftOnlyFeatureGates...)
30+
// TODO FIXME: ignoring min kubelet version writeback for now
31+
featureGates, _, err := generateFeatureMap(featureGateAccess, openshiftOnlyFeatureGates...)
3132
if err != nil {
3233
return nil, fmt.Errorf("could not generate features map: %w", err)
3334
}

pkg/controller/kubelet-config/kubelet_config_controller.go

+53-6
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import (
4242
mcfglistersv1 "github.com/openshift/client-go/machineconfiguration/listers/machineconfiguration/v1"
4343
"github.com/openshift/library-go/pkg/operator/configobserver/featuregates"
4444
"github.com/openshift/machine-config-operator/pkg/apihelpers"
45+
"github.com/openshift/machine-config-operator/pkg/constants"
4546
ctrlcommon "github.com/openshift/machine-config-operator/pkg/controller/common"
4647
mtmpl "github.com/openshift/machine-config-operator/pkg/controller/template"
4748
"github.com/openshift/machine-config-operator/pkg/version"
@@ -528,7 +529,7 @@ func (ctrl *Controller) addAnnotation(cfg *mcfgv1.KubeletConfig, annotationKey,
528529
// This function is not meant to be invoked concurrently with the same key.
529530
//
530531
//nolint:gocyclo
531-
func (ctrl *Controller) syncKubeletConfig(key string) error {
532+
func (ctrl *Controller) syncKubeletConfig(key string) (retErr error) {
532533
startTime := time.Now()
533534
klog.V(4).Infof("Started syncing kubeletconfig %q (%v)", key, startTime)
534535
defer func() {
@@ -595,6 +596,13 @@ func (ctrl *Controller) syncKubeletConfig(key string) error {
595596
return ctrl.syncStatusOnly(cfg, err, "could not get the TLSSecurityProfile from %v: %v", ctrlcommon.APIServerInstanceName, err)
596597
}
597598

599+
updatedPools := map[string]int64{}
600+
601+
featureGates, renderedVersions, err := generateFeatureMap(ctrl.featureGateAccess, openshiftOnlyFeatureGates...)
602+
if err != nil {
603+
return fmt.Errorf("could not generate features map: %w", err)
604+
}
605+
598606
for _, pool := range mcpPools {
599607
if pool.Spec.Configuration.Name == "" {
600608
updateDelay := 5 * time.Second
@@ -629,11 +637,6 @@ func (ctrl *Controller) syncKubeletConfig(key string) error {
629637
return fmt.Errorf("could not get ControllerConfig %w", err)
630638
}
631639

632-
featureGates, err := generateFeatureMap(ctrl.featureGateAccess, openshiftOnlyFeatureGates...)
633-
if err != nil {
634-
return fmt.Errorf("could not generate features map: %w", err)
635-
}
636-
637640
originalKubeConfig, err := generateOriginalKubeletConfigWithFeatureGates(cc, ctrl.templatesDir, role, featureGates, apiServer)
638641
if err != nil {
639642
return ctrl.syncStatusOnly(cfg, err, "could not get original kubelet config: %v", err)
@@ -725,13 +728,57 @@ func (ctrl *Controller) syncKubeletConfig(key string) error {
725728
}
726729
klog.Infof("Applied KubeletConfig %v on MachineConfigPool %v", key, pool.Name)
727730
ctrlcommon.UpdateStateMetric(ctrlcommon.MCCSubControllerState, "machine-config-controller-kubelet-config", "Sync Kubelet Config", pool.Name)
731+
updatedPools[pool.Name] = pool.Status.ObservedGeneration
728732
}
733+
go ctrl.writebackMinimumKubeletVersionIfAppropriate(updatedPools, renderedVersions, nodeConfig, func() ([]*mcfgv1.MachineConfigPool, error) {
734+
return ctrl.getPoolsForKubeletConfig(cfg)
735+
})
729736
if err := ctrl.cleanUpDuplicatedMC(managedKubeletConfigKeyPrefix); err != nil {
730737
return err
731738
}
732739
return ctrl.syncStatusOnly(cfg, nil)
733740
}
734741

742+
func (ctrl *Controller) writebackMinimumKubeletVersionIfAppropriate(updatedPools map[string]int64, renderedVersions []configv1.MinimumComponentVersion, node *osev1.Node, poolGetter func() ([]*mcfgv1.MachineConfigPool, error)) {
743+
renderedKubeletVersion := ""
744+
for _, cv := range renderedVersions {
745+
if cv.Component == configv1.MinimumComponentKubelet {
746+
renderedKubeletVersion = cv.Version
747+
}
748+
}
749+
if node.Spec.MinimumKubeletVersion == node.Status.MinimumKubeletVersion &&
750+
node.Status.MinimumKubeletVersion == renderedKubeletVersion {
751+
klog.InfoS("Skipping writeback to nodes.config.Status.MinimumKubeletVersion because situation not correct",
752+
"nodes.config.Spec.MinimumKubeletVerison", node.Spec.MinimumKubeletVersion,
753+
"nodes.config.Status.MinimumKubeletVerison", node.Status.MinimumKubeletVersion,
754+
"renderedKubeletVersion", renderedKubeletVersion)
755+
return
756+
}
757+
758+
// This featuregate rollout was done as a result of a new minimum kubelet version rolling out, which means we need to wait for at least one
759+
// node in each MCP to finish updating before we set the spec.
760+
if err := wait.ExponentialBackoff(constants.NodeUpdateBackoff, func() (bool, error) {
761+
mcps, err := poolGetter()
762+
if err != nil {
763+
return true, err
764+
}
765+
allUpdated := true
766+
for _, mcp := range mcps {
767+
if oldGeneration, ok := updatedPools[mcp.Name]; ok && (mcp.Status.UpdatedMachineCount == 0 && mcp.Status.ObservedGeneration > oldGeneration) {
768+
allUpdated = false
769+
}
770+
}
771+
return allUpdated, nil
772+
}); err != nil {
773+
klog.Errorf("Failed to update rendered kubelet version: %v", err)
774+
}
775+
776+
node.Status.MinimumKubeletVersion = renderedKubeletVersion
777+
if _, err := ctrl.configClient.ConfigV1().Nodes().Update(context.TODO(), node, metav1.UpdateOptions{}); err != nil {
778+
klog.Errorf("Failed to update node object for rendered kubelet version: %v", err)
779+
}
780+
}
781+
735782
// cleanUpDuplicatedMC removes the MC of non-updated GeneratedByControllerVersionKey if its name contains 'generated-kubelet'.
736783
// BZ 1955517: upgrade when there are more than one configs, the duplicated and upgraded MC will be generated (func getManagedKubeletConfigKey())
737784
// MC with old GeneratedByControllerVersionKey fails the upgrade.

pkg/controller/kubelet-config/kubelet_config_features.go

+23-17
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
"k8s.io/client-go/util/retry"
1818
"k8s.io/klog/v2"
1919

20+
configv1 "github.com/openshift/api/config/v1"
2021
mcfgv1 "github.com/openshift/api/machineconfiguration/v1"
2122
ctrlcommon "github.com/openshift/machine-config-operator/pkg/controller/common"
2223
"github.com/openshift/machine-config-operator/pkg/version"
@@ -73,14 +74,20 @@ func (ctrl *Controller) syncFeatureHandler(key string) error {
7374
return fmt.Errorf("could not get the TLSSecurityProfile from %v: %v", ctrlcommon.APIServerInstanceName, err)
7475
}
7576

77+
featureGates, renderedVersions, err := generateFeatureMap(ctrl.featureGateAccess, openshiftOnlyFeatureGates...)
78+
if err != nil {
79+
return fmt.Errorf("could not generate features map: %w", err)
80+
}
81+
// Fetch the Node Config object
82+
nodeConfig, err := ctrl.nodeConfigLister.Get(ctrlcommon.ClusterNodeInstanceName)
83+
if errors.IsNotFound(err) {
84+
nodeConfig = createNewDefaultNodeconfig()
85+
}
86+
87+
updatedPools := map[string]int64{}
88+
7689
for _, pool := range mcpPools {
77-
var nodeConfig *osev1.Node
7890
role := pool.Name
79-
// Fetch the Node Config object
80-
nodeConfig, err = ctrl.nodeConfigLister.Get(ctrlcommon.ClusterNodeInstanceName)
81-
if errors.IsNotFound(err) {
82-
nodeConfig = createNewDefaultNodeconfig()
83-
}
8491
// Get MachineConfig
8592
managedKey, err := getManagedFeaturesKey(pool, ctrl.client)
8693
if err != nil {
@@ -99,11 +106,6 @@ func (ctrl *Controller) syncFeatureHandler(key string) error {
99106
}
100107
}
101108

102-
featureGates, err := generateFeatureMap(ctrl.featureGateAccess, openshiftOnlyFeatureGates...)
103-
if err != nil {
104-
return fmt.Errorf("could not generate features map: %w", err)
105-
}
106-
107109
rawCfgIgn, err := generateKubeConfigIgnFromFeatures(cc, ctrl.templatesDir, role, featureGates, nodeConfig, apiServer)
108110
if err != nil {
109111
return err
@@ -130,9 +132,12 @@ func (ctrl *Controller) syncFeatureHandler(key string) error {
130132
}
131133
klog.Infof("Applied FeatureSet %v on MachineConfigPool %v", key, pool.Name)
132134
ctrlcommon.UpdateStateMetric(ctrlcommon.MCCSubControllerState, "machine-config-controller-kubelet-config", "Sync FeatureSet", pool.Name)
135+
updatedPools[pool.Name] = pool.Status.ObservedGeneration
133136
}
137+
go ctrl.writebackMinimumKubeletVersionIfAppropriate(updatedPools, renderedVersions, nodeConfig, func() ([]*mcfgv1.MachineConfigPool, error) {
138+
return ctrl.mcpLister.List(labels.Everything())
139+
})
134140
return ctrl.cleanUpDuplicatedMC(managedFeaturesKeyPrefix)
135-
136141
}
137142

138143
func (ctrl *Controller) enqueueFeature(feat *osev1.FeatureGate) {
@@ -179,16 +184,16 @@ func (ctrl *Controller) deleteFeature(obj interface{}) {
179184
// generateFeatureMap returns a map of enabled/disabled feature gate selection with exclusion list
180185
//
181186
//nolint:gocritic
182-
func generateFeatureMap(featuregateAccess featuregates.FeatureGateAccess, exclusions ...osev1.FeatureGateName) (map[string]bool, error) {
187+
func generateFeatureMap(featuregateAccess featuregates.FeatureGateAccess, exclusions ...osev1.FeatureGateName) (map[string]bool, []configv1.MinimumComponentVersion, error) {
183188
rv := make(map[string]bool)
184189

185190
if !featuregateAccess.AreInitialFeatureGatesObserved() {
186-
return nil, fmt.Errorf("initial feature gates are not observed")
191+
return nil, nil, fmt.Errorf("initial feature gates are not observed")
187192
}
188193

189194
features, err := featuregateAccess.CurrentFeatureGates()
190195
if err != nil {
191-
return nil, fmt.Errorf("could not get current feature gates: %w", err)
196+
return nil, nil, fmt.Errorf("could not get current feature gates: %w", err)
192197
}
193198

194199
for _, feat := range features.KnownFeatures() {
@@ -203,7 +208,7 @@ func generateFeatureMap(featuregateAccess featuregates.FeatureGateAccess, exclus
203208
for _, excluded := range exclusions {
204209
delete(rv, string(excluded))
205210
}
206-
return rv, nil
211+
return rv, features.RenderedMinimumComponentVersions(), nil
207212
}
208213

209214
func generateKubeConfigIgnFromFeatures(cc *mcfgv1.ControllerConfig, templatesDir, role string, featureGates map[string]bool, nodeConfig *osev1.Node, apiServer *osev1.APIServer) ([]byte, error) {
@@ -233,7 +238,8 @@ func generateKubeConfigIgnFromFeatures(cc *mcfgv1.ControllerConfig, templatesDir
233238
func RunFeatureGateBootstrap(templateDir string, featureGateAccess featuregates.FeatureGateAccess, nodeConfig *osev1.Node, controllerConfig *mcfgv1.ControllerConfig, mcpPools []*mcfgv1.MachineConfigPool, apiServer *osev1.APIServer) ([]*mcfgv1.MachineConfig, error) {
234239
machineConfigs := []*mcfgv1.MachineConfig{}
235240

236-
featureGates, err := generateFeatureMap(featureGateAccess, openshiftOnlyFeatureGates...)
241+
// TODO FIXME: do we need rendered versions here?
242+
featureGates, _, err := generateFeatureMap(featureGateAccess, openshiftOnlyFeatureGates...)
237243
if err != nil {
238244
return nil, fmt.Errorf("could not generate features map: %w", err)
239245
}

pkg/controller/kubelet-config/kubelet_config_features_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ func TestFeatureGateDrift(t *testing.T) {
4444
fgAccess := featuregates.NewHardcodedFeatureGateAccess(features.Spec.FeatureGateSelection.CustomNoUpgrade.Enabled, features.Spec.FeatureGateSelection.CustomNoUpgrade.Disabled)
4545
ctrl := f.newController(fgAccess)
4646

47-
featureGates, err := generateFeatureMap(ctrl.featureGateAccess, openshiftOnlyFeatureGates...)
47+
featureGates, _, err := generateFeatureMap(ctrl.featureGateAccess, openshiftOnlyFeatureGates...)
4848
require.NoError(t, err)
4949

5050
// Generate kubelet config with feature gates applied
@@ -222,7 +222,7 @@ func TestBootstrapFeaturesCustomNoUpgrade(t *testing.T) {
222222
}
223223

224224
fgAccess := featuregates.NewHardcodedFeatureGateAccess(features.Spec.FeatureGateSelection.CustomNoUpgrade.Enabled, features.Spec.FeatureGateSelection.CustomNoUpgrade.Disabled)
225-
defaultFeatureGates, err := generateFeatureMap(fgAccess)
225+
defaultFeatureGates, _, err := generateFeatureMap(fgAccess)
226226
if err != nil {
227227
t.Errorf("could not generate defaultFeatureGates: %v", err)
228228
}

pkg/controller/kubelet-config/kubelet_config_nodes.go

+4-2
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,8 @@ func (ctrl *Controller) syncNodeConfigHandler(key string) error {
101101
return fmt.Errorf("could not get the TLSSecurityProfile from %v: %v", ctrlcommon.APIServerInstanceName, err)
102102
}
103103

104-
featureGates, err := generateFeatureMap(ctrl.featureGateAccess, openshiftOnlyFeatureGates...)
104+
// renderedVersions will be handled in syncFeatureHandler below
105+
featureGates, _, err := generateFeatureMap(ctrl.featureGateAccess, openshiftOnlyFeatureGates...)
105106
if err != nil {
106107
return fmt.Errorf("could not generate features map: %w", err)
107108
}
@@ -309,7 +310,8 @@ func RunNodeConfigBootstrap(templateDir string, featureGateAccess featuregates.F
309310

310311
configs := []*mcfgv1.MachineConfig{}
311312

312-
featureGates, err := generateFeatureMap(featureGateAccess, openshiftOnlyFeatureGates...)
313+
// Ignoring rendered versions here.
314+
featureGates, _, err := generateFeatureMap(featureGateAccess, openshiftOnlyFeatureGates...)
313315
if err != nil {
314316
return nil, fmt.Errorf("could not generate features map: %w", err)
315317
}

0 commit comments

Comments
 (0)