k8s.io/kubernetes@v1.29.3/pkg/proxy/service.go (about) 1 /* 2 Copyright 2017 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 "net" 22 "reflect" 23 "strings" 24 "sync" 25 26 "k8s.io/client-go/tools/events" 27 "k8s.io/klog/v2" 28 netutils "k8s.io/utils/net" 29 30 v1 "k8s.io/api/core/v1" 31 "k8s.io/apimachinery/pkg/types" 32 "k8s.io/apimachinery/pkg/util/sets" 33 apiservice "k8s.io/kubernetes/pkg/api/v1/service" 34 "k8s.io/kubernetes/pkg/proxy/metrics" 35 proxyutil "k8s.io/kubernetes/pkg/proxy/util" 36 ) 37 38 // BaseServicePortInfo contains base information that defines a service. 39 // This could be used directly by proxier while processing services, 40 // or can be used for constructing a more specific ServiceInfo struct 41 // defined by the proxier if needed. 42 type BaseServicePortInfo struct { 43 clusterIP net.IP 44 port int 45 protocol v1.Protocol 46 nodePort int 47 loadBalancerVIPs []string 48 sessionAffinityType v1.ServiceAffinity 49 stickyMaxAgeSeconds int 50 externalIPs []string 51 loadBalancerSourceRanges []string 52 healthCheckNodePort int 53 externalPolicyLocal bool 54 internalPolicyLocal bool 55 internalTrafficPolicy *v1.ServiceInternalTrafficPolicy 56 hintsAnnotation string 57 } 58 59 var _ ServicePort = &BaseServicePortInfo{} 60 61 // String is part of ServicePort interface. 62 func (bsvcPortInfo *BaseServicePortInfo) String() string { 63 return fmt.Sprintf("%s:%d/%s", bsvcPortInfo.clusterIP, bsvcPortInfo.port, bsvcPortInfo.protocol) 64 } 65 66 // ClusterIP is part of ServicePort interface. 67 func (bsvcPortInfo *BaseServicePortInfo) ClusterIP() net.IP { 68 return bsvcPortInfo.clusterIP 69 } 70 71 // Port is part of ServicePort interface. 72 func (bsvcPortInfo *BaseServicePortInfo) Port() int { 73 return bsvcPortInfo.port 74 } 75 76 // SessionAffinityType is part of the ServicePort interface. 77 func (bsvcPortInfo *BaseServicePortInfo) SessionAffinityType() v1.ServiceAffinity { 78 return bsvcPortInfo.sessionAffinityType 79 } 80 81 // StickyMaxAgeSeconds is part of the ServicePort interface 82 func (bsvcPortInfo *BaseServicePortInfo) StickyMaxAgeSeconds() int { 83 return bsvcPortInfo.stickyMaxAgeSeconds 84 } 85 86 // Protocol is part of ServicePort interface. 87 func (bsvcPortInfo *BaseServicePortInfo) Protocol() v1.Protocol { 88 return bsvcPortInfo.protocol 89 } 90 91 // LoadBalancerSourceRanges is part of ServicePort interface 92 func (bsvcPortInfo *BaseServicePortInfo) LoadBalancerSourceRanges() []string { 93 return bsvcPortInfo.loadBalancerSourceRanges 94 } 95 96 // HealthCheckNodePort is part of ServicePort interface. 97 func (bsvcPortInfo *BaseServicePortInfo) HealthCheckNodePort() int { 98 return bsvcPortInfo.healthCheckNodePort 99 } 100 101 // NodePort is part of the ServicePort interface. 102 func (bsvcPortInfo *BaseServicePortInfo) NodePort() int { 103 return bsvcPortInfo.nodePort 104 } 105 106 // ExternalIPStrings is part of ServicePort interface. 107 func (bsvcPortInfo *BaseServicePortInfo) ExternalIPStrings() []string { 108 return bsvcPortInfo.externalIPs 109 } 110 111 // LoadBalancerVIPStrings is part of ServicePort interface. 112 func (bsvcPortInfo *BaseServicePortInfo) LoadBalancerVIPStrings() []string { 113 return bsvcPortInfo.loadBalancerVIPs 114 } 115 116 // ExternalPolicyLocal is part of ServicePort interface. 117 func (bsvcPortInfo *BaseServicePortInfo) ExternalPolicyLocal() bool { 118 return bsvcPortInfo.externalPolicyLocal 119 } 120 121 // InternalPolicyLocal is part of ServicePort interface 122 func (bsvcPortInfo *BaseServicePortInfo) InternalPolicyLocal() bool { 123 return bsvcPortInfo.internalPolicyLocal 124 } 125 126 // InternalTrafficPolicy is part of ServicePort interface 127 func (bsvcPortInfo *BaseServicePortInfo) InternalTrafficPolicy() *v1.ServiceInternalTrafficPolicy { 128 return bsvcPortInfo.internalTrafficPolicy 129 } 130 131 // HintsAnnotation is part of ServicePort interface. 132 func (bsvcPortInfo *BaseServicePortInfo) HintsAnnotation() string { 133 return bsvcPortInfo.hintsAnnotation 134 } 135 136 // ExternallyAccessible is part of ServicePort interface. 137 func (bsvcPortInfo *BaseServicePortInfo) ExternallyAccessible() bool { 138 return bsvcPortInfo.nodePort != 0 || len(bsvcPortInfo.loadBalancerVIPs) != 0 || len(bsvcPortInfo.externalIPs) != 0 139 } 140 141 // UsesClusterEndpoints is part of ServicePort interface. 142 func (bsvcPortInfo *BaseServicePortInfo) UsesClusterEndpoints() bool { 143 // The service port uses Cluster endpoints if the internal traffic policy is "Cluster", 144 // or if it accepts external traffic at all. (Even if the external traffic policy is 145 // "Local", we need Cluster endpoints to implement short circuiting.) 146 return !bsvcPortInfo.internalPolicyLocal || bsvcPortInfo.ExternallyAccessible() 147 } 148 149 // UsesLocalEndpoints is part of ServicePort interface. 150 func (bsvcPortInfo *BaseServicePortInfo) UsesLocalEndpoints() bool { 151 return bsvcPortInfo.internalPolicyLocal || (bsvcPortInfo.externalPolicyLocal && bsvcPortInfo.ExternallyAccessible()) 152 } 153 154 func (sct *ServiceChangeTracker) newBaseServiceInfo(port *v1.ServicePort, service *v1.Service) *BaseServicePortInfo { 155 externalPolicyLocal := apiservice.ExternalPolicyLocal(service) 156 internalPolicyLocal := apiservice.InternalPolicyLocal(service) 157 158 var stickyMaxAgeSeconds int 159 if service.Spec.SessionAffinity == v1.ServiceAffinityClientIP { 160 // Kube-apiserver side guarantees SessionAffinityConfig won't be nil when session affinity type is ClientIP 161 stickyMaxAgeSeconds = int(*service.Spec.SessionAffinityConfig.ClientIP.TimeoutSeconds) 162 } 163 164 clusterIP := proxyutil.GetClusterIPByFamily(sct.ipFamily, service) 165 info := &BaseServicePortInfo{ 166 clusterIP: netutils.ParseIPSloppy(clusterIP), 167 port: int(port.Port), 168 protocol: port.Protocol, 169 nodePort: int(port.NodePort), 170 sessionAffinityType: service.Spec.SessionAffinity, 171 stickyMaxAgeSeconds: stickyMaxAgeSeconds, 172 externalPolicyLocal: externalPolicyLocal, 173 internalPolicyLocal: internalPolicyLocal, 174 internalTrafficPolicy: service.Spec.InternalTrafficPolicy, 175 } 176 177 // v1.DeprecatedAnnotationTopologyAwareHints has precedence over v1.AnnotationTopologyMode. 178 var ok bool 179 info.hintsAnnotation, ok = service.Annotations[v1.DeprecatedAnnotationTopologyAwareHints] 180 if !ok { 181 info.hintsAnnotation, _ = service.Annotations[v1.AnnotationTopologyMode] 182 } 183 184 loadBalancerSourceRanges := make([]string, len(service.Spec.LoadBalancerSourceRanges)) 185 for i, sourceRange := range service.Spec.LoadBalancerSourceRanges { 186 loadBalancerSourceRanges[i] = strings.TrimSpace(sourceRange) 187 } 188 // filter external ips, source ranges and ingress ips 189 // prior to dual stack services, this was considered an error, but with dual stack 190 // services, this is actually expected. Hence we downgraded from reporting by events 191 // to just log lines with high verbosity 192 193 ipFamilyMap := proxyutil.MapIPsByIPFamily(service.Spec.ExternalIPs) 194 info.externalIPs = ipFamilyMap[sct.ipFamily] 195 196 // Log the IPs not matching the ipFamily 197 if ips, ok := ipFamilyMap[proxyutil.OtherIPFamily(sct.ipFamily)]; ok && len(ips) > 0 { 198 klog.V(4).InfoS("Service change tracker ignored the following external IPs for given service as they don't match IP Family", 199 "ipFamily", sct.ipFamily, "externalIPs", strings.Join(ips, ", "), "service", klog.KObj(service)) 200 } 201 202 ipFamilyMap = proxyutil.MapCIDRsByIPFamily(loadBalancerSourceRanges) 203 info.loadBalancerSourceRanges = ipFamilyMap[sct.ipFamily] 204 // Log the CIDRs not matching the ipFamily 205 if cidrs, ok := ipFamilyMap[proxyutil.OtherIPFamily(sct.ipFamily)]; ok && len(cidrs) > 0 { 206 klog.V(4).InfoS("Service change tracker ignored the following load balancer source ranges for given Service as they don't match IP Family", 207 "ipFamily", sct.ipFamily, "loadBalancerSourceRanges", strings.Join(cidrs, ", "), "service", klog.KObj(service)) 208 } 209 210 // Obtain Load Balancer Ingress 211 var invalidIPs []string 212 for _, ing := range service.Status.LoadBalancer.Ingress { 213 if ing.IP == "" { 214 continue 215 } 216 217 // proxy mode load balancers do not need to track the IPs in the service cache 218 // and they can also implement IP family translation, so no need to check if 219 // the status ingress.IP and the ClusterIP belong to the same family. 220 if !proxyutil.IsVIPMode(ing) { 221 klog.V(4).InfoS("Service change tracker ignored the following load balancer ingress IP for given Service as it using Proxy mode", 222 "ipFamily", sct.ipFamily, "loadBalancerIngressIP", ing.IP, "service", klog.KObj(service)) 223 continue 224 } 225 226 // kube-proxy does not implement IP family translation, skip addresses with 227 // different IP family 228 if ipFamily := proxyutil.GetIPFamilyFromIP(ing.IP); ipFamily == sct.ipFamily { 229 info.loadBalancerVIPs = append(info.loadBalancerVIPs, ing.IP) 230 } else { 231 invalidIPs = append(invalidIPs, ing.IP) 232 } 233 } 234 if len(invalidIPs) > 0 { 235 klog.V(4).InfoS("Service change tracker ignored the following load balancer ingress IPs for given Service as they don't match the IP Family", 236 "ipFamily", sct.ipFamily, "loadBalancerIngressIPs", strings.Join(invalidIPs, ", "), "service", klog.KObj(service)) 237 } 238 239 if apiservice.NeedsHealthCheck(service) { 240 p := service.Spec.HealthCheckNodePort 241 if p == 0 { 242 klog.ErrorS(nil, "Service has no healthcheck nodeport", "service", klog.KObj(service)) 243 } else { 244 info.healthCheckNodePort = int(p) 245 } 246 } 247 248 return info 249 } 250 251 type makeServicePortFunc func(*v1.ServicePort, *v1.Service, *BaseServicePortInfo) ServicePort 252 253 // This handler is invoked by the apply function on every change. This function should not modify the 254 // ServicePortMap's but just use the changes for any Proxier specific cleanup. 255 type processServiceMapChangeFunc func(previous, current ServicePortMap) 256 257 // serviceChange contains all changes to services that happened since proxy rules were synced. For a single object, 258 // changes are accumulated, i.e. previous is state from before applying the changes, 259 // current is state after applying all of the changes. 260 type serviceChange struct { 261 previous ServicePortMap 262 current ServicePortMap 263 } 264 265 // ServiceChangeTracker carries state about uncommitted changes to an arbitrary number of 266 // Services, keyed by their namespace and name. 267 type ServiceChangeTracker struct { 268 // lock protects items. 269 lock sync.Mutex 270 // items maps a service to its serviceChange. 271 items map[types.NamespacedName]*serviceChange 272 // makeServiceInfo allows proxier to inject customized information when processing service. 273 makeServiceInfo makeServicePortFunc 274 processServiceMapChange processServiceMapChangeFunc 275 ipFamily v1.IPFamily 276 277 recorder events.EventRecorder 278 } 279 280 // NewServiceChangeTracker initializes a ServiceChangeTracker 281 func NewServiceChangeTracker(makeServiceInfo makeServicePortFunc, ipFamily v1.IPFamily, recorder events.EventRecorder, processServiceMapChange processServiceMapChangeFunc) *ServiceChangeTracker { 282 return &ServiceChangeTracker{ 283 items: make(map[types.NamespacedName]*serviceChange), 284 makeServiceInfo: makeServiceInfo, 285 recorder: recorder, 286 ipFamily: ipFamily, 287 processServiceMapChange: processServiceMapChange, 288 } 289 } 290 291 // Update updates given service's change map based on the <previous, current> service pair. It returns true if items changed, 292 // otherwise return false. Update can be used to add/update/delete items of ServiceChangeMap. For example, 293 // Add item 294 // - pass <nil, service> as the <previous, current> pair. 295 // 296 // Update item 297 // - pass <oldService, service> as the <previous, current> pair. 298 // 299 // Delete item 300 // - pass <service, nil> as the <previous, current> pair. 301 func (sct *ServiceChangeTracker) Update(previous, current *v1.Service) bool { 302 // This is unexpected, we should return false directly. 303 if previous == nil && current == nil { 304 return false 305 } 306 307 svc := current 308 if svc == nil { 309 svc = previous 310 } 311 metrics.ServiceChangesTotal.Inc() 312 namespacedName := types.NamespacedName{Namespace: svc.Namespace, Name: svc.Name} 313 314 sct.lock.Lock() 315 defer sct.lock.Unlock() 316 317 change, exists := sct.items[namespacedName] 318 if !exists { 319 change = &serviceChange{} 320 change.previous = sct.serviceToServiceMap(previous) 321 sct.items[namespacedName] = change 322 } 323 change.current = sct.serviceToServiceMap(current) 324 // if change.previous equal to change.current, it means no change 325 if reflect.DeepEqual(change.previous, change.current) { 326 delete(sct.items, namespacedName) 327 } else { 328 klog.V(4).InfoS("Service updated ports", "service", klog.KObj(svc), "portCount", len(change.current)) 329 } 330 metrics.ServiceChangesPending.Set(float64(len(sct.items))) 331 return len(sct.items) > 0 332 } 333 334 // UpdateServiceMapResult is the updated results after applying service changes. 335 type UpdateServiceMapResult struct { 336 // UpdatedServices lists the names of all services added/updated/deleted since the 337 // last Update. 338 UpdatedServices sets.Set[types.NamespacedName] 339 340 // DeletedUDPClusterIPs holds stale (no longer assigned to a Service) Service IPs 341 // that had UDP ports. Callers can use this to abort timeout-waits or clear 342 // connection-tracking information. 343 DeletedUDPClusterIPs sets.Set[string] 344 } 345 346 // HealthCheckNodePorts returns a map of Service names to HealthCheckNodePort values 347 // for all Services in sm with non-zero HealthCheckNodePort. 348 func (sm ServicePortMap) HealthCheckNodePorts() map[types.NamespacedName]uint16 { 349 // TODO: If this will appear to be computationally expensive, consider 350 // computing this incrementally similarly to svcPortMap. 351 ports := make(map[types.NamespacedName]uint16) 352 for svcPortName, info := range sm { 353 if info.HealthCheckNodePort() != 0 { 354 ports[svcPortName.NamespacedName] = uint16(info.HealthCheckNodePort()) 355 } 356 } 357 return ports 358 } 359 360 // ServicePortMap maps a service to its ServicePort. 361 type ServicePortMap map[ServicePortName]ServicePort 362 363 // serviceToServiceMap translates a single Service object to a ServicePortMap. 364 // 365 // NOTE: service object should NOT be modified. 366 func (sct *ServiceChangeTracker) serviceToServiceMap(service *v1.Service) ServicePortMap { 367 if service == nil { 368 return nil 369 } 370 371 if proxyutil.ShouldSkipService(service) { 372 return nil 373 } 374 375 clusterIP := proxyutil.GetClusterIPByFamily(sct.ipFamily, service) 376 if clusterIP == "" { 377 return nil 378 } 379 380 svcPortMap := make(ServicePortMap) 381 svcName := types.NamespacedName{Namespace: service.Namespace, Name: service.Name} 382 for i := range service.Spec.Ports { 383 servicePort := &service.Spec.Ports[i] 384 svcPortName := ServicePortName{NamespacedName: svcName, Port: servicePort.Name, Protocol: servicePort.Protocol} 385 baseSvcInfo := sct.newBaseServiceInfo(servicePort, service) 386 if sct.makeServiceInfo != nil { 387 svcPortMap[svcPortName] = sct.makeServiceInfo(servicePort, service, baseSvcInfo) 388 } else { 389 svcPortMap[svcPortName] = baseSvcInfo 390 } 391 } 392 return svcPortMap 393 } 394 395 // Update updates ServicePortMap base on the given changes, returns information about the 396 // diff since the last Update, triggers processServiceMapChange on every change, and 397 // clears the changes map. 398 func (sm ServicePortMap) Update(sct *ServiceChangeTracker) UpdateServiceMapResult { 399 sct.lock.Lock() 400 defer sct.lock.Unlock() 401 402 result := UpdateServiceMapResult{ 403 UpdatedServices: sets.New[types.NamespacedName](), 404 DeletedUDPClusterIPs: sets.New[string](), 405 } 406 407 for nn, change := range sct.items { 408 if sct.processServiceMapChange != nil { 409 sct.processServiceMapChange(change.previous, change.current) 410 } 411 result.UpdatedServices.Insert(nn) 412 413 sm.merge(change.current) 414 // filter out the Update event of current changes from previous changes 415 // before calling unmerge() so that can skip deleting the Update events. 416 change.previous.filter(change.current) 417 sm.unmerge(change.previous, result.DeletedUDPClusterIPs) 418 } 419 // clear changes after applying them to ServicePortMap. 420 sct.items = make(map[types.NamespacedName]*serviceChange) 421 metrics.ServiceChangesPending.Set(0) 422 423 return result 424 } 425 426 // merge adds other ServicePortMap's elements to current ServicePortMap. 427 // If collision, other ALWAYS win. Otherwise add the other to current. 428 // In other words, if some elements in current collisions with other, update the current by other. 429 // It returns a string type set which stores all the newly merged services' identifier, ServicePortName.String(), to help users 430 // tell if a service is deleted or updated. 431 // The returned value is one of the arguments of ServicePortMap.unmerge(). 432 // ServicePortMap A Merge ServicePortMap B will do following 2 things: 433 // - update ServicePortMap A. 434 // - produce a string set which stores all other ServicePortMap's ServicePortName.String(). 435 // 436 // For example, 437 // 438 // A{} 439 // B{{"ns", "cluster-ip", "http"}: {"172.16.55.10", 1234, "TCP"}} 440 // A updated to be {{"ns", "cluster-ip", "http"}: {"172.16.55.10", 1234, "TCP"}} 441 // produce string set {"ns/cluster-ip:http"} 442 // 443 // A{{"ns", "cluster-ip", "http"}: {"172.16.55.10", 345, "UDP"}} 444 // B{{"ns", "cluster-ip", "http"}: {"172.16.55.10", 1234, "TCP"}} 445 // A updated to be {{"ns", "cluster-ip", "http"}: {"172.16.55.10", 1234, "TCP"}} 446 // produce string set {"ns/cluster-ip:http"} 447 func (sm *ServicePortMap) merge(other ServicePortMap) sets.Set[string] { 448 // existingPorts is going to store all identifiers of all services in `other` ServicePortMap. 449 existingPorts := sets.New[string]() 450 for svcPortName, info := range other { 451 // Take ServicePortName.String() as the newly merged service's identifier and put it into existingPorts. 452 existingPorts.Insert(svcPortName.String()) 453 _, exists := (*sm)[svcPortName] 454 if !exists { 455 klog.V(4).InfoS("Adding new service port", "portName", svcPortName, "servicePort", info) 456 } else { 457 klog.V(4).InfoS("Updating existing service port", "portName", svcPortName, "servicePort", info) 458 } 459 (*sm)[svcPortName] = info 460 } 461 return existingPorts 462 } 463 464 // filter filters out elements from ServicePortMap base on given ports string sets. 465 func (sm *ServicePortMap) filter(other ServicePortMap) { 466 for svcPortName := range *sm { 467 // skip the delete for Update event. 468 if _, ok := other[svcPortName]; ok { 469 delete(*sm, svcPortName) 470 } 471 } 472 } 473 474 // unmerge deletes all other ServicePortMap's elements from current ServicePortMap and 475 // updates deletedUDPClusterIPs with all of the newly-deleted UDP cluster IPs. 476 func (sm *ServicePortMap) unmerge(other ServicePortMap, deletedUDPClusterIPs sets.Set[string]) { 477 for svcPortName := range other { 478 info, exists := (*sm)[svcPortName] 479 if exists { 480 klog.V(4).InfoS("Removing service port", "portName", svcPortName) 481 if info.Protocol() == v1.ProtocolUDP { 482 deletedUDPClusterIPs.Insert(info.ClusterIP().String()) 483 } 484 delete(*sm, svcPortName) 485 } else { 486 klog.ErrorS(nil, "Service port does not exists", "portName", svcPortName) 487 } 488 } 489 }