istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/serviceregistry/kube/controller/ambient/services.go (about)

     1  // Copyright Istio Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // nolint: gocritic
    16  package ambient
    17  
    18  import (
    19  	v1 "k8s.io/api/core/v1"
    20  
    21  	networkingclient "istio.io/client-go/pkg/apis/networking/v1alpha3"
    22  	"istio.io/istio/pilot/pkg/model"
    23  	"istio.io/istio/pilot/pkg/serviceregistry/kube"
    24  	"istio.io/istio/pkg/config"
    25  	"istio.io/istio/pkg/config/constants"
    26  	"istio.io/istio/pkg/config/schema/kind"
    27  	"istio.io/istio/pkg/kube/krt"
    28  	"istio.io/istio/pkg/log"
    29  	"istio.io/istio/pkg/slices"
    30  	"istio.io/istio/pkg/workloadapi"
    31  )
    32  
    33  func (a *index) ServicesCollection(
    34  	Services krt.Collection[*v1.Service],
    35  	ServiceEntries krt.Collection[*networkingclient.ServiceEntry],
    36  	Waypoints krt.Collection[Waypoint],
    37  	Namespaces krt.Collection[*v1.Namespace],
    38  ) krt.Collection[model.ServiceInfo] {
    39  	ServicesInfo := krt.NewCollection(Services, func(ctx krt.HandlerContext, s *v1.Service) *model.ServiceInfo {
    40  		portNames := map[int32]model.ServicePortName{}
    41  		for _, p := range s.Spec.Ports {
    42  			portNames[p.Port] = model.ServicePortName{
    43  				PortName:       p.Name,
    44  				TargetPortName: p.TargetPort.StrVal,
    45  			}
    46  		}
    47  		waypointKey := ""
    48  		waypoint := fetchWaypointForService(ctx, Waypoints, Namespaces, s.ObjectMeta)
    49  		if waypoint != nil {
    50  			waypointKey = waypoint.ResourceName()
    51  		}
    52  		a.networkUpdateTrigger.MarkDependant(ctx) // Mark we depend on out of band a.Network
    53  		return &model.ServiceInfo{
    54  			Service:       a.constructService(s, waypoint),
    55  			PortNames:     portNames,
    56  			LabelSelector: model.NewSelector(s.Spec.Selector),
    57  			Source:        kind.Service,
    58  			Waypoint:      waypointKey,
    59  		}
    60  	}, krt.WithName("ServicesInfo"))
    61  	ServiceEntriesInfo := krt.NewManyCollection(ServiceEntries, func(ctx krt.HandlerContext, s *networkingclient.ServiceEntry) []model.ServiceInfo {
    62  		waypoint := fetchWaypointForService(ctx, Waypoints, Namespaces, s.ObjectMeta)
    63  		a.networkUpdateTrigger.MarkDependant(ctx) // Mark we depend on out of band a.Network
    64  		return a.serviceEntriesInfo(s, waypoint)
    65  	}, krt.WithName("ServiceEntriesInfo"))
    66  	WorkloadServices := krt.JoinCollection([]krt.Collection[model.ServiceInfo]{ServicesInfo, ServiceEntriesInfo}, krt.WithName("WorkloadServices"))
    67  	// workloadapi services NOT workloads x services somehow
    68  	return WorkloadServices
    69  }
    70  
    71  func (a *index) serviceEntriesInfo(s *networkingclient.ServiceEntry, w *Waypoint) []model.ServiceInfo {
    72  	sel := model.NewSelector(s.Spec.GetWorkloadSelector().GetLabels())
    73  	portNames := map[int32]model.ServicePortName{}
    74  	for _, p := range s.Spec.Ports {
    75  		portNames[int32(p.Number)] = model.ServicePortName{
    76  			PortName: p.Name,
    77  		}
    78  	}
    79  	waypointKey := ""
    80  	if w != nil {
    81  		waypointKey = w.ResourceName()
    82  	}
    83  	return slices.Map(a.constructServiceEntries(s, w), func(e *workloadapi.Service) model.ServiceInfo {
    84  		return model.ServiceInfo{
    85  			Service:       e,
    86  			PortNames:     portNames,
    87  			LabelSelector: sel,
    88  			Source:        kind.ServiceEntry,
    89  			Waypoint:      waypointKey,
    90  		}
    91  	})
    92  }
    93  
    94  func (a *index) constructServiceEntries(svc *networkingclient.ServiceEntry, w *Waypoint) []*workloadapi.Service {
    95  	addresses, err := slices.MapErr(svc.Spec.Addresses, a.toNetworkAddressFromCidr)
    96  	if err != nil {
    97  		// TODO: perhaps we should support CIDR in the future?
    98  		return nil
    99  	}
   100  	ports := make([]*workloadapi.Port, 0, len(svc.Spec.Ports))
   101  	for _, p := range svc.Spec.Ports {
   102  		ports = append(ports, &workloadapi.Port{
   103  			ServicePort: p.Number,
   104  			TargetPort:  p.TargetPort,
   105  		})
   106  	}
   107  
   108  	// handle svc waypoint scenario
   109  	var waypointAddress *workloadapi.GatewayAddress
   110  	if w != nil {
   111  		waypointAddress = a.getWaypointAddress(w)
   112  	}
   113  
   114  	// TODO this is only checking one controller - we may be missing service vips for instances in another cluster
   115  	res := make([]*workloadapi.Service, 0, len(svc.Spec.Hosts))
   116  	for _, h := range svc.Spec.Hosts {
   117  		res = append(res, &workloadapi.Service{
   118  			Name:      svc.Name,
   119  			Namespace: svc.Namespace,
   120  			Hostname:  h,
   121  			Addresses: addresses,
   122  			Ports:     ports,
   123  			Waypoint:  waypointAddress,
   124  		})
   125  	}
   126  	return res
   127  }
   128  
   129  func (a *index) constructService(svc *v1.Service, w *Waypoint) *workloadapi.Service {
   130  	ports := make([]*workloadapi.Port, 0, len(svc.Spec.Ports))
   131  	for _, p := range svc.Spec.Ports {
   132  		ports = append(ports, &workloadapi.Port{
   133  			ServicePort: uint32(p.Port),
   134  			TargetPort:  uint32(p.TargetPort.IntVal),
   135  		})
   136  	}
   137  
   138  	addresses, err := slices.MapErr(getVIPs(svc), a.toNetworkAddress)
   139  	if err != nil {
   140  		log.Warnf("fail to parse service %v: %v", config.NamespacedName(svc), err)
   141  		return nil
   142  	}
   143  	// handle svc waypoint scenario
   144  	var waypointAddress *workloadapi.GatewayAddress
   145  	if w != nil {
   146  		waypointAddress = a.getWaypointAddress(w)
   147  	}
   148  
   149  	var lb *workloadapi.LoadBalancing
   150  	if svc.Spec.TrafficDistribution != nil && *svc.Spec.TrafficDistribution == v1.ServiceTrafficDistributionPreferClose {
   151  		lb = &workloadapi.LoadBalancing{
   152  			// Prefer endpoints in close zones, but allow spilling over to further endpoints where required.
   153  			RoutingPreference: []workloadapi.LoadBalancing_Scope{
   154  				workloadapi.LoadBalancing_NETWORK,
   155  				workloadapi.LoadBalancing_REGION,
   156  				workloadapi.LoadBalancing_ZONE,
   157  				workloadapi.LoadBalancing_SUBZONE,
   158  			},
   159  			Mode: workloadapi.LoadBalancing_FAILOVER,
   160  		}
   161  	}
   162  	if svc.Labels[constants.ManagedGatewayLabel] == constants.ManagedGatewayMeshControllerLabel {
   163  		// This is waypoint. Enable locality routing
   164  		lb = &workloadapi.LoadBalancing{
   165  			// Prefer endpoints in close zones, but allow spilling over to further endpoints where required.
   166  			RoutingPreference: []workloadapi.LoadBalancing_Scope{
   167  				workloadapi.LoadBalancing_NETWORK,
   168  				workloadapi.LoadBalancing_REGION,
   169  				workloadapi.LoadBalancing_ZONE,
   170  				workloadapi.LoadBalancing_SUBZONE,
   171  			},
   172  			Mode: workloadapi.LoadBalancing_FAILOVER,
   173  		}
   174  	}
   175  	if itp := svc.Spec.InternalTrafficPolicy; itp != nil && *itp == v1.ServiceInternalTrafficPolicyLocal {
   176  		lb = &workloadapi.LoadBalancing{
   177  			// Only allow endpoints on the same node.
   178  			RoutingPreference: []workloadapi.LoadBalancing_Scope{
   179  				workloadapi.LoadBalancing_NODE,
   180  			},
   181  			Mode: workloadapi.LoadBalancing_STRICT,
   182  		}
   183  	}
   184  	// TODO this is only checking one controller - we may be missing service vips for instances in another cluster
   185  	return &workloadapi.Service{
   186  		Name:          svc.Name,
   187  		Namespace:     svc.Namespace,
   188  		Hostname:      string(kube.ServiceHostname(svc.Name, svc.Namespace, a.DomainSuffix)),
   189  		Addresses:     addresses,
   190  		Ports:         ports,
   191  		Waypoint:      waypointAddress,
   192  		LoadBalancing: lb,
   193  	}
   194  }
   195  
   196  func getVIPs(svc *v1.Service) []string {
   197  	res := []string{}
   198  	if svc.Spec.ClusterIP != "" && svc.Spec.ClusterIP != v1.ClusterIPNone {
   199  		res = append(res, svc.Spec.ClusterIP)
   200  	}
   201  	for _, ing := range svc.Status.LoadBalancer.Ingress {
   202  		// IPs are strictly optional for loadbalancers - they may just have a hostname.
   203  		if ing.IP != "" {
   204  			res = append(res, ing.IP)
   205  		}
   206  	}
   207  	return res
   208  }