From 80c6507ccef2455346d73bb6f07dac405d9dc48a Mon Sep 17 00:00:00 2001 From: Adrian Moisey Date: Sat, 14 Feb 2026 11:53:55 +0200 Subject: [PATCH] KEP-5707: Deprecate Service.spec.externalIPs --- pkg/api/service/warnings.go | 5 +++++ pkg/api/service/warnings_test.go | 9 ++++++++- pkg/apis/core/types.go | 2 ++ pkg/features/kube_features.go | 14 ++++++++++++++ pkg/proxy/serviceport.go | 6 ++++++ .../reference/feature_list.md | 1 + .../reference/versioned_feature_list.yaml | 6 ++++++ 7 files changed, 42 insertions(+), 1 deletion(-) diff --git a/pkg/api/service/warnings.go b/pkg/api/service/warnings.go index a9f0cdad25f..58903e2dba3 100644 --- a/pkg/api/service/warnings.go +++ b/pkg/api/service/warnings.go @@ -57,6 +57,11 @@ func GetWarningsForService(service, oldService *api.Service) []string { warnings = append(warnings, utilvalidation.GetWarningsForIP(field.NewPath("spec").Child("externalIPs").Index(i), externalIP)...) } + // Warn about using externalIPs + if len(service.Spec.ExternalIPs) > 0 && !isHeadlessService(service) && service.Spec.Type != api.ServiceTypeExternalName { //nolint:staticcheck // SA1019 testing deprecated field + warnings = append(warnings, "spec.externalIPs is deprecated and may no longer be implemented in some clusters") + } + if len(service.Spec.LoadBalancerIP) > 0 { warnings = append(warnings, utilvalidation.GetWarningsForIP(field.NewPath("spec").Child("loadBalancerIP"), service.Spec.LoadBalancerIP)...) } diff --git a/pkg/api/service/warnings_test.go b/pkg/api/service/warnings_test.go index 2eb6f0e84e6..424a47b390b 100644 --- a/pkg/api/service/warnings_test.go +++ b/pkg/api/service/warnings_test.go @@ -47,7 +47,7 @@ func TestGetWarningsForService(t *testing.T) { name: "externalIPs set when type is ExternalName", tweakSvc: func(s *api.Service) { s.Spec.Type = api.ServiceTypeExternalName - s.Spec.ExternalIPs = []string{"1.2.3.4"} + s.Spec.ExternalIPs = []string{"1.2.3.4"} //nolint:staticcheck // SA1019 testing deprecated field }, numWarnings: 1, }, { @@ -115,6 +115,12 @@ func TestGetWarningsForService(t *testing.T) { s.Spec.TrafficDistribution = ptr.To(api.ServiceTrafficDistributionPreferClose) }, numWarnings: 1, + }, { + name: "ExternalIPs set on ClusterIP service", + tweakSvc: func(s *api.Service) { + s.Spec.ExternalIPs = []string{"1.2.3.4"} //nolint:staticcheck // SA1019 testing deprecated field + }, + numWarnings: 1, }} for _, tc := range testCases { @@ -241,6 +247,7 @@ func TestGetWarningsForServiceClusterIPs(t *testing.T) { `spec.clusterIPs[1]: non-standard IP address "192.012.2.2" is invalid: use "192.12.2.2"`, `spec.externalIPs[0]: IPv6 address "2001:db8:1:0::2" should be in RFC 5952 canonical format ("2001:db8:1::2")`, `spec.externalIPs[1]: non-standard IP address "10.012.2.2" is invalid: use "10.12.2.2"`, + `spec.externalIPs is deprecated and may no longer be implemented in some clusters`, `spec.loadBalancerIP: non-standard IP address "10.001.1.1" is invalid: use "10.1.1.1"`, `spec.loadBalancerSourceRanges[0]: IPv6 CIDR value "2001:db8:1:0::/64" should be in RFC 5952 canonical format ("2001:db8:1::/64")`, `spec.loadBalancerSourceRanges[1]: non-standard CIDR value "10.012.2.0/24" is invalid: use "10.12.2.0/24"`, diff --git a/pkg/apis/core/types.go b/pkg/apis/core/types.go index d5fb3eae978..9b5ca5e82a9 100644 --- a/pkg/apis/core/types.go +++ b/pkg/apis/core/types.go @@ -5126,6 +5126,8 @@ type ServiceSpec struct { // ExternalIPs are used by external load balancers, or can be set by // users to handle external traffic that arrives at a node. + // + // Deprecated: ExternalIPs is deprecated and may be removed in a future version. // +optional ExternalIPs []string diff --git a/pkg/features/kube_features.go b/pkg/features/kube_features.go index 3dea0e00bd7..bd89c861d0c 100644 --- a/pkg/features/kube_features.go +++ b/pkg/features/kube_features.go @@ -56,6 +56,14 @@ const ( // Allow spec.terminationGracePeriodSeconds to be overridden by MaxPodGracePeriodSeconds in soft evictions. AllowOverwriteTerminationGracePeriodSeconds featuregate.Feature = "AllowOverwriteTerminationGracePeriodSeconds" + // owner: @adrianmoisey + // kep: https://kep.k8s.io/5707 + // + // When enabled, kube-proxy will program iptables/nftables/ipvs rules for Service.spec.externalIPs. + // When disabled, kube-proxy will not program rules for externalIPs, effectively disabling this + // deprecated feature. + AllowServiceExternalIPs featuregate.Feature = "AllowServiceExternalIPs" + // owner: @bswartz // // Enables usage of any object for volume data source in PVCs @@ -1142,6 +1150,10 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate {Version: version.MustParse("1.35"), Default: false, PreRelease: featuregate.Deprecated, LockToDefault: true}, // remove in 1.38 }, + AllowServiceExternalIPs: { + {Version: version.MustParse("1.0"), Default: true, PreRelease: featuregate.GA}, + }, + AnyVolumeDataSource: { {Version: version.MustParse("1.18"), Default: false, PreRelease: featuregate.Alpha}, {Version: version.MustParse("1.24"), Default: true, PreRelease: featuregate.Beta}, @@ -2174,6 +2186,8 @@ var defaultKubernetesFeatureGateDependencies = map[featuregate.Feature][]feature AllowOverwriteTerminationGracePeriodSeconds: {}, + AllowServiceExternalIPs: {}, + AnyVolumeDataSource: {}, AuthorizeNodeWithSelectors: {genericfeatures.AuthorizeWithSelectors}, diff --git a/pkg/proxy/serviceport.go b/pkg/proxy/serviceport.go index f7f33e0ca57..1ff5de2f11d 100644 --- a/pkg/proxy/serviceport.go +++ b/pkg/proxy/serviceport.go @@ -23,8 +23,10 @@ import ( "strings" v1 "k8s.io/api/core/v1" + utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/klog/v2" apiservice "k8s.io/kubernetes/pkg/api/v1/service" + "k8s.io/kubernetes/pkg/features" proxyutil "k8s.io/kubernetes/pkg/proxy/util" netutils "k8s.io/utils/net" ) @@ -136,6 +138,10 @@ func (bsvcPortInfo *BaseServicePortInfo) NodePort() int { // ExternalIPs is part of ServicePort interface. func (bsvcPortInfo *BaseServicePortInfo) ExternalIPs() []net.IP { + // Only program rules for externalIPs if the feature gate is enabled + if !utilfeature.DefaultFeatureGate.Enabled(features.AllowServiceExternalIPs) { + return []net.IP{} + } return bsvcPortInfo.externalIPs } diff --git a/test/compatibility_lifecycle/reference/feature_list.md b/test/compatibility_lifecycle/reference/feature_list.md index d22917250b5..401846364fa 100644 --- a/test/compatibility_lifecycle/reference/feature_list.md +++ b/test/compatibility_lifecycle/reference/feature_list.md @@ -14,6 +14,7 @@ | AllowInsecureKubeletCertificateSigningRequests | :ballot_box_with_check: 1.0+ | | | | 1.0–1.30 | 1.31– | | [code](https://cs.k8s.io/?q=%5CbAllowInsecureKubeletCertificateSigningRequests%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes) [KEPs](https://cs.k8s.io/?q=%5CbAllowInsecureKubeletCertificateSigningRequests%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/enhancements) | | AllowOverwriteTerminationGracePeriodSeconds | :ballot_box_with_check: 1.0+ | :closed_lock_with_key: 1.35+ | | | 1.0–1.31 | 1.32– | | [code](https://cs.k8s.io/?q=%5CbAllowOverwriteTerminationGracePeriodSeconds%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes) [KEPs](https://cs.k8s.io/?q=%5CbAllowOverwriteTerminationGracePeriodSeconds%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/enhancements) | | AllowParsingUserUIDFromCertAuth | :ballot_box_with_check: 1.33+ | | | 1.33– | | | | [code](https://cs.k8s.io/?q=%5CbAllowParsingUserUIDFromCertAuth%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes) [KEPs](https://cs.k8s.io/?q=%5CbAllowParsingUserUIDFromCertAuth%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/enhancements) | +| AllowServiceExternalIPs | :ballot_box_with_check: 1.0+ | | | | 1.0– | | | [code](https://cs.k8s.io/?q=%5CbAllowServiceExternalIPs%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes) [KEPs](https://cs.k8s.io/?q=%5CbAllowServiceExternalIPs%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/enhancements) | | AllowUnsafeMalformedObjectDeletion | | | 1.32– | | | | | [code](https://cs.k8s.io/?q=%5CbAllowUnsafeMalformedObjectDeletion%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes) [KEPs](https://cs.k8s.io/?q=%5CbAllowUnsafeMalformedObjectDeletion%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/enhancements) | | AnonymousAuthConfigurableEndpoints | :ballot_box_with_check: 1.32+ | :closed_lock_with_key: 1.34+ | 1.31 | 1.32–1.33 | 1.34– | | | [code](https://cs.k8s.io/?q=%5CbAnonymousAuthConfigurableEndpoints%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes) [KEPs](https://cs.k8s.io/?q=%5CbAnonymousAuthConfigurableEndpoints%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/enhancements) | | AnyVolumeDataSource | :ballot_box_with_check: 1.24+ | :closed_lock_with_key: 1.33+ | 1.18–1.23 | 1.24–1.32 | 1.33– | | | [code](https://cs.k8s.io/?q=%5CbAnyVolumeDataSource%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes) [KEPs](https://cs.k8s.io/?q=%5CbAnyVolumeDataSource%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 ad22ce8406b..a2fcc8f12ef 100644 --- a/test/compatibility_lifecycle/reference/versioned_feature_list.yaml +++ b/test/compatibility_lifecycle/reference/versioned_feature_list.yaml @@ -55,6 +55,12 @@ lockToDefault: false preRelease: Beta version: "1.33" +- name: AllowServiceExternalIPs + versionedSpecs: + - default: true + lockToDefault: false + preRelease: GA + version: "1.0" - name: AllowUnsafeMalformedObjectDeletion versionedSpecs: - default: false