istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/xds/endpoints/ep_filters.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 endpoints
    16  
    17  import (
    18  	"math"
    19  
    20  	core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
    21  	endpoint "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3"
    22  	"google.golang.org/protobuf/proto"
    23  	wrappers "google.golang.org/protobuf/types/known/wrapperspb"
    24  
    25  	"istio.io/istio/pilot/pkg/model"
    26  	"istio.io/istio/pilot/pkg/networking/util"
    27  	labelutil "istio.io/istio/pilot/pkg/serviceregistry/util/label"
    28  	"istio.io/istio/pkg/cluster"
    29  	"istio.io/istio/pkg/config/labels"
    30  	"istio.io/istio/pkg/maps"
    31  	"istio.io/istio/pkg/network"
    32  )
    33  
    34  // EndpointsByNetworkFilter is a network filter function to support Split Horizon EDS - filter the endpoints based on the network
    35  // of the connected sidecar. The filter will filter out all endpoints which are not present within the
    36  // sidecar network and add a gateway endpoint to remote networks that have endpoints
    37  // (if gateway exists and its IP is an IP and not a dns name).
    38  // Information for the mesh networks is provided as a MeshNetwork config map.
    39  func (b *EndpointBuilder) EndpointsByNetworkFilter(endpoints []*LocalityEndpoints) []*LocalityEndpoints {
    40  	if !b.gateways().IsMultiNetworkEnabled() {
    41  		// Multi-network is not configured (this is the case by default). Just access all endpoints directly.
    42  		return endpoints
    43  	}
    44  
    45  	// A new array of endpoints to be returned that will have both local and
    46  	// remote gateways (if any)
    47  	filtered := make([]*LocalityEndpoints, 0)
    48  
    49  	// Scale all weights by the lcm of gateways per network and gateways per cluster.
    50  	// This will allow us to more easily spread traffic to the endpoint across multiple
    51  	// network gateways, increasing reliability of the endpoint.
    52  	scaleFactor := b.gateways().GetLBWeightScaleFactor()
    53  
    54  	// Go through all cluster endpoints and add those with the same network as the sidecar
    55  	// to the result. Also count the number of endpoints per each remote network while
    56  	// iterating so that it can be used as the weight for the gateway endpoint
    57  	for _, ep := range endpoints {
    58  		lbEndpoints := &LocalityEndpoints{
    59  			llbEndpoints: endpoint.LocalityLbEndpoints{
    60  				Locality: ep.llbEndpoints.Locality,
    61  				Priority: ep.llbEndpoints.Priority,
    62  				// Endpoints and weight will be reset below.
    63  			},
    64  		}
    65  
    66  		// Create a map to keep track of the gateways used and their aggregate weights.
    67  		gatewayWeights := make(map[model.NetworkGateway]uint32)
    68  
    69  		// Process all the endpoints.
    70  		for i, lbEp := range ep.llbEndpoints.LbEndpoints {
    71  			istioEndpoint := ep.istioEndpoints[i]
    72  
    73  			// If the proxy can't view the network for this endpoint, exclude it entirely.
    74  			if !b.proxyView.IsVisible(istioEndpoint) {
    75  				continue
    76  			}
    77  
    78  			// Copy the endpoint in order to expand the load balancing weight.
    79  			// When multiplying, be careful to avoid overflow - clipping the
    80  			// result at the maximum value for uint32.
    81  			weight := b.scaleEndpointLBWeight(lbEp, scaleFactor)
    82  			if lbEp.GetLoadBalancingWeight().GetValue() != weight {
    83  				lbEp = proto.Clone(lbEp).(*endpoint.LbEndpoint)
    84  				lbEp.LoadBalancingWeight = &wrappers.UInt32Value{
    85  					Value: weight,
    86  				}
    87  			}
    88  
    89  			epNetwork := istioEndpoint.Network
    90  			epCluster := istioEndpoint.Locality.ClusterID
    91  			gateways := b.selectNetworkGateways(epNetwork, epCluster)
    92  
    93  			// Check if the endpoint is directly reachable. It's considered directly reachable if
    94  			// the endpoint is either on the local network or on a remote network that can be reached
    95  			// directly from the local network.
    96  			if b.proxy.InNetwork(epNetwork) || len(gateways) == 0 {
    97  				// The endpoint is directly reachable - just add it.
    98  				// If there is no gateway, the address must not be empty
    99  				if lbEp.GetEndpoint().GetAddress().GetSocketAddress().GetAddress() != "" {
   100  					lbEndpoints.append(ep.istioEndpoints[i], lbEp)
   101  				}
   102  
   103  				continue
   104  			}
   105  
   106  			// Cross-network traffic relies on mTLS to be enabled for SNI routing
   107  			// TODO BTS may allow us to work around this
   108  			if !isMtlsEnabled(lbEp) {
   109  				continue
   110  			}
   111  
   112  			// Apply the weight for this endpoint to the network gateways.
   113  			splitWeightAmongGateways(weight, gateways, gatewayWeights)
   114  		}
   115  
   116  		// Sort the gateways into an ordered list so that the generated endpoints are deterministic.
   117  		gateways := maps.Keys(gatewayWeights)
   118  		gateways = model.SortGateways(gateways)
   119  
   120  		// Create endpoints for the gateways.
   121  		for _, gw := range gateways {
   122  			epWeight := gatewayWeights[gw]
   123  			if epWeight == 0 {
   124  				log.Warnf("gateway weight must be greater than 0, scaleFactor is %d", scaleFactor)
   125  				epWeight = 1
   126  			}
   127  			epAddr := util.BuildAddress(gw.Addr, gw.Port)
   128  
   129  			// Generate a fake IstioEndpoint to carry network and cluster information.
   130  			gwIstioEp := &model.IstioEndpoint{
   131  				Network: gw.Network,
   132  				Locality: model.Locality{
   133  					ClusterID: gw.Cluster,
   134  				},
   135  				Labels: labelutil.AugmentLabels(nil, gw.Cluster, "", "", gw.Network),
   136  			}
   137  
   138  			// Generate the EDS endpoint for this gateway.
   139  			gwEp := &endpoint.LbEndpoint{
   140  				HostIdentifier: &endpoint.LbEndpoint_Endpoint{
   141  					Endpoint: &endpoint.Endpoint{
   142  						Address: epAddr,
   143  					},
   144  				},
   145  				LoadBalancingWeight: &wrappers.UInt32Value{
   146  					Value: epWeight,
   147  				},
   148  				Metadata: &core.Metadata{},
   149  			}
   150  			// TODO: figure out a way to extract locality data from the gateway public endpoints in meshNetworks
   151  			util.AppendLbEndpointMetadata(&model.EndpointMetadata{
   152  				Network:   gw.Network,
   153  				TLSMode:   model.IstioMutualTLSModeLabel,
   154  				ClusterID: gw.Cluster,
   155  				Labels:    labels.Instance{},
   156  			}, gwEp.Metadata)
   157  			// Currently gateway endpoint does not support tunnel.
   158  			lbEndpoints.append(gwIstioEp, gwEp)
   159  		}
   160  
   161  		// Endpoint members could be stripped or aggregated by network. Adjust weight value here.
   162  		lbEndpoints.refreshWeight()
   163  		filtered = append(filtered, lbEndpoints)
   164  	}
   165  
   166  	return filtered
   167  }
   168  
   169  // selectNetworkGateways chooses the gateways that best match the network and cluster. If there is
   170  // no match for the network+cluster, then all gateways matching the network are returned. Preferring
   171  // gateways that match against cluster has the following advantages:
   172  //
   173  //  1. Potentially reducing extra latency incurred when the gateway and endpoint reside in different
   174  //     clusters.
   175  //
   176  //  2. Enables Kubernetes MCS use cases, where endpoints for a service might be exported in one
   177  //     cluster but not another within the same network. By targeting the gateway for the cluster
   178  //     where the exported endpoints reside, we ensure that we only send traffic to exported endpoints.
   179  func (b *EndpointBuilder) selectNetworkGateways(nw network.ID, c cluster.ID) []model.NetworkGateway {
   180  	// Get the gateways for this network+cluster combination.
   181  	gws := b.gateways().GatewaysForNetworkAndCluster(nw, c)
   182  	if len(gws) == 0 {
   183  		// No match for network+cluster, just match the network.
   184  		gws = b.gateways().GatewaysForNetwork(nw)
   185  	}
   186  	return gws
   187  }
   188  
   189  func (b *EndpointBuilder) scaleEndpointLBWeight(ep *endpoint.LbEndpoint, scaleFactor uint32) uint32 {
   190  	if ep.GetLoadBalancingWeight() == nil || ep.GetLoadBalancingWeight().Value == 0 {
   191  		return scaleFactor
   192  	}
   193  	weight := uint32(math.MaxUint32)
   194  	if ep.GetLoadBalancingWeight().Value < math.MaxUint32/scaleFactor {
   195  		weight = ep.GetLoadBalancingWeight().Value * scaleFactor
   196  	}
   197  	return weight
   198  }
   199  
   200  // Apply the weight for this endpoint to the network gateways.
   201  func splitWeightAmongGateways(weight uint32, gateways []model.NetworkGateway, gatewayWeights map[model.NetworkGateway]uint32) {
   202  	// Spread the weight across the gateways.
   203  	weightPerGateway := weight / uint32(len(gateways))
   204  	for _, gateway := range gateways {
   205  		gatewayWeights[gateway] += weightPerGateway
   206  	}
   207  }
   208  
   209  // EndpointsWithMTLSFilter removes all endpoints that do not handle mTLS. This is determined by looking at
   210  // auto-mTLS, DestinationRule, and PeerAuthentication to determine if we would send mTLS to these endpoints.
   211  // Note there is no guarantee these destinations *actually* handle mTLS; just that we are configured to send mTLS to them.
   212  func (b *EndpointBuilder) EndpointsWithMTLSFilter(endpoints []*LocalityEndpoints) []*LocalityEndpoints {
   213  	// A new array of endpoints to be returned that will have both local and
   214  	// remote gateways (if any)
   215  	filtered := make([]*LocalityEndpoints, 0)
   216  
   217  	// Go through all cluster endpoints and add those with mTLS enabled
   218  	for _, ep := range endpoints {
   219  		lbEndpoints := &LocalityEndpoints{
   220  			llbEndpoints: endpoint.LocalityLbEndpoints{
   221  				Locality: ep.llbEndpoints.Locality,
   222  				Priority: ep.llbEndpoints.Priority,
   223  				// Endpoints and will be reset below.
   224  			},
   225  		}
   226  
   227  		for i, lbEp := range ep.llbEndpoints.LbEndpoints {
   228  			if !isMtlsEnabled(lbEp) {
   229  				// no mTLS, skip it
   230  				continue
   231  			}
   232  			lbEndpoints.append(ep.istioEndpoints[i], lbEp)
   233  		}
   234  
   235  		lbEndpoints.refreshWeight()
   236  		filtered = append(filtered, lbEndpoints)
   237  	}
   238  
   239  	return filtered
   240  }