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 }