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
parent
d0e06cf3e0
commit
6675eba3ef
@@ -37,8 +37,8 @@ import (
|
||||
utilnet "k8s.io/utils/net"
|
||||
)
|
||||
|
||||
// podToEndpoint returns an Endpoint object generated from pod, node, and service.
|
||||
func podToEndpoint(pod *corev1.Pod, node *corev1.Node, service *corev1.Service) discovery.Endpoint {
|
||||
// podToEndpoint returns an Endpoint object generated from a Pod, a Node, and a Service for a particular addressType.
|
||||
func podToEndpoint(pod *corev1.Pod, node *corev1.Node, service *corev1.Service, addressType discovery.AddressType) discovery.Endpoint {
|
||||
// Build out topology information. This is currently limited to hostname,
|
||||
// zone, and region, but this will be expanded in the future.
|
||||
topology := map[string]string{}
|
||||
@@ -62,7 +62,7 @@ func podToEndpoint(pod *corev1.Pod, node *corev1.Node, service *corev1.Service)
|
||||
|
||||
ready := service.Spec.PublishNotReadyAddresses || podutil.IsPodReady(pod)
|
||||
ep := discovery.Endpoint{
|
||||
Addresses: getEndpointAddresses(pod.Status, service),
|
||||
Addresses: getEndpointAddresses(pod.Status, service, addressType),
|
||||
Conditions: discovery.EndpointConditions{
|
||||
Ready: &ready,
|
||||
},
|
||||
@@ -117,12 +117,16 @@ func getEndpointPorts(service *corev1.Service, pod *corev1.Pod) []discovery.Endp
|
||||
}
|
||||
|
||||
// getEndpointAddresses returns a list of addresses generated from a pod status.
|
||||
func getEndpointAddresses(podStatus corev1.PodStatus, service *corev1.Service) []string {
|
||||
func getEndpointAddresses(podStatus corev1.PodStatus, service *corev1.Service, addressType discovery.AddressType) []string {
|
||||
addresses := []string{}
|
||||
|
||||
for _, podIP := range podStatus.PodIPs {
|
||||
isIPv6PodIP := utilnet.IsIPv6String(podIP.IP)
|
||||
if isIPv6PodIP == endpointutil.IsIPv6Service(service) {
|
||||
if isIPv6PodIP && addressType == discovery.AddressTypeIPv6 {
|
||||
addresses = append(addresses, podIP.IP)
|
||||
}
|
||||
|
||||
if !isIPv6PodIP && addressType == discovery.AddressTypeIPv4 {
|
||||
addresses = append(addresses, podIP.IP)
|
||||
}
|
||||
}
|
||||
@@ -346,3 +350,62 @@ func (sl endpointSliceEndpointLen) Swap(i, j int) { sl[i], sl[j] = sl[j], sl[i]
|
||||
func (sl endpointSliceEndpointLen) Less(i, j int) bool {
|
||||
return len(sl[i].Endpoints) > len(sl[j].Endpoints)
|
||||
}
|
||||
|
||||
// returns a map of address types used by a service
|
||||
func getAddressTypesForService(service *corev1.Service) map[discovery.AddressType]struct{} {
|
||||
serviceSupportedAddresses := make(map[discovery.AddressType]struct{})
|
||||
// TODO: (khenidak) when address types are removed in favor of
|
||||
// v1.IPFamily this will need to be removed, and work directly with
|
||||
// v1.IPFamily types
|
||||
|
||||
// IMPORTANT: we assume that IP of (discovery.AddressType enum) is never in use
|
||||
// as it gets deprecated
|
||||
for _, family := range service.Spec.IPFamilies {
|
||||
if family == corev1.IPv4Protocol {
|
||||
serviceSupportedAddresses[discovery.AddressTypeIPv4] = struct{}{}
|
||||
}
|
||||
|
||||
if family == corev1.IPv6Protocol {
|
||||
serviceSupportedAddresses[discovery.AddressTypeIPv6] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
if len(serviceSupportedAddresses) > 0 {
|
||||
return serviceSupportedAddresses // we have found families for this service
|
||||
}
|
||||
|
||||
// TODO (khenidak) remove when (1) dual stack becomes
|
||||
// enabled by default (2) v1.19 falls off supported versions
|
||||
|
||||
// Why do we need this:
|
||||
// a cluster being upgraded to the new apis
|
||||
// will have service.spec.IPFamilies: nil
|
||||
// if the controller manager connected to old api
|
||||
// server. This will have the nasty side effect of
|
||||
// removing all slices already created for this service.
|
||||
// this will disable all routing to service vip (ClusterIP)
|
||||
// this ensures that this does not happen. Same for headless services
|
||||
// we assume it is dual stack, until they get defaulted by *new* api-server
|
||||
// this ensures that traffic is not disrupted until then. But *may*
|
||||
// include undesired families for headless services until then.
|
||||
|
||||
if len(service.Spec.ClusterIP) > 0 && service.Spec.ClusterIP != corev1.ClusterIPNone { // headfull
|
||||
addrType := discovery.AddressTypeIPv4
|
||||
if utilnet.IsIPv6String(service.Spec.ClusterIP) {
|
||||
addrType = discovery.AddressTypeIPv6
|
||||
}
|
||||
serviceSupportedAddresses[addrType] = struct{}{}
|
||||
klog.V(2).Infof("couldn't find ipfamilies for headless service: %v/%v. This could happen if controller manager is connected to an old apiserver that does not support ip families yet. EndpointSlices for this Service will use %s as the IP Family based on familyOf(ClusterIP:%v).", service.Namespace, service.Name, addrType, service.Spec.ClusterIP)
|
||||
return serviceSupportedAddresses
|
||||
}
|
||||
|
||||
// headless
|
||||
// for now we assume two families. This should have minimal side effect
|
||||
// if the service is headless with no selector, then this will remain the case
|
||||
// if the service is headless with selector then chances are pods are still using single family
|
||||
// since kubelet will need to restart in order to start patching pod status with multiple ips
|
||||
serviceSupportedAddresses[discovery.AddressTypeIPv4] = struct{}{}
|
||||
serviceSupportedAddresses[discovery.AddressTypeIPv6] = struct{}{}
|
||||
klog.V(2).Infof("couldn't find ipfamilies for headless service: %v/%v likely because controller manager is likely connected to an old apiserver that does not support ip families yet. The service endpoint slice will use dual stack families until api-server default it correctly", service.Namespace, service.Name)
|
||||
return serviceSupportedAddresses
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user