istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/serviceregistry/kube/controller/network.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 controller 16 17 import ( 18 "net" 19 "strconv" 20 "sync" 21 22 "github.com/yl2chen/cidranger" 23 v1 "k8s.io/api/core/v1" 24 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 25 "k8s.io/apimachinery/pkg/types" 26 "sigs.k8s.io/gateway-api/apis/v1beta1" 27 28 "istio.io/api/label" 29 "istio.io/istio/pilot/pkg/features" 30 "istio.io/istio/pilot/pkg/model" 31 "istio.io/istio/pilot/pkg/serviceregistry/kube" 32 "istio.io/istio/pkg/cluster" 33 "istio.io/istio/pkg/config/constants" 34 "istio.io/istio/pkg/config/host" 35 "istio.io/istio/pkg/config/mesh" 36 "istio.io/istio/pkg/config/schema/gvr" 37 "istio.io/istio/pkg/kube/kclient" 38 "istio.io/istio/pkg/kube/kubetypes" 39 "istio.io/istio/pkg/network" 40 "istio.io/istio/pkg/slices" 41 ) 42 43 type networkManager struct { 44 sync.RWMutex 45 // CIDR ranger based on path-compressed prefix trie 46 ranger cidranger.Ranger 47 clusterID cluster.ID 48 49 gatewayResourceClient kclient.Informer[*v1beta1.Gateway] 50 meshNetworksWatcher mesh.NetworksWatcher 51 52 // Network name for to be used when the meshNetworks fromRegistry nor network label on pod is specified 53 // This is defined by a topology.istio.io/network label on the system namespace. 54 network network.ID 55 // Network name for the registry as specified by the MeshNetworks configmap 56 networkFromMeshConfig network.ID 57 // map of svc fqdn to partially built network gateways; the actual gateways will be built from these into networkGatewaysBySvc 58 // this map just enumerates which networks/ports each Service is a gateway for 59 registryServiceNameGateways map[host.Name][]model.NetworkGateway 60 // gateways for each service 61 networkGatewaysBySvc map[host.Name]model.NetworkGatewaySet 62 // gateways from kubernetes Gateway resources 63 gatewaysFromResource map[types.UID]model.NetworkGatewaySet 64 // we don't want to discover gateways with class "istio-remote" from outside cluster's API servers. 65 discoverRemoteGatewayResources bool 66 67 // implements NetworkGatewaysWatcher; we need to call c.NotifyGatewayHandlers when our gateways change 68 model.NetworkGatewaysHandler 69 } 70 71 func initNetworkManager(c *Controller, options Options) *networkManager { 72 n := &networkManager{ 73 clusterID: options.ClusterID, 74 meshNetworksWatcher: options.MeshNetworksWatcher, 75 // zero values are a workaround structcheck issue: https://github.com/golangci/golangci-lint/issues/826 76 ranger: nil, 77 network: "", 78 networkFromMeshConfig: "", 79 registryServiceNameGateways: make(map[host.Name][]model.NetworkGateway), 80 networkGatewaysBySvc: make(map[host.Name]model.NetworkGatewaySet), 81 gatewaysFromResource: make(map[types.UID]model.NetworkGatewaySet), 82 discoverRemoteGatewayResources: options.ConfigCluster, 83 } 84 // initialize the gateway resource client when any feature that uses it is enabled 85 if features.MultiNetworkGatewayAPI { 86 n.gatewayResourceClient = kclient.NewDelayedInformer[*v1beta1.Gateway](c.client, gvr.KubernetesGateway, kubetypes.StandardInformer, kubetypes.Filter{}) 87 } 88 if features.MultiNetworkGatewayAPI { 89 // conditionally register this handler 90 registerHandlers(c, n.gatewayResourceClient, "Gateways", n.handleGatewayResource, nil) 91 } 92 return n 93 } 94 95 // setNetworkFromNamespace sets network got from system namespace, returns whether it has changed 96 func (n *networkManager) setNetworkFromNamespace(ns *v1.Namespace) bool { 97 nw := ns.Labels[label.TopologyNetwork.Name] 98 n.Lock() 99 defer n.Unlock() 100 oldDefaultNetwork := n.network 101 n.network = network.ID(nw) 102 return oldDefaultNetwork != n.network 103 } 104 105 func (n *networkManager) networkFromSystemNamespace() network.ID { 106 n.RLock() 107 defer n.RUnlock() 108 return n.network 109 } 110 111 func (n *networkManager) networkFromMeshNetworks(endpointIP string) network.ID { 112 n.RLock() 113 defer n.RUnlock() 114 if n.networkFromMeshConfig != "" { 115 return n.networkFromMeshConfig 116 } 117 118 if n.ranger != nil { 119 ip := net.ParseIP(endpointIP) 120 if ip == nil { 121 return "" 122 } 123 entries, err := n.ranger.ContainingNetworks(ip) 124 if err != nil { 125 log.Errorf("error getting cidr ranger entry from endpoint ip %s", endpointIP) 126 return "" 127 } 128 if len(entries) > 1 { 129 log.Warnf("Found multiple networks CIDRs matching the endpoint IP: %s. Using the first match.", endpointIP) 130 } 131 if len(entries) > 0 { 132 return (entries[0].(namedRangerEntry)).name 133 } 134 } 135 return "" 136 } 137 138 // namedRangerEntry for holding network's CIDR and name 139 type namedRangerEntry struct { 140 name network.ID 141 network net.IPNet 142 } 143 144 // Network returns the IPNet for the network 145 func (n namedRangerEntry) Network() net.IPNet { 146 return n.network 147 } 148 149 // onNetworkChange is fired if the default network is changed either via the namespace label or mesh-networks 150 func (c *Controller) onNetworkChange() { 151 // the network for endpoints are computed when we process the events; this will fix the cache 152 // NOTE: this must run before the other network watcher handler that creates a force push 153 if err := c.syncPods(); err != nil { 154 log.Errorf("one or more errors force-syncing pods: %v", err) 155 } 156 if err := c.endpoints.initializeNamespace(metav1.NamespaceAll, true); err != nil { 157 log.Errorf("one or more errors force-syncing endpoints: %v", err) 158 } 159 c.reloadNetworkGateways() 160 // This is to ensure the ambient workloads are updated dynamically, aligning them with the current network settings. 161 // With this, the pod do not need to restart when the network configuration changes. 162 if c.ambientIndex != nil { 163 c.ambientIndex.SyncAll() 164 } 165 } 166 167 // reloadMeshNetworks will read the mesh networks configuration to setup 168 // fromRegistry and cidr based network lookups for this registry 169 func (n *networkManager) reloadMeshNetworks() { 170 n.Lock() 171 defer n.Unlock() 172 ranger := cidranger.NewPCTrieRanger() 173 174 n.networkFromMeshConfig = "" 175 n.registryServiceNameGateways = make(map[host.Name][]model.NetworkGateway) 176 177 meshNetworks := n.meshNetworksWatcher.Networks() 178 if meshNetworks == nil || len(meshNetworks.Networks) == 0 { 179 return 180 } 181 for id, v := range meshNetworks.Networks { 182 // track endpoints items from this registry are a part of this network 183 fromRegistry := false 184 for _, ep := range v.Endpoints { 185 if ep.GetFromCidr() != "" { 186 _, nw, err := net.ParseCIDR(ep.GetFromCidr()) 187 if err != nil { 188 log.Warnf("unable to parse CIDR %q for network %s", ep.GetFromCidr(), id) 189 continue 190 } 191 rangerEntry := namedRangerEntry{ 192 name: network.ID(id), 193 network: *nw, 194 } 195 _ = ranger.Insert(rangerEntry) 196 } 197 if ep.GetFromRegistry() != "" && cluster.ID(ep.GetFromRegistry()) == n.clusterID { 198 fromRegistry = true 199 } 200 } 201 202 // fromRegistry field specified this cluster 203 if fromRegistry { 204 // treat endpoints in this cluster as part of this network 205 if n.networkFromMeshConfig != "" { 206 log.Warnf("multiple networks specify %s in fromRegistry; endpoints from %s will continue to be treated as part of %s", 207 n.clusterID, n.clusterID, n.networkFromMeshConfig) 208 } else { 209 n.networkFromMeshConfig = network.ID(id) 210 } 211 212 // services in this registry matching the registryServiceName and port are part of this network 213 for _, gw := range v.Gateways { 214 if gwSvcName := gw.GetRegistryServiceName(); gwSvcName != "" { 215 svc := host.Name(gwSvcName) 216 n.registryServiceNameGateways[svc] = append(n.registryServiceNameGateways[svc], model.NetworkGateway{ 217 Network: network.ID(id), 218 Cluster: n.clusterID, 219 Port: gw.GetPort(), 220 }) 221 } 222 } 223 } 224 225 } 226 n.ranger = ranger 227 } 228 229 func (c *Controller) NetworkGateways() []model.NetworkGateway { 230 c.networkManager.RLock() 231 defer c.networkManager.RUnlock() 232 233 // Merge all the gateways into a single set to eliminate duplicates. 234 out := make(model.NetworkGatewaySet) 235 for _, gateways := range c.networkGatewaysBySvc { 236 out.Merge(gateways) 237 } 238 for _, gateways := range c.gatewaysFromResource { 239 out.Merge(gateways) 240 } 241 242 unsorted := out.UnsortedList() 243 return model.SortGateways(unsorted) 244 } 245 246 // extractGatewaysFromService checks if the service is a cross-network gateway 247 // and if it is, updates the controller's gateways. 248 func (c *Controller) extractGatewaysFromService(svc *model.Service) bool { 249 changed := c.extractGatewaysInner(svc) 250 if changed { 251 c.NotifyGatewayHandlers() 252 } 253 return changed 254 } 255 256 // reloadNetworkGateways performs extractGatewaysFromService for all services registered with the controller. 257 // It is called only by `onNetworkChange`. 258 // It iterates over all services, because mesh networks can be set with a service name. 259 func (c *Controller) reloadNetworkGateways() { 260 c.Lock() 261 gwsChanged := false 262 for _, svc := range c.servicesMap { 263 if c.extractGatewaysInner(svc) { 264 gwsChanged = true 265 break 266 } 267 } 268 c.Unlock() 269 if gwsChanged { 270 c.NotifyGatewayHandlers() 271 // TODO ConfigUpdate via gateway handler 272 c.opts.XDSUpdater.ConfigUpdate(&model.PushRequest{Full: true, Reason: model.NewReasonStats(model.NetworksTrigger)}) 273 } 274 } 275 276 // extractGatewaysInner performs the logic for extractGatewaysFromService without locking the controller. 277 // Returns true if any gateways changed. 278 func (n *networkManager) extractGatewaysInner(svc *model.Service) bool { 279 n.Lock() 280 defer n.Unlock() 281 previousGateways := n.networkGatewaysBySvc[svc.Hostname] 282 gateways := n.getGatewayDetails(svc) 283 // short circuit for most services. 284 if len(previousGateways) == 0 && len(gateways) == 0 { 285 return false 286 } 287 288 newGateways := make(model.NetworkGatewaySet) 289 // check if we have node port mappings 290 nodePortMap := make(map[uint32]uint32) 291 if svc.Attributes.ClusterExternalPorts != nil { 292 if npm, exists := svc.Attributes.ClusterExternalPorts[n.clusterID]; exists { 293 nodePortMap = npm 294 } 295 } 296 297 for _, addr := range svc.Attributes.ClusterExternalAddresses.GetAddressesFor(n.clusterID) { 298 for _, gw := range gateways { 299 // what we now have is a service port. If there is a mapping for cluster external ports, 300 // look it up and get the node port for the remote port 301 if nodePort, exists := nodePortMap[gw.Port]; exists { 302 gw.Port = nodePort 303 } 304 305 gw.Cluster = n.clusterID 306 gw.Addr = addr 307 newGateways.Insert(gw) 308 } 309 } 310 311 gatewaysChanged := !newGateways.Equals(previousGateways) 312 if len(newGateways) > 0 { 313 n.networkGatewaysBySvc[svc.Hostname] = newGateways 314 } else { 315 delete(n.networkGatewaysBySvc, svc.Hostname) 316 } 317 318 return gatewaysChanged 319 } 320 321 // getGatewayDetails returns gateways without the address populated, only the network and (unmapped) port for a given service. 322 func (n *networkManager) getGatewayDetails(svc *model.Service) []model.NetworkGateway { 323 // TODO should we start checking if svc's Ports contain the gateway port? 324 325 // label based gateways 326 // TODO label based gateways could support being the gateway for multiple networks 327 if nw := svc.Attributes.Labels[label.TopologyNetwork.Name]; nw != "" { 328 if gwPortStr := svc.Attributes.Labels[label.NetworkingGatewayPort.Name]; gwPortStr != "" { 329 if gwPort, err := strconv.Atoi(gwPortStr); err == nil { 330 return []model.NetworkGateway{{Port: uint32(gwPort), Network: network.ID(nw)}} 331 } 332 log.Warnf("could not parse %q for %s on %s/%s; defaulting to %d", 333 gwPortStr, label.NetworkingGatewayPort.Name, svc.Attributes.Namespace, svc.Attributes.Name, DefaultNetworkGatewayPort) 334 } 335 return []model.NetworkGateway{{Port: DefaultNetworkGatewayPort, Network: network.ID(nw)}} 336 } 337 338 // meshNetworks registryServiceName+fromRegistry 339 if gws, ok := n.registryServiceNameGateways[svc.Hostname]; ok { 340 out := append(make([]model.NetworkGateway, 0, len(gws)), gws...) 341 return out 342 } 343 344 return nil 345 } 346 347 // handleGateway resource adds a NetworkGateway for each combination of address and auto-passthrough listener 348 // discovering duplicates from the generated Service is not a huge concern as we de-duplicate in NetworkGateways 349 // which returns a set, although it's not totally efficient. 350 func (n *networkManager) handleGatewayResource(_ *v1beta1.Gateway, gw *v1beta1.Gateway, event model.Event) error { 351 if nw := gw.GetLabels()[label.TopologyNetwork.Name]; nw == "" { 352 return nil 353 } 354 355 // Gateway with istio-remote: only discover this from the config cluster 356 // this is a way to reference a gateway that lives in a place that this control plane 357 // won't have API server access. Nothing will be deployed for these Gateway resources. 358 if !n.discoverRemoteGatewayResources && gw.Spec.GatewayClassName == constants.RemoteGatewayClassName { 359 return nil 360 } 361 362 gatewaysChanged := false 363 n.Lock() 364 defer func() { 365 n.Unlock() 366 if gatewaysChanged { 367 n.NotifyGatewayHandlers() 368 } 369 }() 370 371 previousGateways := n.gatewaysFromResource[gw.UID] 372 373 if event == model.EventDelete { 374 gatewaysChanged = len(previousGateways) > 0 375 delete(n.gatewaysFromResource, gw.UID) 376 return nil 377 } 378 379 autoPassthrough := func(l v1beta1.Listener) bool { 380 return kube.IsAutoPassthrough(gw.GetLabels(), l) 381 } 382 383 base := model.NetworkGateway{ 384 Network: network.ID(gw.GetLabels()[label.TopologyNetwork.Name]), 385 Cluster: n.clusterID, 386 } 387 newGateways := model.NetworkGatewaySet{} 388 for _, addr := range gw.Spec.Addresses { 389 if addr.Type == nil { 390 continue 391 } 392 if addrType := *addr.Type; addrType != v1beta1.IPAddressType && addrType != v1beta1.HostnameAddressType { 393 continue 394 } 395 for _, l := range slices.Filter(gw.Spec.Listeners, autoPassthrough) { 396 networkGateway := base 397 networkGateway.Addr = addr.Value 398 networkGateway.Port = uint32(l.Port) 399 newGateways.Insert(networkGateway) 400 } 401 } 402 n.gatewaysFromResource[gw.UID] = newGateways 403 404 if len(previousGateways) != len(newGateways) { 405 gatewaysChanged = true 406 return nil 407 } 408 409 gatewaysChanged = !newGateways.Equals(previousGateways) 410 if len(newGateways) > 0 { 411 n.gatewaysFromResource[gw.UID] = newGateways 412 } else { 413 delete(n.gatewaysFromResource, gw.UID) 414 } 415 416 return nil 417 } 418 419 func (n *networkManager) HasSynced() bool { 420 if n.gatewayResourceClient == nil { 421 return true 422 } 423 return n.gatewayResourceClient.HasSynced() 424 } 425 426 // updateServiceNodePortAddresses updates ClusterExternalAddresses for Services of nodePort type 427 func (c *Controller) updateServiceNodePortAddresses(svcs ...*model.Service) bool { 428 // node event, update all nodePort gateway services 429 if len(svcs) == 0 { 430 svcs = c.getNodePortGatewayServices() 431 } 432 // no nodePort gateway service found, no update 433 if len(svcs) == 0 { 434 return false 435 } 436 for _, svc := range svcs { 437 c.RLock() 438 nodeSelector := c.nodeSelectorsForServices[svc.Hostname] 439 c.RUnlock() 440 // update external address 441 var nodeAddresses []string 442 for _, n := range c.nodeInfoMap { 443 if nodeSelector.SubsetOf(n.labels) { 444 nodeAddresses = append(nodeAddresses, n.address) 445 } 446 } 447 if svc.Attributes.ClusterExternalAddresses == nil { 448 svc.Attributes.ClusterExternalAddresses = &model.AddressMap{} 449 } 450 svc.Attributes.ClusterExternalAddresses.SetAddressesFor(c.Cluster(), nodeAddresses) 451 // update gateways that use the service 452 c.extractGatewaysFromService(svc) 453 } 454 return true 455 } 456 457 // getNodePortServices returns nodePort type gateway service 458 func (c *Controller) getNodePortGatewayServices() []*model.Service { 459 c.RLock() 460 defer c.RUnlock() 461 out := make([]*model.Service, 0, len(c.nodeSelectorsForServices)) 462 for hostname := range c.nodeSelectorsForServices { 463 svc := c.servicesMap[hostname] 464 if svc != nil { 465 out = append(out, svc) 466 } 467 } 468 469 return out 470 }