k8s.io/kubernetes@v1.29.3/pkg/proxy/endpointslicecache.go (about) 1 /* 2 Copyright 2019 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package proxy 18 19 import ( 20 "fmt" 21 "reflect" 22 "sort" 23 "strings" 24 "sync" 25 26 v1 "k8s.io/api/core/v1" 27 discovery "k8s.io/api/discovery/v1" 28 "k8s.io/apimachinery/pkg/types" 29 "k8s.io/apimachinery/pkg/util/sets" 30 utilfeature "k8s.io/apiserver/pkg/util/feature" 31 "k8s.io/client-go/tools/events" 32 "k8s.io/klog/v2" 33 "k8s.io/kubernetes/pkg/features" 34 proxyutil "k8s.io/kubernetes/pkg/proxy/util" 35 utilnet "k8s.io/utils/net" 36 ) 37 38 // EndpointSliceCache is used as a cache of EndpointSlice information. 39 type EndpointSliceCache struct { 40 // lock protects trackerByServiceMap. 41 lock sync.Mutex 42 43 // trackerByServiceMap is the basis of this cache. It contains endpoint 44 // slice trackers grouped by service name and endpoint slice name. The first 45 // key represents a namespaced service name while the second key represents 46 // an endpoint slice name. Since endpoints can move between slices, we 47 // require slice specific caching to prevent endpoints being removed from 48 // the cache when they may have just moved to a different slice. 49 trackerByServiceMap map[types.NamespacedName]*endpointSliceTracker 50 51 makeEndpointInfo makeEndpointFunc 52 hostname string 53 ipFamily v1.IPFamily 54 recorder events.EventRecorder 55 } 56 57 // endpointSliceTracker keeps track of EndpointSlices as they have been applied 58 // by a proxier along with any pending EndpointSlices that have been updated 59 // in this cache but not yet applied by a proxier. 60 type endpointSliceTracker struct { 61 applied endpointSliceInfoByName 62 pending endpointSliceInfoByName 63 } 64 65 // endpointSliceInfoByName groups endpointSliceInfo by the names of the 66 // corresponding EndpointSlices. 67 type endpointSliceInfoByName map[string]*endpointSliceInfo 68 69 // endpointSliceInfo contains just the attributes kube-proxy cares about. 70 // Used for caching. Intentionally small to limit memory util. 71 type endpointSliceInfo struct { 72 Ports []discovery.EndpointPort 73 Endpoints []*endpointInfo 74 Remove bool 75 } 76 77 // endpointInfo contains just the attributes kube-proxy cares about. 78 // Used for caching. Intentionally small to limit memory util. 79 // Addresses, NodeName, and Zone are copied from EndpointSlice Endpoints. 80 type endpointInfo struct { 81 Addresses []string 82 NodeName *string 83 Zone *string 84 ZoneHints sets.Set[string] 85 86 Ready bool 87 Serving bool 88 Terminating bool 89 } 90 91 // spToEndpointMap stores groups Endpoint objects by ServicePortName and 92 // endpoint string (returned by Endpoint.String()). 93 type spToEndpointMap map[ServicePortName]map[string]Endpoint 94 95 // NewEndpointSliceCache initializes an EndpointSliceCache. 96 func NewEndpointSliceCache(hostname string, ipFamily v1.IPFamily, recorder events.EventRecorder, makeEndpointInfo makeEndpointFunc) *EndpointSliceCache { 97 if makeEndpointInfo == nil { 98 makeEndpointInfo = standardEndpointInfo 99 } 100 return &EndpointSliceCache{ 101 trackerByServiceMap: map[types.NamespacedName]*endpointSliceTracker{}, 102 hostname: hostname, 103 ipFamily: ipFamily, 104 makeEndpointInfo: makeEndpointInfo, 105 recorder: recorder, 106 } 107 } 108 109 // newEndpointSliceTracker initializes an endpointSliceTracker. 110 func newEndpointSliceTracker() *endpointSliceTracker { 111 return &endpointSliceTracker{ 112 applied: endpointSliceInfoByName{}, 113 pending: endpointSliceInfoByName{}, 114 } 115 } 116 117 // newEndpointSliceInfo generates endpointSliceInfo from an EndpointSlice. 118 func newEndpointSliceInfo(endpointSlice *discovery.EndpointSlice, remove bool) *endpointSliceInfo { 119 esInfo := &endpointSliceInfo{ 120 Ports: make([]discovery.EndpointPort, len(endpointSlice.Ports)), 121 Endpoints: []*endpointInfo{}, 122 Remove: remove, 123 } 124 125 // copy here to avoid mutating shared EndpointSlice object. 126 copy(esInfo.Ports, endpointSlice.Ports) 127 sort.Sort(byPort(esInfo.Ports)) 128 129 if !remove { 130 for _, endpoint := range endpointSlice.Endpoints { 131 epInfo := &endpointInfo{ 132 Addresses: endpoint.Addresses, 133 Zone: endpoint.Zone, 134 NodeName: endpoint.NodeName, 135 136 // conditions 137 Ready: endpoint.Conditions.Ready == nil || *endpoint.Conditions.Ready, 138 Serving: endpoint.Conditions.Serving == nil || *endpoint.Conditions.Serving, 139 Terminating: endpoint.Conditions.Terminating != nil && *endpoint.Conditions.Terminating, 140 } 141 142 if utilfeature.DefaultFeatureGate.Enabled(features.TopologyAwareHints) { 143 if endpoint.Hints != nil && len(endpoint.Hints.ForZones) > 0 { 144 epInfo.ZoneHints = sets.New[string]() 145 for _, zone := range endpoint.Hints.ForZones { 146 epInfo.ZoneHints.Insert(zone.Name) 147 } 148 } 149 } 150 151 esInfo.Endpoints = append(esInfo.Endpoints, epInfo) 152 } 153 154 sort.Sort(byAddress(esInfo.Endpoints)) 155 } 156 157 return esInfo 158 } 159 160 // standardEndpointInfo is the default makeEndpointFunc. 161 func standardEndpointInfo(ep *BaseEndpointInfo, _ *ServicePortName) Endpoint { 162 return ep 163 } 164 165 // updatePending updates a pending slice in the cache. 166 func (cache *EndpointSliceCache) updatePending(endpointSlice *discovery.EndpointSlice, remove bool) bool { 167 serviceKey, sliceKey, err := endpointSliceCacheKeys(endpointSlice) 168 if err != nil { 169 klog.ErrorS(err, "Error getting endpoint slice cache keys") 170 return false 171 } 172 173 esInfo := newEndpointSliceInfo(endpointSlice, remove) 174 175 cache.lock.Lock() 176 defer cache.lock.Unlock() 177 178 if _, ok := cache.trackerByServiceMap[serviceKey]; !ok { 179 cache.trackerByServiceMap[serviceKey] = newEndpointSliceTracker() 180 } 181 182 changed := cache.esInfoChanged(serviceKey, sliceKey, esInfo) 183 184 if changed { 185 cache.trackerByServiceMap[serviceKey].pending[sliceKey] = esInfo 186 } 187 188 return changed 189 } 190 191 // checkoutChanges returns a map of all endpointsChanges that are 192 // pending and then marks them as applied. 193 func (cache *EndpointSliceCache) checkoutChanges() map[types.NamespacedName]*endpointsChange { 194 changes := make(map[types.NamespacedName]*endpointsChange) 195 196 cache.lock.Lock() 197 defer cache.lock.Unlock() 198 199 for serviceNN, esTracker := range cache.trackerByServiceMap { 200 if len(esTracker.pending) == 0 { 201 continue 202 } 203 204 change := &endpointsChange{} 205 206 change.previous = cache.getEndpointsMap(serviceNN, esTracker.applied) 207 208 for name, sliceInfo := range esTracker.pending { 209 if sliceInfo.Remove { 210 delete(esTracker.applied, name) 211 } else { 212 esTracker.applied[name] = sliceInfo 213 } 214 215 delete(esTracker.pending, name) 216 } 217 218 change.current = cache.getEndpointsMap(serviceNN, esTracker.applied) 219 changes[serviceNN] = change 220 } 221 222 return changes 223 } 224 225 // getEndpointsMap computes an EndpointsMap for a given set of EndpointSlices. 226 func (cache *EndpointSliceCache) getEndpointsMap(serviceNN types.NamespacedName, sliceInfoByName endpointSliceInfoByName) EndpointsMap { 227 endpointInfoBySP := cache.endpointInfoByServicePort(serviceNN, sliceInfoByName) 228 return endpointsMapFromEndpointInfo(endpointInfoBySP) 229 } 230 231 // endpointInfoByServicePort groups endpoint info by service port name and address. 232 func (cache *EndpointSliceCache) endpointInfoByServicePort(serviceNN types.NamespacedName, sliceInfoByName endpointSliceInfoByName) spToEndpointMap { 233 endpointInfoBySP := spToEndpointMap{} 234 235 for _, sliceInfo := range sliceInfoByName { 236 for _, port := range sliceInfo.Ports { 237 if port.Name == nil { 238 klog.ErrorS(nil, "Ignoring port with nil name", "portName", port.Name) 239 continue 240 } 241 // TODO: handle nil ports to mean "all" 242 if port.Port == nil || *port.Port == int32(0) { 243 klog.ErrorS(nil, "Ignoring invalid endpoint port", "portName", *port.Name) 244 continue 245 } 246 247 svcPortName := ServicePortName{ 248 NamespacedName: serviceNN, 249 Port: *port.Name, 250 Protocol: *port.Protocol, 251 } 252 253 endpointInfoBySP[svcPortName] = cache.addEndpoints(&svcPortName, int(*port.Port), endpointInfoBySP[svcPortName], sliceInfo.Endpoints) 254 } 255 } 256 257 return endpointInfoBySP 258 } 259 260 // addEndpoints adds endpointInfo for each unique endpoint. 261 func (cache *EndpointSliceCache) addEndpoints(svcPortName *ServicePortName, portNum int, endpointSet map[string]Endpoint, endpoints []*endpointInfo) map[string]Endpoint { 262 if endpointSet == nil { 263 endpointSet = map[string]Endpoint{} 264 } 265 266 // iterate through endpoints to add them to endpointSet. 267 for _, endpoint := range endpoints { 268 if len(endpoint.Addresses) == 0 { 269 klog.ErrorS(nil, "Ignoring invalid endpoint port with empty address", "endpoint", endpoint) 270 continue 271 } 272 273 // Filter out the incorrect IP version case. Any endpoint port that 274 // contains incorrect IP version will be ignored. 275 if (cache.ipFamily == v1.IPv6Protocol) != utilnet.IsIPv6String(endpoint.Addresses[0]) { 276 // Emit event on the corresponding service which had a different IP 277 // version than the endpoint. 278 proxyutil.LogAndEmitIncorrectIPVersionEvent(cache.recorder, "endpointslice", endpoint.Addresses[0], svcPortName.NamespacedName.Namespace, svcPortName.NamespacedName.Name, "") 279 continue 280 } 281 282 isLocal := endpoint.NodeName != nil && cache.isLocal(*endpoint.NodeName) 283 284 endpointInfo := newBaseEndpointInfo(endpoint.Addresses[0], portNum, isLocal, 285 endpoint.Ready, endpoint.Serving, endpoint.Terminating, endpoint.ZoneHints) 286 287 // This logic ensures we're deduplicating potential overlapping endpoints 288 // isLocal should not vary between matching endpoints, but if it does, we 289 // favor a true value here if it exists. 290 if _, exists := endpointSet[endpointInfo.String()]; !exists || isLocal { 291 endpointSet[endpointInfo.String()] = cache.makeEndpointInfo(endpointInfo, svcPortName) 292 } 293 } 294 295 return endpointSet 296 } 297 298 func (cache *EndpointSliceCache) isLocal(hostname string) bool { 299 return len(cache.hostname) > 0 && hostname == cache.hostname 300 } 301 302 // esInfoChanged returns true if the esInfo parameter should be set as a new 303 // pending value in the cache. 304 func (cache *EndpointSliceCache) esInfoChanged(serviceKey types.NamespacedName, sliceKey string, esInfo *endpointSliceInfo) bool { 305 if _, ok := cache.trackerByServiceMap[serviceKey]; ok { 306 appliedInfo, appliedOk := cache.trackerByServiceMap[serviceKey].applied[sliceKey] 307 pendingInfo, pendingOk := cache.trackerByServiceMap[serviceKey].pending[sliceKey] 308 309 // If there's already a pending value, return whether or not this would 310 // change that. 311 if pendingOk { 312 return !reflect.DeepEqual(esInfo, pendingInfo) 313 } 314 315 // If there's already an applied value, return whether or not this would 316 // change that. 317 if appliedOk { 318 return !reflect.DeepEqual(esInfo, appliedInfo) 319 } 320 } 321 322 // If this is marked for removal and does not exist in the cache, no changes 323 // are necessary. 324 if esInfo.Remove { 325 return false 326 } 327 328 // If not in the cache, and not marked for removal, it should be added. 329 return true 330 } 331 332 // endpointsMapFromEndpointInfo computes an endpointsMap from endpointInfo that 333 // has been grouped by service port and IP. 334 func endpointsMapFromEndpointInfo(endpointInfoBySP map[ServicePortName]map[string]Endpoint) EndpointsMap { 335 endpointsMap := EndpointsMap{} 336 337 // transform endpointInfoByServicePort into an endpointsMap with sorted IPs. 338 for svcPortName, endpointSet := range endpointInfoBySP { 339 if len(endpointSet) > 0 { 340 endpointsMap[svcPortName] = []Endpoint{} 341 for _, endpointInfo := range endpointSet { 342 endpointsMap[svcPortName] = append(endpointsMap[svcPortName], endpointInfo) 343 344 } 345 // Ensure endpoints are always returned in the same order to simplify diffing. 346 sort.Sort(byEndpoint(endpointsMap[svcPortName])) 347 348 klog.V(3).InfoS("Setting endpoints for service port name", "portName", svcPortName, "endpoints", formatEndpointsList(endpointsMap[svcPortName])) 349 } 350 } 351 352 return endpointsMap 353 } 354 355 // formatEndpointsList returns a string list converted from an endpoints list. 356 func formatEndpointsList(endpoints []Endpoint) []string { 357 var formattedList []string 358 for _, ep := range endpoints { 359 formattedList = append(formattedList, ep.String()) 360 } 361 return formattedList 362 } 363 364 // endpointSliceCacheKeys returns cache keys used for a given EndpointSlice. 365 func endpointSliceCacheKeys(endpointSlice *discovery.EndpointSlice) (types.NamespacedName, string, error) { 366 var err error 367 serviceName, ok := endpointSlice.Labels[discovery.LabelServiceName] 368 if !ok || serviceName == "" { 369 err = fmt.Errorf("no %s label set on endpoint slice: %s", discovery.LabelServiceName, endpointSlice.Name) 370 } else if endpointSlice.Namespace == "" || endpointSlice.Name == "" { 371 err = fmt.Errorf("expected EndpointSlice name and namespace to be set: %v", endpointSlice) 372 } 373 return types.NamespacedName{Namespace: endpointSlice.Namespace, Name: serviceName}, endpointSlice.Name, err 374 } 375 376 // byAddress helps sort endpointInfo 377 type byAddress []*endpointInfo 378 379 func (e byAddress) Len() int { 380 return len(e) 381 } 382 func (e byAddress) Swap(i, j int) { 383 e[i], e[j] = e[j], e[i] 384 } 385 func (e byAddress) Less(i, j int) bool { 386 return strings.Join(e[i].Addresses, ",") < strings.Join(e[j].Addresses, ",") 387 } 388 389 // byEndpoint helps sort endpoints by endpoint string. 390 type byEndpoint []Endpoint 391 392 func (e byEndpoint) Len() int { 393 return len(e) 394 } 395 func (e byEndpoint) Swap(i, j int) { 396 e[i], e[j] = e[j], e[i] 397 } 398 func (e byEndpoint) Less(i, j int) bool { 399 return e[i].String() < e[j].String() 400 } 401 402 // byPort helps sort EndpointSlice ports by port number 403 type byPort []discovery.EndpointPort 404 405 func (p byPort) Len() int { 406 return len(p) 407 } 408 func (p byPort) Swap(i, j int) { 409 p[i], p[j] = p[j], p[i] 410 } 411 func (p byPort) Less(i, j int) bool { 412 return *p[i].Port < *p[j].Port 413 }