From aa185281afe4a69d9de869ba56f3085ea527e45e Mon Sep 17 00:00:00 2001 From: Anant Kumar Date: Fri, 30 Oct 2026 11:28:55 +0530 Subject: [PATCH] pkg/features: remove GA feature gate NodeSwap The gate graduated to GA and was locked to its default in 1.34, so 1.38 is the first release in which the compatibility window allows removing it. With the gate always on, the kubelet checks collapse: swap behaviour is validated unconditionally, the node status reports swap capacity whenever cadvisor reports it, and LimitedSwap depends only on the swap controller being available. The kubelet configuration error for setting memorySwap while the gate was disabled is unreachable, since a locked gate cannot be turned off, and goes with it. The e2e feature label of the same name is a test selector rather than a reference to the gate, so it stays. --- cmd/kubelet/app/server.go | 16 +++++++--------- pkg/features/kube_features.go | 15 --------------- .../allocation/allocation_manager_test.go | 1 - .../apis/config/validation/validation.go | 17 ++++++----------- .../apis/config/validation/validation_test.go | 1 - .../kuberuntime/kuberuntime_container_linux.go | 2 +- pkg/kubelet/nodestatus/setters.go | 2 +- pkg/kubelet/nodestatus/setters_test.go | 17 ----------------- .../reference/feature_list.md | 1 - .../reference/versioned_feature_list.yaml | 18 ------------------ test/e2e_node/kubelet_config_dir_test.go | 6 ------ test/e2e_node/swap_test.go | 10 ---------- 12 files changed, 15 insertions(+), 91 deletions(-) diff --git a/cmd/kubelet/app/server.go b/cmd/kubelet/app/server.go index 9bf7967f80c..dcd25b1dc87 100644 --- a/cmd/kubelet/app/server.go +++ b/cmd/kubelet/app/server.go @@ -911,15 +911,13 @@ func run(ctx context.Context, s *options.KubeletServer, kubeDeps *kubelet.Depend return fmt.Errorf("topology manager policy options %v require feature gates %q enabled", s.TopologyManagerPolicyOptions, features.TopologyManagerPolicyOptions) } - if utilfeature.DefaultFeatureGate.Enabled(features.NodeSwap) { - if !kubeletutil.IsCgroup2UnifiedMode() && s.MemorySwap.SwapBehavior == string(kubelettypes.LimitedSwap) { - // This feature is not supported for cgroupv1 so we are failing early. - return fmt.Errorf("swap feature is enabled and LimitedSwap but it is only supported with cgroupv2") - } - if !s.FailSwapOn && s.MemorySwap.SwapBehavior == "" { - // This is just a log because we are using a default of NoSwap. - logger.Info("NoSwap is set due to memorySwapBehavior not specified", "memorySwapBehavior", s.MemorySwap.SwapBehavior, "FailSwapOn", s.FailSwapOn) - } + if !kubeletutil.IsCgroup2UnifiedMode() && s.MemorySwap.SwapBehavior == string(kubelettypes.LimitedSwap) { + // LimitedSwap is not supported for cgroupv1 so we are failing early. + return fmt.Errorf("LimitedSwap is only supported with cgroupv2") + } + if !s.FailSwapOn && s.MemorySwap.SwapBehavior == "" { + // This is just a log because we are using a default of NoSwap. + logger.Info("NoSwap is set due to memorySwapBehavior not specified", "memorySwapBehavior", s.MemorySwap.SwapBehavior, "FailSwapOn", s.FailSwapOn) } kubeDeps.ContainerManager, err = cm.NewContainerManager( diff --git a/pkg/features/kube_features.go b/pkg/features/kube_features.go index 09224d9e4b6..04128dcc7d8 100644 --- a/pkg/features/kube_features.go +++ b/pkg/features/kube_features.go @@ -762,12 +762,6 @@ const ( // The recommendation is to enable it on a need basis for debugging purposes and disabling otherwise. NodeLogQuery featuregate.Feature = "NodeLogQuery" - // owner: @iholder101 @kannon92 - // kep: https://kep.k8s.io/2400 - // - // Permits kubelet to run with swap enabled. - NodeSwap featuregate.Feature = "NodeSwap" - // owner: @sanposhiho, @wojtek-t // kep: https://kep.k8s.io/5278 // @@ -1756,13 +1750,6 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate {Version: version.MustParse("1.36"), Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.39, locked to default in 1.36 }, - NodeSwap: { - {Version: version.MustParse("1.22"), Default: false, PreRelease: featuregate.Alpha}, - {Version: version.MustParse("1.28"), Default: false, PreRelease: featuregate.Beta}, - {Version: version.MustParse("1.30"), Default: true, PreRelease: featuregate.Beta}, - {Version: version.MustParse("1.34"), Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.37 - }, - NominatedNodeNameForExpectation: { {Version: version.MustParse("1.34"), Default: false, PreRelease: featuregate.Alpha}, {Version: version.MustParse("1.35"), Default: true, PreRelease: featuregate.Beta}, @@ -2555,8 +2542,6 @@ var defaultKubernetesFeatureGateDependencies = map[featuregate.Feature][]feature NodeLogQuery: {}, - NodeSwap: {}, - NominatedNodeNameForExpectation: {}, OpportunisticBatching: {}, diff --git a/pkg/kubelet/allocation/allocation_manager_test.go b/pkg/kubelet/allocation/allocation_manager_test.go index bdbf926c17d..d23a4a6119f 100644 --- a/pkg/kubelet/allocation/allocation_manager_test.go +++ b/pkg/kubelet/allocation/allocation_manager_test.go @@ -1694,7 +1694,6 @@ func TestRetryPendingResizesWithSwap(t *testing.T) { featuregatetesting.SetFeatureGatesDuringTest(t, utilfeature.DefaultFeatureGate, featuregatetesting.FeatureOverrides{ features.InPlacePodVerticalScaling: true, - features.NodeSwap: true, }) logger, tCtx := ktesting.NewTestContext(t) noSwapContainerName, swapContainerName := "test-container-noswap", "test-container-limitedswap" diff --git a/pkg/kubelet/apis/config/validation/validation.go b/pkg/kubelet/apis/config/validation/validation.go index c38c4523f2f..76d21be7ccf 100644 --- a/pkg/kubelet/apis/config/validation/validation.go +++ b/pkg/kubelet/apis/config/validation/validation.go @@ -250,17 +250,12 @@ func ValidateKubeletConfiguration(kc *kubeletconfig.KubeletConfiguration, featur allErrors = append(allErrors, fmt.Errorf("invalid configuration: Specifying shutdownGracePeriodByPodPriority requires feature gate GracefulNodeShutdownBasedOnPodPriority")) } } - if localFeatureGate.Enabled(features.NodeSwap) { - switch kc.MemorySwap.SwapBehavior { - case "": - case string(kubetypes.NoSwap): - case string(kubetypes.LimitedSwap): - default: - allErrors = append(allErrors, fmt.Errorf("invalid configuration: memorySwap.swapBehavior %q must be one of: \"\", %q or %q", kc.MemorySwap.SwapBehavior, kubetypes.LimitedSwap, kubetypes.NoSwap)) - } - } - if !localFeatureGate.Enabled(features.NodeSwap) && kc.MemorySwap != (kubeletconfig.MemorySwapConfiguration{}) { - allErrors = append(allErrors, fmt.Errorf("invalid configuration: memorySwap.swapBehavior cannot be set when NodeSwap feature flag is disabled")) + switch kc.MemorySwap.SwapBehavior { + case "": + case string(kubetypes.NoSwap): + case string(kubetypes.LimitedSwap): + default: + allErrors = append(allErrors, fmt.Errorf("invalid configuration: memorySwap.swapBehavior %q must be one of: \"\", %q or %q", kc.MemorySwap.SwapBehavior, kubetypes.LimitedSwap, kubetypes.NoSwap)) } if localFeatureGate.Enabled(features.KubeletCrashLoopBackOffMax) { diff --git a/pkg/kubelet/apis/config/validation/validation_test.go b/pkg/kubelet/apis/config/validation/validation_test.go index dfb9dec5046..1f6b9303abf 100644 --- a/pkg/kubelet/apis/config/validation/validation_test.go +++ b/pkg/kubelet/apis/config/validation/validation_test.go @@ -396,7 +396,6 @@ func TestValidateKubeletConfiguration(t *testing.T) { }, { name: "invalid MemorySwap.SwapBehavior", configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { - conf.FeatureGates = map[string]bool{"NodeSwap": true} conf.MemorySwap.SwapBehavior = "invalid-behavior" return conf }, diff --git a/pkg/kubelet/kuberuntime/kuberuntime_container_linux.go b/pkg/kubelet/kuberuntime/kuberuntime_container_linux.go index e3d76f91e13..75fb1364adf 100644 --- a/pkg/kubelet/kuberuntime/kuberuntime_container_linux.go +++ b/pkg/kubelet/kuberuntime/kuberuntime_container_linux.go @@ -305,7 +305,7 @@ func (m *kubeGenericRuntimeManager) configureContainerSwapResources(ctx context. func (m *kubeGenericRuntimeManager) GetContainerSwapBehavior(pod *v1.Pod, container *v1.Container) types.SwapBehavior { c := types.SwapBehavior(m.memorySwapBehavior) if c == types.LimitedSwap { - if !utilfeature.DefaultFeatureGate.Enabled(kubefeatures.NodeSwap) || !m.getSwapControllerAvailable() { + if !m.getSwapControllerAvailable() { return types.NoSwap } diff --git a/pkg/kubelet/nodestatus/setters.go b/pkg/kubelet/nodestatus/setters.go index 6fa5b3294c5..300abe9763d 100644 --- a/pkg/kubelet/nodestatus/setters.go +++ b/pkg/kubelet/nodestatus/setters.go @@ -284,7 +284,7 @@ func MachineInfo(nodeName string, node.Status.Capacity[v1.ResourceName(removedResource)] = *resource.NewQuantity(int64(0), resource.DecimalSI) } - if utilfeature.DefaultFeatureGate.Enabled(features.NodeSwap) && info.SwapCapacity != 0 { + if info.SwapCapacity != 0 { node.Status.NodeInfo.Swap = &v1.NodeSwapStatus{ Capacity: ptr.To(int64(info.SwapCapacity)), } diff --git a/pkg/kubelet/nodestatus/setters_test.go b/pkg/kubelet/nodestatus/setters_test.go index 7a40da9111c..e6cf9d238d3 100644 --- a/pkg/kubelet/nodestatus/setters_test.go +++ b/pkg/kubelet/nodestatus/setters_test.go @@ -37,12 +37,9 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/rand" "k8s.io/apimachinery/pkg/util/uuid" - utilfeature "k8s.io/apiserver/pkg/util/feature" - "k8s.io/component-base/featuregate" "k8s.io/component-base/version" "k8s.io/klog/v2" "k8s.io/ktesting" - "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/kubelet/cm" kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" kubecontainertest "k8s.io/kubernetes/pkg/kubelet/container/testing" @@ -452,7 +449,6 @@ func TestMachineInfo(t *testing.T) { expectNode *v1.Node expectEvents []testEvent disableLocalStorageCapacityIsolation bool - featureGateDependencies []featuregate.Feature }{ { desc: "machine identifiers, basic capacity and allocatable", @@ -897,23 +893,10 @@ func TestMachineInfo(t *testing.T) { }, }, }, - featureGateDependencies: []featuregate.Feature{features.NodeSwap}, }, } for _, tc := range cases { - featureGatesMissing := false - for _, featureGateDependency := range tc.featureGateDependencies { - if !utilfeature.DefaultFeatureGate.Enabled(featureGateDependency) { - featureGatesMissing = true - break - } - } - - if featureGatesMissing { - continue - } - t.Run(tc.desc, func(t *testing.T) { ctx := ktesting.Init(t) machineInfoFunc := func() (*cadvisorapi.MachineInfo, error) { diff --git a/test/compatibility_lifecycle/reference/feature_list.md b/test/compatibility_lifecycle/reference/feature_list.md index dca19b5c5b5..30bd7d26671 100644 --- a/test/compatibility_lifecycle/reference/feature_list.md +++ b/test/compatibility_lifecycle/reference/feature_list.md @@ -149,7 +149,6 @@ | NodeInclusionPolicyInPodTopologySpread | :ballot_box_with_check: 1.26+ | :closed_lock_with_key: 1.33+ | 1.25 | 1.26–1.32 | 1.33– | | | [code](https://cs.k8s.io/?q=%5CbNodeInclusionPolicyInPodTopologySpread%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes) [KEPs](https://cs.k8s.io/?q=%5CbNodeInclusionPolicyInPodTopologySpread%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/enhancements) | | NodeLifecycleConditions | | | 1.37– | | | | | [code](https://cs.k8s.io/?q=%5CbNodeLifecycleConditions%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes) [KEPs](https://cs.k8s.io/?q=%5CbNodeLifecycleConditions%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/enhancements) | | NodeLogQuery | :ballot_box_with_check: 1.36+ | :closed_lock_with_key: 1.36+ | 1.27–1.29 | 1.30–1.35 | 1.36– | | | [code](https://cs.k8s.io/?q=%5CbNodeLogQuery%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes) [KEPs](https://cs.k8s.io/?q=%5CbNodeLogQuery%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/enhancements) | -| NodeSwap | :ballot_box_with_check: 1.30+ | :closed_lock_with_key: 1.34+ | 1.22–1.27 | 1.28–1.33 | 1.34– | | | [code](https://cs.k8s.io/?q=%5CbNodeSwap%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes) [KEPs](https://cs.k8s.io/?q=%5CbNodeSwap%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/enhancements) | | NominatedNodeNameForExpectation | :ballot_box_with_check: 1.35+ | | 1.34 | 1.35– | | | | [code](https://cs.k8s.io/?q=%5CbNominatedNodeNameForExpectation%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes) [KEPs](https://cs.k8s.io/?q=%5CbNominatedNodeNameForExpectation%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/enhancements) | | OpenAPIEnums | :ballot_box_with_check: 1.24+ | | 1.23 | 1.24– | | | | [code](https://cs.k8s.io/?q=%5CbOpenAPIEnums%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes) [KEPs](https://cs.k8s.io/?q=%5CbOpenAPIEnums%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/enhancements) | | OpenAPIV2LazyBuild | :ballot_box_with_check: 1.38+ | | | 1.38– | | | | [code](https://cs.k8s.io/?q=%5CbOpenAPIV2LazyBuild%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes) [KEPs](https://cs.k8s.io/?q=%5CbOpenAPIV2LazyBuild%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/enhancements) | diff --git a/test/compatibility_lifecycle/reference/versioned_feature_list.yaml b/test/compatibility_lifecycle/reference/versioned_feature_list.yaml index be849223285..280ed4d7760 100644 --- a/test/compatibility_lifecycle/reference/versioned_feature_list.yaml +++ b/test/compatibility_lifecycle/reference/versioned_feature_list.yaml @@ -1335,24 +1335,6 @@ lockToDefault: true preRelease: GA version: "1.36" -- name: NodeSwap - versionedSpecs: - - default: false - lockToDefault: false - preRelease: Alpha - version: "1.22" - - default: false - lockToDefault: false - preRelease: Beta - version: "1.28" - - default: true - lockToDefault: false - preRelease: Beta - version: "1.30" - - default: true - lockToDefault: true - preRelease: GA - version: "1.34" - name: NominatedNodeNameForExpectation versionedSpecs: - default: false diff --git a/test/e2e_node/kubelet_config_dir_test.go b/test/e2e_node/kubelet_config_dir_test.go index b95504ff0ea..bbfc462380c 100644 --- a/test/e2e_node/kubelet_config_dir_test.go +++ b/test/e2e_node/kubelet_config_dir_test.go @@ -174,12 +174,6 @@ featureGates: } overrides["PodAndContainerStatsFromCRI"] = false overrides["KubeletServiceAccountTokenForCredentialProviders"] = true - - // In some CI jobs, `NodeSwap` is explicitly disabled as the images are cgroupv1 based, - // so such flags should be picked up directly from the initial configuration - if _, ok := initialConfig.FeatureGates["NodeSwap"]; ok { - overrides["NodeSwap"] = initialConfig.FeatureGates["NodeSwap"] - } initialConfig.FeatureGates = overrides // Compare the expected config with the merged config gomega.Expect(initialConfig).To(gomega.BeComparableTo(mergedConfig), "Merged kubelet config does not match the expected configuration.") diff --git a/test/e2e_node/swap_test.go b/test/e2e_node/swap_test.go index 876774182c2..b39ec1a0028 100644 --- a/test/e2e_node/swap_test.go +++ b/test/e2e_node/swap_test.go @@ -38,7 +38,6 @@ import ( "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/rand" - "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/kubelet/types" "k8s.io/kubernetes/test/e2e/framework" e2epod "k8s.io/kubernetes/test/e2e/framework/pod" @@ -62,10 +61,6 @@ var _ = SIGDescribe("Swap", "[LinuxOnly]", ginkgo.Ordered, feature.Swap, framewo addAfterEachForCleaningUpPods(f) f.NamespacePodSecurityLevel = admissionapi.LevelBaseline - ginkgo.BeforeEach(func() { - gomega.Expect(isSwapFeatureGateEnabled()).To(gomega.BeTrueBecause("NodeSwap feature should be on")) - }) - f.Context(framework.WithNodeConformance(), func() { ginkgo.DescribeTable("with configuration", func(qosClass v1.PodQOSClass, memoryRequestEqualLimit bool) { @@ -459,11 +454,6 @@ func runPodAndWaitUntilScheduled(f *framework.Framework, pod *v1.Pod) *v1.Pod { return pod } -func isSwapFeatureGateEnabled() bool { - ginkgo.By("figuring if NodeSwap feature gate is turned on") - return e2eskipper.IsFeatureGateEnabled(features.NodeSwap) -} - func isPodCgroupV2(f *framework.Framework, pod *v1.Pod) bool { ginkgo.By("figuring is test pod runs cgroup v2") output := e2epod.ExecCommandInContainer(f, pod.Name, pod.Spec.Containers[0].Name, "/bin/sh", "-ec", `if test -f "/sys/fs/cgroup/cgroup.controllers"; then echo "true"; else echo "false"; fi`)