From 996d11d4e877a54306ddeabffe2a64a0d5835aad Mon Sep 17 00:00:00 2001 From: Gaurav Ghildiyal Date: Fri, 23 Feb 2024 12:15:40 -0800 Subject: [PATCH] Add new field trafficDistribution to Service spec --- pkg/apis/core/types.go | 21 +++++++++++++++++++++ pkg/apis/core/validation/validation.go | 19 +++++++++++++++++++ pkg/apis/core/validation/validation_test.go | 12 ++++++++++++ pkg/features/kube_features.go | 9 +++++++++ pkg/registry/core/service/strategy.go | 6 +++++- staging/src/k8s.io/api/core/v1/types.go | 21 +++++++++++++++++++++ 6 files changed, 87 insertions(+), 1 deletion(-) diff --git a/pkg/apis/core/types.go b/pkg/apis/core/types.go index 191b9ff1e02..e10c0b9ecb5 100644 --- a/pkg/apis/core/types.go +++ b/pkg/apis/core/types.go @@ -4163,6 +4163,18 @@ const ( ServiceExternalTrafficPolicyLocal ServiceExternalTrafficPolicy = "Local" ) +// These are valid values for the TrafficDistribution field of a Service. +const ( + // Indicates a preference for routing traffic to endpoints that are + // topologically proximate to the client. The interpretation of "topologically + // proximate" may vary across implementations and could encompass endpoints + // within the same node, rack, zone, or even region. Setting this value gives + // implementations permission to make different tradeoffs, e.g. optimizing for + // proximity rather than equal distribution of load. Users should not set this + // value if such tradeoffs are not acceptable. + ServiceTrafficDistributionPreferClose = "PreferClose" +) + // These are the valid conditions of a service. const ( // LoadBalancerPortsError represents the condition of the requested ports @@ -4426,6 +4438,15 @@ type ServiceSpec struct { // (possibly modified by topology and other features). // +optional InternalTrafficPolicy *ServiceInternalTrafficPolicy + + // TrafficDistribution offers a way to express preferences for how traffic is + // distributed to Service endpoints. Implementations can use this field as a + // hint, but are not required to guarantee strict adherence. If the field is + // not set, the implementation will apply its default routing strategy. If set + // to "PreferClose", implementations should prioritize endpoints that are + // topologically close (e.g., same zone). + // +optional + TrafficDistribution *string } // ServicePort represents the port on which the service is exposed diff --git a/pkg/apis/core/validation/validation.go b/pkg/apis/core/validation/validation.go index 335ea269e2c..0861e2f64fc 100644 --- a/pkg/apis/core/validation/validation.go +++ b/pkg/apis/core/validation/validation.go @@ -5494,6 +5494,9 @@ func ValidateService(service *core.Service) field.ErrorList { // internal traffic policy field allErrs = append(allErrs, validateServiceInternalTrafficFieldsValue(service)...) + // traffic distribution field + allErrs = append(allErrs, validateServiceTrafficDistribution(service)...) + return allErrs } @@ -5611,6 +5614,22 @@ func validateServiceInternalTrafficFieldsValue(service *core.Service) field.Erro return allErrs } +// validateServiceTrafficDistribution validates the values for the +// trafficDistribution field. +func validateServiceTrafficDistribution(service *core.Service) field.ErrorList { + allErrs := field.ErrorList{} + + if service.Spec.TrafficDistribution == nil { + return allErrs + } + + if *service.Spec.TrafficDistribution != v1.ServiceTrafficDistributionPreferClose { + allErrs = append(allErrs, field.NotSupported(field.NewPath("spec").Child("trafficDistribution"), *service.Spec.TrafficDistribution, []string{v1.ServiceTrafficDistributionPreferClose})) + } + + return allErrs +} + // ValidateServiceCreate validates Services as they are created. func ValidateServiceCreate(service *core.Service) field.ErrorList { return ValidateService(service) diff --git a/pkg/apis/core/validation/validation_test.go b/pkg/apis/core/validation/validation_test.go index ef21af8154f..55f1e5b74d8 100644 --- a/pkg/apis/core/validation/validation_test.go +++ b/pkg/apis/core/validation/validation_test.go @@ -16740,6 +16740,18 @@ func TestValidateServiceCreate(t *testing.T) { s.Annotations[core.AnnotationTopologyMode] = "different" }, numErrs: 1, + }, { + name: "valid: trafficDistribution field set to PreferClose", + tweakSvc: func(s *core.Service) { + s.Spec.TrafficDistribution = utilpointer.String("PreferClose") + }, + numErrs: 0, + }, { + name: "invalid: trafficDistribution field set to Random", + tweakSvc: func(s *core.Service) { + s.Spec.TrafficDistribution = utilpointer.String("Random") + }, + numErrs: 1, }, } diff --git a/pkg/features/kube_features.go b/pkg/features/kube_features.go index 94ea3460aa9..109cb6bbd1d 100644 --- a/pkg/features/kube_features.go +++ b/pkg/features/kube_features.go @@ -730,6 +730,13 @@ const ( // Subdivide the NodePort range for dynamic and static port allocation. ServiceNodePortStaticSubrange featuregate.Feature = "ServiceNodePortStaticSubrange" + // owner: @gauravkghildiyal @robscott + // kep: https://kep.k8s.io/4444 + // alpha: v1.30 + // + // Enables trafficDistribution field on Services. + ServiceTrafficDistribution featuregate.Feature = "ServiceTrafficDistribution" + // owner: @gjkim42 @SergeyKanzhelev @matthyx @tzneal // kep: http://kep.k8s.io/753 // alpha: v1.28 @@ -1128,6 +1135,8 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS ServiceNodePortStaticSubrange: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // GA in 1.29; remove in 1.31 + ServiceTrafficDistribution: {Default: false, PreRelease: featuregate.Alpha}, + SidecarContainers: {Default: true, PreRelease: featuregate.Beta}, SizeMemoryBackedVolumes: {Default: true, PreRelease: featuregate.Beta}, diff --git a/pkg/registry/core/service/strategy.go b/pkg/registry/core/service/strategy.go index 4a91778a37f..6d35e6625ea 100644 --- a/pkg/registry/core/service/strategy.go +++ b/pkg/registry/core/service/strategy.go @@ -120,7 +120,11 @@ func (svcStrategy) AllowUnconditionalUpdate() bool { // newSvc.Spec.MyFeature = nil // } func dropServiceDisabledFields(newSvc *api.Service, oldSvc *api.Service) { - + // Drop condition for TrafficDistribution field. + isTrafficDistributionInUse := (oldSvc != nil && oldSvc.Spec.TrafficDistribution != nil) + if !utilfeature.DefaultFeatureGate.Enabled(features.ServiceTrafficDistribution) && !isTrafficDistributionInUse { + newSvc.Spec.TrafficDistribution = nil + } } type serviceStatusStrategy struct { diff --git a/staging/src/k8s.io/api/core/v1/types.go b/staging/src/k8s.io/api/core/v1/types.go index 976c79979b7..13bcd09bc55 100644 --- a/staging/src/k8s.io/api/core/v1/types.go +++ b/staging/src/k8s.io/api/core/v1/types.go @@ -4908,6 +4908,18 @@ const ( ServiceExternalTrafficPolicyTypeCluster = ServiceExternalTrafficPolicyCluster ) +// These are valid values for the TrafficDistribution field of a Service. +const ( + // Indicates a preference for routing traffic to endpoints that are + // topologically proximate to the client. The interpretation of "topologically + // proximate" may vary across implementations and could encompass endpoints + // within the same node, rack, zone, or even region. Setting this value gives + // implementations permission to make different tradeoffs, e.g. optimizing for + // proximity rather than equal distribution of load. Users should not set this + // value if such tradeoffs are not acceptable. + ServiceTrafficDistributionPreferClose = "PreferClose" +) + // These are the valid conditions of a service. const ( // LoadBalancerPortsError represents the condition of the requested ports @@ -5252,6 +5264,15 @@ type ServiceSpec struct { // (possibly modified by topology and other features). // +optional InternalTrafficPolicy *ServiceInternalTrafficPolicy `json:"internalTrafficPolicy,omitempty" protobuf:"bytes,22,opt,name=internalTrafficPolicy"` + + // TrafficDistribution offers a way to express preferences for how traffic is + // distributed to Service endpoints. Implementations can use this field as a + // hint, but are not required to guarantee strict adherence. If the field is + // not set, the implementation will apply its default routing strategy. If set + // to "PreferClose", implementations should prioritize endpoints that are + // topologically close (e.g., same zone). + // +optional + TrafficDistribution *string `json:"trafficDistribution,omitempty" protobuf:"bytes,23,opt,name=trafficDistribution"` } // ServicePort contains information on service's port.