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 }