istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/serviceregistry/kube/controller/ambient/ambientindex.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 package ambient 16 17 import ( 18 "net/netip" 19 "strings" 20 21 v1 "k8s.io/api/core/v1" 22 "sigs.k8s.io/gateway-api/apis/v1beta1" 23 24 networkingclient "istio.io/client-go/pkg/apis/networking/v1alpha3" 25 securityclient "istio.io/client-go/pkg/apis/security/v1beta1" 26 "istio.io/istio/pilot/pkg/model" 27 "istio.io/istio/pkg/cluster" 28 "istio.io/istio/pkg/config/constants" 29 "istio.io/istio/pkg/config/labels" 30 "istio.io/istio/pkg/config/schema/gvr" 31 "istio.io/istio/pkg/config/schema/kind" 32 kubeclient "istio.io/istio/pkg/kube" 33 "istio.io/istio/pkg/kube/kclient" 34 "istio.io/istio/pkg/kube/krt" 35 "istio.io/istio/pkg/kube/kubetypes" 36 "istio.io/istio/pkg/log" 37 "istio.io/istio/pkg/maps" 38 "istio.io/istio/pkg/network" 39 "istio.io/istio/pkg/slices" 40 "istio.io/istio/pkg/util/sets" 41 "istio.io/istio/pkg/workloadapi" 42 ) 43 44 type Index interface { 45 Lookup(key string) []model.AddressInfo 46 All() []model.AddressInfo 47 WorkloadsForWaypoint(key model.WaypointKey) []model.WorkloadInfo 48 ServicesForWaypoint(key model.WaypointKey) []model.ServiceInfo 49 SyncAll() 50 HasSynced() bool 51 model.AmbientIndexes 52 } 53 54 var _ Index = &index{} 55 56 type workloadsCollection struct { 57 krt.Collection[model.WorkloadInfo] 58 ByAddress *krt.Index[model.WorkloadInfo, networkAddress] 59 ByServiceKey *krt.Index[model.WorkloadInfo, string] 60 ByOwningWaypoint *krt.Index[model.WorkloadInfo, networkAddress] 61 } 62 63 type waypointsCollection struct { 64 krt.Collection[Waypoint] 65 } 66 67 type servicesCollection struct { 68 krt.Collection[model.ServiceInfo] 69 ByAddress *krt.Index[model.ServiceInfo, networkAddress] 70 ByOwningWaypoint *krt.Index[model.ServiceInfo, networkAddress] 71 } 72 73 // index maintains an index of ambient WorkloadInfo objects by various keys. 74 // These are intentionally pre-computed based on events such that lookups are efficient. 75 type index struct { 76 services servicesCollection 77 workloads workloadsCollection 78 waypoints waypointsCollection 79 80 authorizationPolicies krt.Collection[model.WorkloadAuthorization] 81 networkUpdateTrigger *krt.RecomputeTrigger 82 83 SystemNamespace string 84 DomainSuffix string 85 ClusterID cluster.ID 86 XDSUpdater model.XDSUpdater 87 Network LookupNetwork 88 } 89 90 type Options struct { 91 Client kubeclient.Client 92 93 Revision string 94 SystemNamespace string 95 DomainSuffix string 96 ClusterID cluster.ID 97 XDSUpdater model.XDSUpdater 98 LookupNetwork LookupNetwork 99 } 100 101 func New(options Options) Index { 102 a := &index{ 103 networkUpdateTrigger: krt.NewRecomputeTrigger(), 104 105 SystemNamespace: options.SystemNamespace, 106 DomainSuffix: options.DomainSuffix, 107 ClusterID: options.ClusterID, 108 XDSUpdater: options.XDSUpdater, 109 Network: options.LookupNetwork, 110 } 111 112 filter := kclient.Filter{ 113 ObjectFilter: options.Client.ObjectFilter(), 114 } 115 ConfigMaps := krt.NewInformerFiltered[*v1.ConfigMap](options.Client, filter, krt.WithName("ConfigMaps")) 116 117 authzPolicies := kclient.NewDelayedInformer[*securityclient.AuthorizationPolicy](options.Client, 118 gvr.AuthorizationPolicy, kubetypes.StandardInformer, filter) 119 AuthzPolicies := krt.WrapClient[*securityclient.AuthorizationPolicy](authzPolicies, krt.WithName("AuthorizationPolicies")) 120 121 peerAuths := kclient.NewDelayedInformer[*securityclient.PeerAuthentication](options.Client, 122 gvr.PeerAuthentication, kubetypes.StandardInformer, filter) 123 PeerAuths := krt.WrapClient[*securityclient.PeerAuthentication](peerAuths, krt.WithName("PeerAuthentications")) 124 125 serviceEntries := kclient.NewDelayedInformer[*networkingclient.ServiceEntry](options.Client, 126 gvr.ServiceEntry, kubetypes.StandardInformer, filter) 127 ServiceEntries := krt.WrapClient[*networkingclient.ServiceEntry](serviceEntries, krt.WithName("ServiceEntries")) 128 129 workloadEntries := kclient.NewDelayedInformer[*networkingclient.WorkloadEntry](options.Client, 130 gvr.WorkloadEntry, kubetypes.StandardInformer, filter) 131 WorkloadEntries := krt.WrapClient[*networkingclient.WorkloadEntry](workloadEntries, krt.WithName("WorkloadEntries")) 132 133 gatewayClient := kclient.NewDelayedInformer[*v1beta1.Gateway](options.Client, gvr.KubernetesGateway, kubetypes.StandardInformer, filter) 134 Gateways := krt.WrapClient[*v1beta1.Gateway](gatewayClient, krt.WithName("Gateways")) 135 136 gatewayClassClient := kclient.NewDelayedInformer[*v1beta1.GatewayClass](options.Client, gvr.GatewayClass, kubetypes.StandardInformer, filter) 137 GatewayClasses := krt.WrapClient[*v1beta1.GatewayClass](gatewayClassClient, krt.WithName("GatewayClasses")) 138 139 Services := krt.NewInformerFiltered[*v1.Service](options.Client, filter, krt.WithName("Services")) 140 Nodes := krt.NewInformerFiltered[*v1.Node](options.Client, kclient.Filter{ 141 ObjectFilter: options.Client.ObjectFilter(), 142 ObjectTransform: kubeclient.StripNodeUnusedFields, 143 }, krt.WithName("Nodes")) 144 Pods := krt.NewInformerFiltered[*v1.Pod](options.Client, kclient.Filter{ 145 ObjectFilter: options.Client.ObjectFilter(), 146 ObjectTransform: kubeclient.StripPodUnusedFields, 147 }, krt.WithName("Pods")) 148 149 // TODO: Should this go ahead and transform the full ns into some intermediary with just the details we care about? 150 Namespaces := krt.NewInformer[*v1.Namespace](options.Client, krt.WithName("Namespaces")) 151 152 MeshConfig := MeshConfigCollection(ConfigMaps, options) 153 Waypoints := WaypointsCollection(Gateways, GatewayClasses, Pods) 154 155 // AllPolicies includes peer-authentication converted policies 156 AuthorizationPolicies, AllPolicies := PolicyCollections(AuthzPolicies, PeerAuths, MeshConfig, Waypoints, Pods) 157 AllPolicies.RegisterBatch(PushXds(a.XDSUpdater, func(i model.WorkloadAuthorization) model.ConfigKey { 158 return model.ConfigKey{Kind: kind.AuthorizationPolicy, Name: i.Authorization.Name, Namespace: i.Authorization.Namespace} 159 }), false) 160 161 // these are workloadapi-style services combined from kube services and service entries 162 WorkloadServices := a.ServicesCollection(Services, ServiceEntries, Waypoints, Namespaces) 163 ServiceAddressIndex := krt.NewIndex[model.ServiceInfo, networkAddress](WorkloadServices, networkAddressFromService) 164 ServiceInfosByOwningWaypoint := krt.NewIndex[model.ServiceInfo, networkAddress](WorkloadServices, func(s model.ServiceInfo) []networkAddress { 165 // Filter out waypoint services 166 if s.Labels[constants.ManagedGatewayLabel] == constants.ManagedGatewayMeshControllerLabel { 167 return nil 168 } 169 waypoint := s.Service.Waypoint 170 if waypoint == nil { 171 return nil 172 } 173 waypointAddress := waypoint.GetAddress() 174 if waypointAddress == nil { 175 return nil 176 } 177 178 ip := waypointAddress.GetAddress() 179 netip, _ := netip.AddrFromSlice(ip) 180 netaddr := networkAddress{ 181 network: waypointAddress.GetNetwork(), 182 ip: netip.String(), 183 } 184 return append(make([]networkAddress, 1), netaddr) 185 }) 186 WorkloadServices.RegisterBatch(krt.BatchedEventFilter( 187 func(a model.ServiceInfo) *workloadapi.Service { 188 // Only trigger push if the XDS object changed; the rest is just for computation of others 189 return a.Service 190 }, 191 PushXds(a.XDSUpdater, func(i model.ServiceInfo) model.ConfigKey { 192 return model.ConfigKey{Kind: kind.Address, Name: i.ResourceName()} 193 })), false) 194 195 Workloads := a.WorkloadsCollection( 196 Pods, 197 Nodes, 198 MeshConfig, 199 AuthorizationPolicies, 200 PeerAuths, 201 Waypoints, 202 WorkloadServices, 203 WorkloadEntries, 204 ServiceEntries, 205 AllPolicies, 206 Namespaces, 207 ) 208 WorkloadAddressIndex := krt.NewIndex[model.WorkloadInfo, networkAddress](Workloads, networkAddressFromWorkload) 209 WorkloadServiceIndex := krt.NewIndex[model.WorkloadInfo, string](Workloads, func(o model.WorkloadInfo) []string { 210 return maps.Keys(o.Services) 211 }) 212 WorkloadWaypointIndex := krt.NewIndex[model.WorkloadInfo, networkAddress](Workloads, func(w model.WorkloadInfo) []networkAddress { 213 // Filter out waypoints. 214 if w.Labels[constants.ManagedGatewayLabel] == constants.ManagedGatewayMeshControllerLabel { 215 return nil 216 } 217 waypoint := w.Waypoint 218 if waypoint == nil { 219 return nil 220 } 221 waypointAddress := waypoint.GetAddress() 222 if waypointAddress == nil { 223 return nil 224 } 225 226 ip := waypointAddress.GetAddress() 227 netip, _ := netip.AddrFromSlice(ip) 228 netaddr := networkAddress{ 229 network: waypointAddress.GetNetwork(), 230 ip: netip.String(), 231 } 232 return append(make([]networkAddress, 1), netaddr) 233 }) 234 // Subtle: make sure we register the event after the Index are created. This ensures when we get the event, the index is populated. 235 Workloads.RegisterBatch(krt.BatchedEventFilter( 236 func(a model.WorkloadInfo) *workloadapi.Workload { 237 // Only trigger push if the XDS object changed; the rest is just for computation of others 238 return a.Workload 239 }, 240 PushXds(a.XDSUpdater, func(i model.WorkloadInfo) model.ConfigKey { 241 return model.ConfigKey{Kind: kind.Address, Name: i.ResourceName()} 242 })), false) 243 244 a.workloads = workloadsCollection{ 245 Collection: Workloads, 246 ByAddress: WorkloadAddressIndex, 247 ByServiceKey: WorkloadServiceIndex, 248 ByOwningWaypoint: WorkloadWaypointIndex, 249 } 250 a.services = servicesCollection{ 251 Collection: WorkloadServices, 252 ByAddress: ServiceAddressIndex, 253 ByOwningWaypoint: ServiceInfosByOwningWaypoint, 254 } 255 a.waypoints = waypointsCollection{ 256 Collection: Waypoints, 257 } 258 a.authorizationPolicies = AllPolicies 259 260 return a 261 } 262 263 // Lookup finds all addresses associated with a given key. Many different key formats are supported; see inline comments. 264 func (a *index) Lookup(key string) []model.AddressInfo { 265 // 1. Workload UID 266 if w := a.workloads.GetKey(krt.Key[model.WorkloadInfo](key)); w != nil { 267 return []model.AddressInfo{workloadToAddressInfo(w.Workload)} 268 } 269 270 network, ip, found := strings.Cut(key, "/") 271 if !found { 272 log.Warnf(`key (%v) did not contain the expected "/" character`, key) 273 return nil 274 } 275 networkAddr := networkAddress{network: network, ip: ip} 276 277 // 2. Workload by IP 278 if wls := a.workloads.ByAddress.Lookup(networkAddr); len(wls) > 0 { 279 // If there is just one, return it 280 if len(wls) == 1 { 281 return []model.AddressInfo{modelWorkloadToAddressInfo(wls[0])} 282 } 283 // Otherwise, try to find a pod - pods have precedence 284 pod := slices.FindFunc(wls, func(info model.WorkloadInfo) bool { 285 return info.Source == kind.Pod 286 }) 287 if pod != nil { 288 return []model.AddressInfo{modelWorkloadToAddressInfo(*pod)} 289 } 290 // Otherwise just return the first one; all WorkloadEntry have the same weight 291 return []model.AddressInfo{modelWorkloadToAddressInfo(wls[0])} 292 } 293 294 // 3. Service 295 if svc := a.lookupService(key); svc != nil { 296 res := []model.AddressInfo{serviceToAddressInfo(svc.Service)} 297 for _, w := range a.workloads.ByServiceKey.Lookup(svc.ResourceName()) { 298 res = append(res, workloadToAddressInfo(w.Workload)) 299 } 300 return res 301 } 302 return nil 303 } 304 305 func (a *index) lookupService(key string) *model.ServiceInfo { 306 // 1. namespace/hostname format 307 s := a.services.GetKey(krt.Key[model.ServiceInfo](key)) 308 if s != nil { 309 return s 310 } 311 312 // 2. network/ip format 313 network, ip, _ := strings.Cut(key, "/") 314 services := a.services.ByAddress.Lookup(networkAddress{ 315 network: network, 316 ip: ip, 317 }) 318 return slices.First(services) 319 } 320 321 // All return all known workloads. Result is un-ordered 322 func (a *index) All() []model.AddressInfo { 323 res := []model.AddressInfo{} 324 type kindindex struct { 325 k kind.Kind 326 index int 327 } 328 addrm := map[netip.Addr]kindindex{} 329 for _, wl := range a.workloads.List() { 330 overwrite := -1 331 write := true 332 for _, addr := range wl.Addresses { 333 a := byteIPToAddr(addr) 334 if existing, f := addrm[a]; f { 335 // This address was already found. We want unique addresses in the result. 336 // Pod > WorkloadEntry 337 if wl.Source == kind.Pod && existing.k != kind.Pod { 338 overwrite = existing.index 339 addrm[a] = kindindex{ 340 k: wl.Source, 341 index: overwrite, 342 } 343 } else { 344 write = false 345 } 346 } 347 } 348 if overwrite >= 0 { 349 res[overwrite] = workloadToAddressInfo(wl.Workload) 350 } else if write { 351 res = append(res, workloadToAddressInfo(wl.Workload)) 352 for _, addr := range wl.Addresses { 353 a := byteIPToAddr(addr) 354 addrm[a] = kindindex{ 355 k: wl.Source, 356 index: overwrite, 357 } 358 } 359 } 360 } 361 for _, s := range a.services.List() { 362 res = append(res, serviceToAddressInfo(s.Service)) 363 } 364 return res 365 } 366 367 // AddressInformation returns all AddressInfo's in the cluster. 368 // This may be scoped to specific subsets by specifying a non-empty addresses field 369 func (a *index) AddressInformation(addresses sets.String) ([]model.AddressInfo, sets.String) { 370 if len(addresses) == 0 { 371 // Full update 372 return a.All(), nil 373 } 374 var res []model.AddressInfo 375 var removed []string 376 got := sets.New[string]() 377 for wname := range addresses { 378 wl := a.Lookup(wname) 379 if len(wl) == 0 { 380 removed = append(removed, wname) 381 } else { 382 for _, addr := range wl { 383 if !got.InsertContains(addr.ResourceName()) { 384 res = append(res, addr) 385 } 386 } 387 } 388 } 389 return res, sets.New(removed...) 390 } 391 392 func (a *index) ServicesForWaypoint(key model.WaypointKey) []model.ServiceInfo { 393 var out []model.ServiceInfo 394 for _, addr := range key.Addresses { 395 out = append(out, a.services.ByOwningWaypoint.Lookup(networkAddress{ 396 network: key.Network, 397 ip: addr, 398 })...) 399 } 400 return out 401 } 402 403 func (a *index) WorkloadsForWaypoint(key model.WaypointKey) []model.WorkloadInfo { 404 // TODO: we should be able to handle multiple IPs or a hostname 405 if len(key.Addresses) == 0 { 406 return nil 407 } 408 workloads := a.workloads.ByOwningWaypoint.Lookup(networkAddress{ 409 network: key.Network, 410 ip: key.Addresses[0], 411 }) 412 workloads = model.SortWorkloadsByCreationTime(workloads) 413 return workloads 414 } 415 416 func (a *index) AdditionalPodSubscriptions( 417 proxy *model.Proxy, 418 allAddresses sets.String, 419 currentSubs sets.String, 420 ) sets.String { 421 shouldSubscribe := sets.New[string]() 422 423 // First, we want to handle VIP subscriptions. Example: 424 // Client subscribes to VIP1. Pod1, part of VIP1, is sent. 425 // The client wouldn't be explicitly subscribed to Pod1, so it would normally ignore it. 426 // Since it is a part of VIP1 which we are subscribe to, add it to the subscriptions 427 for addr := range allAddresses { 428 for _, wl := range model.ExtractWorkloadsFromAddresses(a.Lookup(addr)) { 429 // We may have gotten an update for Pod, but are subscribed to a Service. 430 // We need to force a subscription on the Pod as well 431 for namespacedHostname := range wl.Services { 432 if currentSubs.Contains(namespacedHostname) { 433 shouldSubscribe.Insert(wl.ResourceName()) 434 break 435 } 436 } 437 } 438 } 439 440 // Next, as an optimization, we will send all node-local endpoints 441 if nodeName := proxy.Metadata.NodeName; nodeName != "" { 442 for _, wl := range model.ExtractWorkloadsFromAddresses(a.All()) { 443 if wl.Node == nodeName { 444 n := wl.ResourceName() 445 if currentSubs.Contains(n) { 446 continue 447 } 448 shouldSubscribe.Insert(n) 449 } 450 } 451 } 452 453 return shouldSubscribe 454 } 455 456 func (a *index) SyncAll() { 457 a.networkUpdateTrigger.TriggerRecomputation() 458 } 459 460 func (a *index) HasSynced() bool { 461 return a.services.Synced().HasSynced() && 462 a.workloads.Synced().HasSynced() && 463 a.waypoints.Synced().HasSynced() && 464 a.authorizationPolicies.Synced().HasSynced() 465 } 466 467 type LookupNetwork func(endpointIP string, labels labels.Instance) network.ID 468 469 func PushXds[T any](xds model.XDSUpdater, f func(T) model.ConfigKey) func(events []krt.Event[T], initialSync bool) { 470 return func(events []krt.Event[T], initialSync bool) { 471 cu := sets.New[model.ConfigKey]() 472 for _, e := range events { 473 for _, i := range e.Items() { 474 cu.Insert(f(i)) 475 } 476 } 477 xds.ConfigUpdate(&model.PushRequest{ 478 Full: false, 479 ConfigsUpdated: cu, 480 Reason: model.NewReasonStats(model.AmbientUpdate), 481 }) 482 } 483 }