mirror of
				https://github.com/k3s-io/kubernetes.git
				synced 2025-10-31 05:40:42 +00:00 
			
		
		
		
	dual stack services (#91824)
* api: structure change * api: defaulting, conversion, and validation * [FIX] validation: auto remove second ip/family when service changes to SingleStack * [FIX] api: defaulting, conversion, and validation * api-server: clusterIPs alloc, printers, storage and strategy * [FIX] clusterIPs default on read * alloc: auto remove second ip/family when service changes to SingleStack * api-server: repair loop handling for clusterIPs * api-server: force kubernetes default service into single stack * api-server: tie dualstack feature flag with endpoint feature flag * controller-manager: feature flag, endpoint, and endpointSlice controllers handling multi family service * [FIX] controller-manager: feature flag, endpoint, and endpointSlicecontrollers handling multi family service * kube-proxy: feature-flag, utils, proxier, and meta proxier * [FIX] kubeproxy: call both proxier at the same time * kubenet: remove forced pod IP sorting * kubectl: modify describe to include ClusterIPs, IPFamilies, and IPFamilyPolicy * e2e: fix tests that depends on IPFamily field AND add dual stack tests * e2e: fix expected error message for ClusterIP immutability * add integration tests for dualstack the third phase of dual stack is a very complex change in the API, basically it introduces Dual Stack services. Main changes are: - It pluralizes the Service IPFamily field to IPFamilies, and removes the singular field. - It introduces a new field IPFamilyPolicyType that can take 3 values to express the "dual-stack(mad)ness" of the cluster: SingleStack, PreferDualStack and RequireDualStack - It pluralizes ClusterIP to ClusterIPs. The goal is to add coverage to the services API operations, taking into account the 6 different modes a cluster can have: - single stack: IP4 or IPv6 (as of today) - dual stack: IPv4 only, IPv6 only, IPv4 - IPv6, IPv6 - IPv4 * [FIX] add integration tests for dualstack * generated data * generated files Co-authored-by: Antonio Ojea <aojea@redhat.com>
This commit is contained in:
		
				
					committed by
					
						 GitHub
						GitHub
					
				
			
			
				
	
			
			
			
						parent
						
							d0e06cf3e0
						
					
				
				
					commit
					6675eba3ef
				
			| @@ -1,110 +0,0 @@ | ||||
| /* | ||||
| Copyright 2019 The Kubernetes Authors. | ||||
|  | ||||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| you may not use this file except in compliance with the License. | ||||
| You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
| Unless required by applicable law or agreed to in writing, software | ||||
| distributed under the License is distributed on an "AS IS" BASIS, | ||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
|  | ||||
| package validation | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"net" | ||||
| 	"strings" | ||||
|  | ||||
| 	"k8s.io/apimachinery/pkg/util/validation/field" | ||||
| 	utilfeature "k8s.io/apiserver/pkg/util/feature" | ||||
| 	api "k8s.io/kubernetes/pkg/apis/core" | ||||
| 	"k8s.io/kubernetes/pkg/features" | ||||
| 	netutils "k8s.io/utils/net" | ||||
| ) | ||||
|  | ||||
| // ValidateConditionalService validates conditionally valid fields. allowedIPFamilies is an ordered | ||||
| // list of the valid IP families (IPv4 or IPv6) that are supported. The first family in the slice | ||||
| // is the cluster default, although the clusterIP here dictates the family defaulting. | ||||
| func ValidateConditionalService(service, oldService *api.Service, allowedIPFamilies []api.IPFamily) field.ErrorList { | ||||
| 	var errs field.ErrorList | ||||
|  | ||||
| 	errs = append(errs, validateIPFamily(service, oldService, allowedIPFamilies)...) | ||||
|  | ||||
| 	return errs | ||||
| } | ||||
|  | ||||
| // validateIPFamily checks the IPFamily field. | ||||
| func validateIPFamily(service, oldService *api.Service, allowedIPFamilies []api.IPFamily) field.ErrorList { | ||||
| 	var errs field.ErrorList | ||||
|  | ||||
| 	// specifically allow an invalid value to remain in storage as long as the user isn't changing it, regardless of gate | ||||
| 	if oldService != nil && oldService.Spec.IPFamily != nil && service.Spec.IPFamily != nil && *oldService.Spec.IPFamily == *service.Spec.IPFamily { | ||||
| 		return errs | ||||
| 	} | ||||
|  | ||||
| 	// If the gate is off, setting or changing IPFamily is not allowed, but clearing it is | ||||
| 	if !utilfeature.DefaultFeatureGate.Enabled(features.IPv6DualStack) { | ||||
| 		if service.Spec.IPFamily != nil { | ||||
| 			if oldService != nil { | ||||
| 				errs = append(errs, ValidateImmutableField(service.Spec.IPFamily, oldService.Spec.IPFamily, field.NewPath("spec", "ipFamily"))...) | ||||
| 			} else { | ||||
| 				errs = append(errs, field.Forbidden(field.NewPath("spec", "ipFamily"), "programmer error, must be cleared when the dual-stack feature gate is off")) | ||||
| 			} | ||||
| 		} | ||||
| 		return errs | ||||
| 	} | ||||
|  | ||||
| 	// PrepareCreate, PrepareUpdate, and test cases must all set IPFamily when the gate is on | ||||
| 	if service.Spec.IPFamily == nil { | ||||
| 		errs = append(errs, field.Required(field.NewPath("spec", "ipFamily"), "programmer error, must be set or defaulted by other fields")) | ||||
| 		return errs | ||||
| 	} | ||||
|  | ||||
| 	// A user is not allowed to change the IPFamily field, except for ExternalName services | ||||
| 	if oldService != nil && oldService.Spec.IPFamily != nil && service.Spec.Type != api.ServiceTypeExternalName { | ||||
| 		errs = append(errs, ValidateImmutableField(service.Spec.IPFamily, oldService.Spec.IPFamily, field.NewPath("spec", "ipFamily"))...) | ||||
| 	} | ||||
|  | ||||
| 	// Verify the IPFamily is one of the allowed families | ||||
| 	desiredFamily := *service.Spec.IPFamily | ||||
| 	if hasIPFamily(allowedIPFamilies, desiredFamily) { | ||||
| 		// the IP family is one of the allowed families, verify that it matches cluster IP | ||||
| 		switch ip := net.ParseIP(service.Spec.ClusterIP); { | ||||
| 		case ip == nil: | ||||
| 			// do not need to check anything | ||||
| 		case netutils.IsIPv6(ip) && desiredFamily != api.IPv6Protocol: | ||||
| 			errs = append(errs, field.Invalid(field.NewPath("spec", "ipFamily"), *service.Spec.IPFamily, "does not match IPv6 cluster IP")) | ||||
| 		case !netutils.IsIPv6(ip) && desiredFamily != api.IPv4Protocol: | ||||
| 			errs = append(errs, field.Invalid(field.NewPath("spec", "ipFamily"), *service.Spec.IPFamily, "does not match IPv4 cluster IP")) | ||||
| 		} | ||||
| 	} else { | ||||
| 		errs = append(errs, field.Invalid(field.NewPath("spec", "ipFamily"), desiredFamily, fmt.Sprintf("only the following families are allowed: %s", joinIPFamilies(allowedIPFamilies, ", ")))) | ||||
| 	} | ||||
| 	return errs | ||||
| } | ||||
|  | ||||
| func hasIPFamily(families []api.IPFamily, family api.IPFamily) bool { | ||||
| 	for _, allow := range families { | ||||
| 		if allow == family { | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| func joinIPFamilies(families []api.IPFamily, separator string) string { | ||||
| 	var b strings.Builder | ||||
| 	for i, family := range families { | ||||
| 		if i != 0 { | ||||
| 			b.WriteString(separator) | ||||
| 		} | ||||
| 		b.WriteString(string(family)) | ||||
| 	} | ||||
| 	return b.String() | ||||
| } | ||||
		Reference in New Issue
	
	Block a user