istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/networking/core/loadbalancer/loadbalancer.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 // packages used for load balancer setting 16 package loadbalancer 17 18 import ( 19 "math" 20 "sort" 21 "strings" 22 23 core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" 24 endpoint "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3" 25 wrappers "google.golang.org/protobuf/types/known/wrapperspb" 26 27 "istio.io/api/networking/v1alpha3" 28 "istio.io/istio/pilot/pkg/model" 29 "istio.io/istio/pilot/pkg/networking/util" 30 "istio.io/istio/pkg/util/sets" 31 ) 32 33 const ( 34 FailoverPriorityLabelDefaultSeparator = '=' 35 ) 36 37 func GetLocalityLbSetting( 38 mesh *v1alpha3.LocalityLoadBalancerSetting, 39 destrule *v1alpha3.LocalityLoadBalancerSetting, 40 ) *v1alpha3.LocalityLoadBalancerSetting { 41 var enabled bool 42 // Locality lb is enabled if its not explicitly disabled in mesh global config 43 if mesh != nil && (mesh.Enabled == nil || mesh.Enabled.Value) { 44 enabled = true 45 } 46 // Unless we explicitly override this in destination rule 47 if destrule != nil { 48 if destrule.Enabled != nil && !destrule.Enabled.Value { 49 enabled = false 50 } else { 51 enabled = true 52 } 53 } 54 if !enabled { 55 return nil 56 } 57 58 // Destination Rule overrides mesh config. If its defined, use that 59 if destrule != nil { 60 return destrule 61 } 62 // Otherwise fall back to mesh default 63 return mesh 64 } 65 66 func ApplyLocalityLoadBalancer( 67 loadAssignment *endpoint.ClusterLoadAssignment, 68 wrappedLocalityLbEndpoints []*WrappedLocalityLbEndpoints, 69 locality *core.Locality, 70 proxyLabels map[string]string, 71 localityLB *v1alpha3.LocalityLoadBalancerSetting, 72 enableFailover bool, 73 ) { 74 if localityLB == nil || loadAssignment == nil { 75 return 76 } 77 78 // one of Distribute or Failover settings can be applied. 79 if localityLB.GetDistribute() != nil { 80 applyLocalityWeights(locality, loadAssignment, localityLB.GetDistribute()) 81 // Failover needs outlier detection, otherwise Envoy will never drop down to a lower priority. 82 // Do not apply default failover when locality LB is disabled. 83 } else if enableFailover && (localityLB.Enabled == nil || localityLB.Enabled.Value) { 84 if len(localityLB.FailoverPriority) > 0 { 85 // Apply user defined priority failover settings. 86 applyFailoverPriorities(loadAssignment, wrappedLocalityLbEndpoints, proxyLabels, localityLB.FailoverPriority) 87 // If failover is expliciltly configured with failover priority, apply failover settings also. 88 if len(localityLB.Failover) != 0 { 89 applyLocalityFailover(locality, loadAssignment, localityLB.Failover) 90 } 91 } else { 92 // Apply default failover settings or user defined region failover settings. 93 applyLocalityFailover(locality, loadAssignment, localityLB.Failover) 94 } 95 } 96 } 97 98 // set locality loadbalancing weight based on user defined weights. 99 func applyLocalityWeights( 100 locality *core.Locality, 101 loadAssignment *endpoint.ClusterLoadAssignment, 102 distribute []*v1alpha3.LocalityLoadBalancerSetting_Distribute, 103 ) { 104 if distribute == nil { 105 return 106 } 107 108 // Support Locality weighted load balancing 109 // (https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/upstream/load_balancing/locality_weight#locality-weighted-load-balancing) 110 // by providing weights in LocalityLbEndpoints via load_balancing_weight. 111 // By setting weights across different localities, it can allow 112 // Envoy to do weighted load balancing across different zones and geographical locations. 113 for _, localityWeightSetting := range distribute { 114 if localityWeightSetting != nil && 115 util.LocalityMatch(locality, localityWeightSetting.From) { 116 misMatched := sets.Set[int]{} 117 for i := range loadAssignment.Endpoints { 118 misMatched.Insert(i) 119 } 120 for locality, weight := range localityWeightSetting.To { 121 // index -> original weight 122 destLocMap := map[int]uint32{} 123 totalWeight := uint32(0) 124 for i, ep := range loadAssignment.Endpoints { 125 if misMatched.Contains(i) { 126 if util.LocalityMatch(ep.Locality, locality) { 127 delete(misMatched, i) 128 if ep.LoadBalancingWeight != nil { 129 destLocMap[i] = ep.LoadBalancingWeight.Value 130 } else { 131 destLocMap[i] = 1 132 } 133 totalWeight += destLocMap[i] 134 } 135 } 136 } 137 // in case wildcard dest matching multi groups of endpoints 138 // the load balancing weight for a locality is divided by the sum of the weights of all localities 139 for index, originalWeight := range destLocMap { 140 destWeight := float64(originalWeight*weight) / float64(totalWeight) 141 if destWeight > 0 { 142 loadAssignment.Endpoints[index].LoadBalancingWeight = &wrappers.UInt32Value{ 143 Value: uint32(math.Ceil(destWeight)), 144 } 145 } 146 } 147 } 148 149 // remove groups of endpoints in a locality that miss matched 150 for i := range misMatched { 151 if loadAssignment.Endpoints[i] != nil { 152 loadAssignment.Endpoints[i].LbEndpoints = nil 153 } 154 } 155 break 156 } 157 } 158 } 159 160 // set locality loadbalancing priority - This is based on Region/Zone/SubZone matching. 161 func applyLocalityFailover( 162 locality *core.Locality, 163 loadAssignment *endpoint.ClusterLoadAssignment, 164 failover []*v1alpha3.LocalityLoadBalancerSetting_Failover, 165 ) { 166 // key is priority, value is the index of the LocalityLbEndpoints in ClusterLoadAssignment 167 priorityMap := map[int][]int{} 168 169 // 1. calculate the LocalityLbEndpoints.Priority compared with proxy locality 170 for i, localityEndpoint := range loadAssignment.Endpoints { 171 // if region/zone/subZone all match, the priority is 0. 172 // if region/zone match, the priority is 1. 173 // if region matches, the priority is 2. 174 // if locality not match, the priority is 3. 175 priority := util.LbPriority(locality, localityEndpoint.Locality) 176 // region not match, apply failover settings when specified 177 // update localityLbEndpoints' priority to 4 if failover not match 178 if priority == 3 { 179 for _, failoverSetting := range failover { 180 if failoverSetting.From == locality.Region { 181 if localityEndpoint.Locality == nil || localityEndpoint.Locality.Region != failoverSetting.To { 182 priority = 4 183 } 184 break 185 } 186 } 187 } 188 // priority is calculated using the already assigned priority using failoverPriority. 189 // Since there are at most 5 priorities can be assigned using locality failover(0-4), 190 // we multiply the priority by 5 for maintaining the priorities already assigned. 191 // Afterwards the final priorities can be calculted from 0 (highest) to N (lowest) without skipping. 192 priorityInt := int(loadAssignment.Endpoints[i].Priority*5) + priority 193 loadAssignment.Endpoints[i].Priority = uint32(priorityInt) 194 priorityMap[priorityInt] = append(priorityMap[priorityInt], i) 195 } 196 197 // since Priorities should range from 0 (highest) to N (lowest) without skipping. 198 // 2. adjust the priorities in order 199 // 2.1 sort all priorities in increasing order. 200 priorities := []int{} 201 for priority := range priorityMap { 202 priorities = append(priorities, priority) 203 } 204 sort.Ints(priorities) 205 // 2.2 adjust LocalityLbEndpoints priority 206 // if the index and value of priorities array is not equal. 207 for i, priority := range priorities { 208 if i != priority { 209 // the LocalityLbEndpoints index in ClusterLoadAssignment.Endpoints 210 for _, index := range priorityMap[priority] { 211 loadAssignment.Endpoints[index].Priority = uint32(i) 212 } 213 } 214 } 215 } 216 217 // WrappedLocalityLbEndpoints contain an envoy LocalityLbEndpoints 218 // and the original IstioEndpoints used to generate it. 219 // It is used to do failover priority label match with proxy labels. 220 type WrappedLocalityLbEndpoints struct { 221 IstioEndpoints []*model.IstioEndpoint 222 LocalityLbEndpoints *endpoint.LocalityLbEndpoints 223 } 224 225 // set loadbalancing priority by failover priority label. 226 func applyFailoverPriorities( 227 loadAssignment *endpoint.ClusterLoadAssignment, 228 wrappedLocalityLbEndpoints []*WrappedLocalityLbEndpoints, 229 proxyLabels map[string]string, 230 failoverPriorities []string, 231 ) { 232 if len(proxyLabels) == 0 || len(wrappedLocalityLbEndpoints) == 0 { 233 return 234 } 235 priorityMap := make(map[int][]int, len(failoverPriorities)) 236 localityLbEndpoints := []*endpoint.LocalityLbEndpoints{} 237 for _, wrappedLbEndpoint := range wrappedLocalityLbEndpoints { 238 localityLbEndpointsPerLocality := applyFailoverPriorityPerLocality(proxyLabels, wrappedLbEndpoint, failoverPriorities) 239 localityLbEndpoints = append(localityLbEndpoints, localityLbEndpointsPerLocality...) 240 } 241 for i, ep := range localityLbEndpoints { 242 priorityMap[int(ep.Priority)] = append(priorityMap[int(ep.Priority)], i) 243 } 244 // since Priorities should range from 0 (highest) to N (lowest) without skipping. 245 // adjust the priorities in order 246 // 1. sort all priorities in increasing order. 247 priorities := []int{} 248 for priority := range priorityMap { 249 priorities = append(priorities, priority) 250 } 251 sort.Ints(priorities) 252 // 2. adjust LocalityLbEndpoints priority 253 // if the index and value of priorities array is not equal. 254 for i, priority := range priorities { 255 if i != priority { 256 // the LocalityLbEndpoints index in ClusterLoadAssignment.Endpoints 257 for _, index := range priorityMap[priority] { 258 localityLbEndpoints[index].Priority = uint32(i) 259 } 260 } 261 } 262 loadAssignment.Endpoints = localityLbEndpoints 263 } 264 265 // Returning the label names in a separate array as the iteration of map is not ordered. 266 func priorityLabelOverrides(labels []string) ([]string, map[string]string) { 267 priorityLabels := make([]string, 0, len(labels)) 268 overriddenValueByLabel := make(map[string]string, len(labels)) 269 var tempStrings []string 270 for _, labelWithValue := range labels { 271 tempStrings = strings.Split(labelWithValue, string(FailoverPriorityLabelDefaultSeparator)) 272 priorityLabels = append(priorityLabels, tempStrings[0]) 273 if len(tempStrings) == 2 { 274 overriddenValueByLabel[tempStrings[0]] = tempStrings[1] 275 continue 276 } 277 } 278 return priorityLabels, overriddenValueByLabel 279 } 280 281 // set loadbalancing priority by failover priority label. 282 // split one LocalityLbEndpoints to multiple LocalityLbEndpoints based on failover priorities. 283 func applyFailoverPriorityPerLocality( 284 proxyLabels map[string]string, 285 ep *WrappedLocalityLbEndpoints, 286 failoverPriorities []string, 287 ) []*endpoint.LocalityLbEndpoints { 288 lowestPriority := len(failoverPriorities) 289 // key is priority, value is the index of LocalityLbEndpoints.LbEndpoints 290 priorityMap := map[int][]int{} 291 priorityLabels, priorityLabelOverrides := priorityLabelOverrides(failoverPriorities) 292 for i, istioEndpoint := range ep.IstioEndpoints { 293 var priority int 294 // failoverPriority labels match 295 for j, label := range priorityLabels { 296 valueForProxy, ok := priorityLabelOverrides[label] 297 if !ok { 298 valueForProxy = proxyLabels[label] 299 } 300 if valueForProxy != istioEndpoint.Labels[label] { 301 priority = lowestPriority - j 302 break 303 } 304 } 305 priorityMap[priority] = append(priorityMap[priority], i) 306 } 307 308 // sort all priorities in increasing order. 309 priorities := []int{} 310 for priority := range priorityMap { 311 priorities = append(priorities, priority) 312 } 313 sort.Ints(priorities) 314 315 out := make([]*endpoint.LocalityLbEndpoints, len(priorityMap)) 316 for i, priority := range priorities { 317 out[i] = util.CloneLocalityLbEndpoint(ep.LocalityLbEndpoints) 318 out[i].LbEndpoints = nil 319 out[i].Priority = uint32(priority) 320 var weight uint32 321 for _, index := range priorityMap[priority] { 322 out[i].LbEndpoints = append(out[i].LbEndpoints, ep.LocalityLbEndpoints.LbEndpoints[index]) 323 weight += ep.LocalityLbEndpoints.LbEndpoints[index].GetLoadBalancingWeight().GetValue() 324 } 325 // reset weight 326 out[i].LoadBalancingWeight = &wrappers.UInt32Value{ 327 Value: weight, 328 } 329 } 330 331 return out 332 }