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  }