Split ServicePort/Endpoint from ServiceChangeTracker/EndpointsChangeTracker
Move the ServicePort/BaseServicePortInfo types to serviceport.go. Move the Endpoint/BaseEndpointInfo types to endpoint.go. To avoid confusion with the new filenames, rename service.go to servicechangetracker.go and endpoints.go to endpointschangetracker.go. (No code changes; this just moves some code from types.go and services.go to serviceport.go, and some code from types.go and endpoints.go to endpoint.go.)
This commit is contained in:
		
							
								
								
									
										138
									
								
								pkg/proxy/endpoint.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										138
									
								
								pkg/proxy/endpoint.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,138 @@ | ||||
| /* | ||||
| Copyright 2017 The Kubernetes Authors. | ||||
|  | ||||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| you may not use this file except in compliance with the License. | ||||
| You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
| Unless required by applicable law or agreed to in writing, software | ||||
| distributed under the License is distributed on an "AS IS" BASIS, | ||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
|  | ||||
| package proxy | ||||
|  | ||||
| import ( | ||||
| 	"net" | ||||
| 	"strconv" | ||||
|  | ||||
| 	"k8s.io/apimachinery/pkg/util/sets" | ||||
| ) | ||||
|  | ||||
| // Endpoint in an interface which abstracts information about an endpoint. | ||||
| type Endpoint interface { | ||||
| 	// String returns endpoint string.  An example format can be: `IP:Port`. | ||||
| 	// We take the returned value as ServiceEndpoint.Endpoint. | ||||
| 	String() string | ||||
| 	// IP returns IP part of the endpoint. | ||||
| 	IP() string | ||||
| 	// Port returns the Port part of the endpoint. | ||||
| 	Port() int | ||||
|  | ||||
| 	// IsLocal returns true if the endpoint is running on the same host as kube-proxy. | ||||
| 	IsLocal() bool | ||||
| 	// IsReady returns true if an endpoint is ready and not terminating, or | ||||
| 	// if PublishNotReadyAddresses is set on the service. | ||||
| 	IsReady() bool | ||||
| 	// IsServing returns true if an endpoint is ready. It does not account | ||||
| 	// for terminating state. | ||||
| 	IsServing() bool | ||||
| 	// IsTerminating returns true if an endpoint is terminating. For pods, | ||||
| 	// that is any pod with a deletion timestamp. | ||||
| 	IsTerminating() bool | ||||
|  | ||||
| 	// ZoneHints returns the zone hint for the endpoint. This is based on | ||||
| 	// endpoint.hints.forZones[0].name in the EndpointSlice API. | ||||
| 	ZoneHints() sets.Set[string] | ||||
| } | ||||
|  | ||||
| // BaseEndpointInfo contains base information that defines an endpoint. | ||||
| // This could be used directly by proxier while processing endpoints, | ||||
| // or can be used for constructing a more specific EndpointInfo struct | ||||
| // defined by the proxier if needed. | ||||
| type BaseEndpointInfo struct { | ||||
| 	// Cache this values to improve performance | ||||
| 	ip   string | ||||
| 	port int | ||||
| 	// endpoint is the same as net.JoinHostPort(ip,port) | ||||
| 	endpoint string | ||||
|  | ||||
| 	// isLocal indicates whether the endpoint is running on same host as kube-proxy. | ||||
| 	isLocal bool | ||||
|  | ||||
| 	// ready indicates whether this endpoint is ready and NOT terminating, unless | ||||
| 	// PublishNotReadyAddresses is set on the service, in which case it will just | ||||
| 	// always be true. | ||||
| 	ready bool | ||||
| 	// serving indicates whether this endpoint is ready regardless of its terminating state. | ||||
| 	// For pods this is true if it has a ready status regardless of its deletion timestamp. | ||||
| 	serving bool | ||||
| 	// terminating indicates whether this endpoint is terminating. | ||||
| 	// For pods this is true if it has a non-nil deletion timestamp. | ||||
| 	terminating bool | ||||
|  | ||||
| 	// zoneHints represent the zone hints for the endpoint. This is based on | ||||
| 	// endpoint.hints.forZones[*].name in the EndpointSlice API. | ||||
| 	zoneHints sets.Set[string] | ||||
| } | ||||
|  | ||||
| var _ Endpoint = &BaseEndpointInfo{} | ||||
|  | ||||
| // String is part of proxy.Endpoint interface. | ||||
| func (info *BaseEndpointInfo) String() string { | ||||
| 	return info.endpoint | ||||
| } | ||||
|  | ||||
| // IP returns just the IP part of the endpoint, it's a part of proxy.Endpoint interface. | ||||
| func (info *BaseEndpointInfo) IP() string { | ||||
| 	return info.ip | ||||
| } | ||||
|  | ||||
| // Port returns just the Port part of the endpoint. | ||||
| func (info *BaseEndpointInfo) Port() int { | ||||
| 	return info.port | ||||
| } | ||||
|  | ||||
| // IsLocal is part of proxy.Endpoint interface. | ||||
| func (info *BaseEndpointInfo) IsLocal() bool { | ||||
| 	return info.isLocal | ||||
| } | ||||
|  | ||||
| // IsReady returns true if an endpoint is ready and not terminating. | ||||
| func (info *BaseEndpointInfo) IsReady() bool { | ||||
| 	return info.ready | ||||
| } | ||||
|  | ||||
| // IsServing returns true if an endpoint is ready, regardless of if the | ||||
| // endpoint is terminating. | ||||
| func (info *BaseEndpointInfo) IsServing() bool { | ||||
| 	return info.serving | ||||
| } | ||||
|  | ||||
| // IsTerminating retruns true if an endpoint is terminating. For pods, | ||||
| // that is any pod with a deletion timestamp. | ||||
| func (info *BaseEndpointInfo) IsTerminating() bool { | ||||
| 	return info.terminating | ||||
| } | ||||
|  | ||||
| // ZoneHints returns the zone hint for the endpoint. | ||||
| func (info *BaseEndpointInfo) ZoneHints() sets.Set[string] { | ||||
| 	return info.zoneHints | ||||
| } | ||||
|  | ||||
| func newBaseEndpointInfo(ip string, port int, isLocal, ready, serving, terminating bool, zoneHints sets.Set[string]) *BaseEndpointInfo { | ||||
| 	return &BaseEndpointInfo{ | ||||
| 		ip:          ip, | ||||
| 		port:        port, | ||||
| 		endpoint:    net.JoinHostPort(ip, strconv.Itoa(port)), | ||||
| 		isLocal:     isLocal, | ||||
| 		ready:       ready, | ||||
| 		serving:     serving, | ||||
| 		terminating: terminating, | ||||
| 		zoneHints:   zoneHints, | ||||
| 	} | ||||
| } | ||||
| @@ -17,18 +17,15 @@ limitations under the License. | ||||
| package proxy | ||||
| 
 | ||||
| import ( | ||||
| 	"net" | ||||
| 	"strconv" | ||||
| 	"sync" | ||||
| 	"time" | ||||
| 
 | ||||
| 	"k8s.io/client-go/tools/events" | ||||
| 	"k8s.io/klog/v2" | ||||
| 
 | ||||
| 	v1 "k8s.io/api/core/v1" | ||||
| 	discovery "k8s.io/api/discovery/v1" | ||||
| 	"k8s.io/apimachinery/pkg/types" | ||||
| 	"k8s.io/apimachinery/pkg/util/sets" | ||||
| 	"k8s.io/client-go/tools/events" | ||||
| 	"k8s.io/klog/v2" | ||||
| 	"k8s.io/kubernetes/pkg/proxy/metrics" | ||||
| ) | ||||
| 
 | ||||
| @@ -37,93 +34,6 @@ var supportedEndpointSliceAddressTypes = sets.New[string]( | ||||
| 	string(discovery.AddressTypeIPv6), | ||||
| ) | ||||
| 
 | ||||
| // BaseEndpointInfo contains base information that defines an endpoint. | ||||
| // This could be used directly by proxier while processing endpoints, | ||||
| // or can be used for constructing a more specific EndpointInfo struct | ||||
| // defined by the proxier if needed. | ||||
| type BaseEndpointInfo struct { | ||||
| 	// Cache this values to improve performance | ||||
| 	ip   string | ||||
| 	port int | ||||
| 	// endpoint is the same as net.JoinHostPort(ip,port) | ||||
| 	endpoint string | ||||
| 
 | ||||
| 	// isLocal indicates whether the endpoint is running on same host as kube-proxy. | ||||
| 	isLocal bool | ||||
| 
 | ||||
| 	// ready indicates whether this endpoint is ready and NOT terminating, unless | ||||
| 	// PublishNotReadyAddresses is set on the service, in which case it will just | ||||
| 	// always be true. | ||||
| 	ready bool | ||||
| 	// serving indicates whether this endpoint is ready regardless of its terminating state. | ||||
| 	// For pods this is true if it has a ready status regardless of its deletion timestamp. | ||||
| 	serving bool | ||||
| 	// terminating indicates whether this endpoint is terminating. | ||||
| 	// For pods this is true if it has a non-nil deletion timestamp. | ||||
| 	terminating bool | ||||
| 
 | ||||
| 	// zoneHints represent the zone hints for the endpoint. This is based on | ||||
| 	// endpoint.hints.forZones[*].name in the EndpointSlice API. | ||||
| 	zoneHints sets.Set[string] | ||||
| } | ||||
| 
 | ||||
| var _ Endpoint = &BaseEndpointInfo{} | ||||
| 
 | ||||
| // String is part of proxy.Endpoint interface. | ||||
| func (info *BaseEndpointInfo) String() string { | ||||
| 	return info.endpoint | ||||
| } | ||||
| 
 | ||||
| // IP returns just the IP part of the endpoint, it's a part of proxy.Endpoint interface. | ||||
| func (info *BaseEndpointInfo) IP() string { | ||||
| 	return info.ip | ||||
| } | ||||
| 
 | ||||
| // Port returns just the Port part of the endpoint. | ||||
| func (info *BaseEndpointInfo) Port() int { | ||||
| 	return info.port | ||||
| } | ||||
| 
 | ||||
| // IsLocal is part of proxy.Endpoint interface. | ||||
| func (info *BaseEndpointInfo) IsLocal() bool { | ||||
| 	return info.isLocal | ||||
| } | ||||
| 
 | ||||
| // IsReady returns true if an endpoint is ready and not terminating. | ||||
| func (info *BaseEndpointInfo) IsReady() bool { | ||||
| 	return info.ready | ||||
| } | ||||
| 
 | ||||
| // IsServing returns true if an endpoint is ready, regardless of if the | ||||
| // endpoint is terminating. | ||||
| func (info *BaseEndpointInfo) IsServing() bool { | ||||
| 	return info.serving | ||||
| } | ||||
| 
 | ||||
| // IsTerminating retruns true if an endpoint is terminating. For pods, | ||||
| // that is any pod with a deletion timestamp. | ||||
| func (info *BaseEndpointInfo) IsTerminating() bool { | ||||
| 	return info.terminating | ||||
| } | ||||
| 
 | ||||
| // ZoneHints returns the zone hint for the endpoint. | ||||
| func (info *BaseEndpointInfo) ZoneHints() sets.Set[string] { | ||||
| 	return info.zoneHints | ||||
| } | ||||
| 
 | ||||
| func newBaseEndpointInfo(ip string, port int, isLocal, ready, serving, terminating bool, zoneHints sets.Set[string]) *BaseEndpointInfo { | ||||
| 	return &BaseEndpointInfo{ | ||||
| 		ip:          ip, | ||||
| 		port:        port, | ||||
| 		endpoint:    net.JoinHostPort(ip, strconv.Itoa(port)), | ||||
| 		isLocal:     isLocal, | ||||
| 		ready:       ready, | ||||
| 		serving:     serving, | ||||
| 		terminating: terminating, | ||||
| 		zoneHints:   zoneHints, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| type makeEndpointFunc func(info *BaseEndpointInfo, svcPortName *ServicePortName) Endpoint | ||||
| 
 | ||||
| // This handler is invoked by the apply function on every change. This function should not modify the | ||||
							
								
								
									
										247
									
								
								pkg/proxy/servicechangetracker.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										247
									
								
								pkg/proxy/servicechangetracker.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,247 @@ | ||||
| /* | ||||
| Copyright 2017 The Kubernetes Authors. | ||||
|  | ||||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| you may not use this file except in compliance with the License. | ||||
| You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
| Unless required by applicable law or agreed to in writing, software | ||||
| distributed under the License is distributed on an "AS IS" BASIS, | ||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
|  | ||||
| package proxy | ||||
|  | ||||
| import ( | ||||
| 	"reflect" | ||||
| 	"sync" | ||||
|  | ||||
| 	v1 "k8s.io/api/core/v1" | ||||
| 	"k8s.io/apimachinery/pkg/types" | ||||
| 	"k8s.io/apimachinery/pkg/util/sets" | ||||
| 	"k8s.io/client-go/tools/events" | ||||
| 	"k8s.io/klog/v2" | ||||
| 	"k8s.io/kubernetes/pkg/proxy/metrics" | ||||
| 	proxyutil "k8s.io/kubernetes/pkg/proxy/util" | ||||
| ) | ||||
|  | ||||
| type makeServicePortFunc func(*v1.ServicePort, *v1.Service, *BaseServicePortInfo) ServicePort | ||||
|  | ||||
| // This handler is invoked by the apply function on every change. This function should not modify the | ||||
| // ServicePortMap's but just use the changes for any Proxier specific cleanup. | ||||
| type processServiceMapChangeFunc func(previous, current ServicePortMap) | ||||
|  | ||||
| // serviceChange contains all changes to services that happened since proxy rules were synced.  For a single object, | ||||
| // changes are accumulated, i.e. previous is state from before applying the changes, | ||||
| // current is state after applying all of the changes. | ||||
| type serviceChange struct { | ||||
| 	previous ServicePortMap | ||||
| 	current  ServicePortMap | ||||
| } | ||||
|  | ||||
| // ServiceChangeTracker carries state about uncommitted changes to an arbitrary number of | ||||
| // Services, keyed by their namespace and name. | ||||
| type ServiceChangeTracker struct { | ||||
| 	// lock protects items. | ||||
| 	lock sync.Mutex | ||||
| 	// items maps a service to its serviceChange. | ||||
| 	items map[types.NamespacedName]*serviceChange | ||||
| 	// makeServiceInfo allows proxier to inject customized information when processing service. | ||||
| 	makeServiceInfo         makeServicePortFunc | ||||
| 	processServiceMapChange processServiceMapChangeFunc | ||||
| 	ipFamily                v1.IPFamily | ||||
|  | ||||
| 	recorder events.EventRecorder | ||||
| } | ||||
|  | ||||
| // NewServiceChangeTracker initializes a ServiceChangeTracker | ||||
| func NewServiceChangeTracker(makeServiceInfo makeServicePortFunc, ipFamily v1.IPFamily, recorder events.EventRecorder, processServiceMapChange processServiceMapChangeFunc) *ServiceChangeTracker { | ||||
| 	return &ServiceChangeTracker{ | ||||
| 		items:                   make(map[types.NamespacedName]*serviceChange), | ||||
| 		makeServiceInfo:         makeServiceInfo, | ||||
| 		recorder:                recorder, | ||||
| 		ipFamily:                ipFamily, | ||||
| 		processServiceMapChange: processServiceMapChange, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Update updates given service's change map based on the <previous, current> service pair.  It returns true if items changed, | ||||
| // otherwise return false.  Update can be used to add/update/delete items of ServiceChangeMap.  For example, | ||||
| // Add item | ||||
| //   - pass <nil, service> as the <previous, current> pair. | ||||
| // | ||||
| // Update item | ||||
| //   - pass <oldService, service> as the <previous, current> pair. | ||||
| // | ||||
| // Delete item | ||||
| //   - pass <service, nil> as the <previous, current> pair. | ||||
| func (sct *ServiceChangeTracker) Update(previous, current *v1.Service) bool { | ||||
| 	// This is unexpected, we should return false directly. | ||||
| 	if previous == nil && current == nil { | ||||
| 		return false | ||||
| 	} | ||||
|  | ||||
| 	svc := current | ||||
| 	if svc == nil { | ||||
| 		svc = previous | ||||
| 	} | ||||
| 	metrics.ServiceChangesTotal.Inc() | ||||
| 	namespacedName := types.NamespacedName{Namespace: svc.Namespace, Name: svc.Name} | ||||
|  | ||||
| 	sct.lock.Lock() | ||||
| 	defer sct.lock.Unlock() | ||||
|  | ||||
| 	change, exists := sct.items[namespacedName] | ||||
| 	if !exists { | ||||
| 		change = &serviceChange{} | ||||
| 		change.previous = sct.serviceToServiceMap(previous) | ||||
| 		sct.items[namespacedName] = change | ||||
| 	} | ||||
| 	change.current = sct.serviceToServiceMap(current) | ||||
| 	// if change.previous equal to change.current, it means no change | ||||
| 	if reflect.DeepEqual(change.previous, change.current) { | ||||
| 		delete(sct.items, namespacedName) | ||||
| 	} else { | ||||
| 		klog.V(4).InfoS("Service updated ports", "service", klog.KObj(svc), "portCount", len(change.current)) | ||||
| 	} | ||||
| 	metrics.ServiceChangesPending.Set(float64(len(sct.items))) | ||||
| 	return len(sct.items) > 0 | ||||
| } | ||||
|  | ||||
| // UpdateServiceMapResult is the updated results after applying service changes. | ||||
| type UpdateServiceMapResult struct { | ||||
| 	// UpdatedServices lists the names of all services added/updated/deleted since the | ||||
| 	// last Update. | ||||
| 	UpdatedServices sets.Set[types.NamespacedName] | ||||
|  | ||||
| 	// DeletedUDPClusterIPs holds stale (no longer assigned to a Service) Service IPs | ||||
| 	// that had UDP ports. Callers can use this to abort timeout-waits or clear | ||||
| 	// connection-tracking information. | ||||
| 	DeletedUDPClusterIPs sets.Set[string] | ||||
| } | ||||
|  | ||||
| // HealthCheckNodePorts returns a map of Service names to HealthCheckNodePort values | ||||
| // for all Services in sm with non-zero HealthCheckNodePort. | ||||
| func (sm ServicePortMap) HealthCheckNodePorts() map[types.NamespacedName]uint16 { | ||||
| 	// TODO: If this will appear to be computationally expensive, consider | ||||
| 	// computing this incrementally similarly to svcPortMap. | ||||
| 	ports := make(map[types.NamespacedName]uint16) | ||||
| 	for svcPortName, info := range sm { | ||||
| 		if info.HealthCheckNodePort() != 0 { | ||||
| 			ports[svcPortName.NamespacedName] = uint16(info.HealthCheckNodePort()) | ||||
| 		} | ||||
| 	} | ||||
| 	return ports | ||||
| } | ||||
|  | ||||
| // ServicePortMap maps a service to its ServicePort. | ||||
| type ServicePortMap map[ServicePortName]ServicePort | ||||
|  | ||||
| // serviceToServiceMap translates a single Service object to a ServicePortMap. | ||||
| // | ||||
| // NOTE: service object should NOT be modified. | ||||
| func (sct *ServiceChangeTracker) serviceToServiceMap(service *v1.Service) ServicePortMap { | ||||
| 	if service == nil { | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	if proxyutil.ShouldSkipService(service) { | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	clusterIP := proxyutil.GetClusterIPByFamily(sct.ipFamily, service) | ||||
| 	if clusterIP == "" { | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	svcPortMap := make(ServicePortMap) | ||||
| 	svcName := types.NamespacedName{Namespace: service.Namespace, Name: service.Name} | ||||
| 	for i := range service.Spec.Ports { | ||||
| 		servicePort := &service.Spec.Ports[i] | ||||
| 		svcPortName := ServicePortName{NamespacedName: svcName, Port: servicePort.Name, Protocol: servicePort.Protocol} | ||||
| 		baseSvcInfo := newBaseServiceInfo(service, sct.ipFamily, servicePort) | ||||
| 		if sct.makeServiceInfo != nil { | ||||
| 			svcPortMap[svcPortName] = sct.makeServiceInfo(servicePort, service, baseSvcInfo) | ||||
| 		} else { | ||||
| 			svcPortMap[svcPortName] = baseSvcInfo | ||||
| 		} | ||||
| 	} | ||||
| 	return svcPortMap | ||||
| } | ||||
|  | ||||
| // Update updates ServicePortMap base on the given changes, returns information about the | ||||
| // diff since the last Update, triggers processServiceMapChange on every change, and | ||||
| // clears the changes map. | ||||
| func (sm ServicePortMap) Update(sct *ServiceChangeTracker) UpdateServiceMapResult { | ||||
| 	sct.lock.Lock() | ||||
| 	defer sct.lock.Unlock() | ||||
|  | ||||
| 	result := UpdateServiceMapResult{ | ||||
| 		UpdatedServices:      sets.New[types.NamespacedName](), | ||||
| 		DeletedUDPClusterIPs: sets.New[string](), | ||||
| 	} | ||||
|  | ||||
| 	for nn, change := range sct.items { | ||||
| 		if sct.processServiceMapChange != nil { | ||||
| 			sct.processServiceMapChange(change.previous, change.current) | ||||
| 		} | ||||
| 		result.UpdatedServices.Insert(nn) | ||||
|  | ||||
| 		sm.merge(change.current) | ||||
| 		// filter out the Update event of current changes from previous changes | ||||
| 		// before calling unmerge() so that can skip deleting the Update events. | ||||
| 		change.previous.filter(change.current) | ||||
| 		sm.unmerge(change.previous, result.DeletedUDPClusterIPs) | ||||
| 	} | ||||
| 	// clear changes after applying them to ServicePortMap. | ||||
| 	sct.items = make(map[types.NamespacedName]*serviceChange) | ||||
| 	metrics.ServiceChangesPending.Set(0) | ||||
|  | ||||
| 	return result | ||||
| } | ||||
|  | ||||
| // merge adds other ServicePortMap's elements to current ServicePortMap. | ||||
| // If collision, other ALWAYS win. Otherwise add the other to current. | ||||
| // In other words, if some elements in current collisions with other, update the current by other. | ||||
| func (sm *ServicePortMap) merge(other ServicePortMap) { | ||||
| 	for svcPortName, info := range other { | ||||
| 		_, exists := (*sm)[svcPortName] | ||||
| 		if !exists { | ||||
| 			klog.V(4).InfoS("Adding new service port", "portName", svcPortName, "servicePort", info) | ||||
| 		} else { | ||||
| 			klog.V(4).InfoS("Updating existing service port", "portName", svcPortName, "servicePort", info) | ||||
| 		} | ||||
| 		(*sm)[svcPortName] = info | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // filter filters out elements from ServicePortMap base on given ports string sets. | ||||
| func (sm *ServicePortMap) filter(other ServicePortMap) { | ||||
| 	for svcPortName := range *sm { | ||||
| 		// skip the delete for Update event. | ||||
| 		if _, ok := other[svcPortName]; ok { | ||||
| 			delete(*sm, svcPortName) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // unmerge deletes all other ServicePortMap's elements from current ServicePortMap and | ||||
| // updates deletedUDPClusterIPs with all of the newly-deleted UDP cluster IPs. | ||||
| func (sm *ServicePortMap) unmerge(other ServicePortMap, deletedUDPClusterIPs sets.Set[string]) { | ||||
| 	for svcPortName := range other { | ||||
| 		info, exists := (*sm)[svcPortName] | ||||
| 		if exists { | ||||
| 			klog.V(4).InfoS("Removing service port", "portName", svcPortName) | ||||
| 			if info.Protocol() == v1.ProtocolUDP { | ||||
| 				deletedUDPClusterIPs.Insert(info.ClusterIP().String()) | ||||
| 			} | ||||
| 			delete(*sm, svcPortName) | ||||
| 		} else { | ||||
| 			klog.ErrorS(nil, "Service port does not exists", "portName", svcPortName) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @@ -19,22 +19,56 @@ package proxy | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"net" | ||||
| 	"reflect" | ||||
| 	"strings" | ||||
| 	"sync" | ||||
| 
 | ||||
| 	"k8s.io/client-go/tools/events" | ||||
| 	"k8s.io/klog/v2" | ||||
| 	netutils "k8s.io/utils/net" | ||||
| 
 | ||||
| 	v1 "k8s.io/api/core/v1" | ||||
| 	"k8s.io/apimachinery/pkg/types" | ||||
| 	"k8s.io/apimachinery/pkg/util/sets" | ||||
| 	"k8s.io/klog/v2" | ||||
| 	apiservice "k8s.io/kubernetes/pkg/api/v1/service" | ||||
| 	"k8s.io/kubernetes/pkg/proxy/metrics" | ||||
| 	proxyutil "k8s.io/kubernetes/pkg/proxy/util" | ||||
| 	netutils "k8s.io/utils/net" | ||||
| ) | ||||
| 
 | ||||
| // ServicePort is an interface which abstracts information about a service. | ||||
| type ServicePort interface { | ||||
| 	// String returns service string.  An example format can be: `IP:Port/Protocol`. | ||||
| 	String() string | ||||
| 	// ClusterIP returns service cluster IP in net.IP format. | ||||
| 	ClusterIP() net.IP | ||||
| 	// Port returns service port if present. If return 0 means not present. | ||||
| 	Port() int | ||||
| 	// SessionAffinityType returns service session affinity type | ||||
| 	SessionAffinityType() v1.ServiceAffinity | ||||
| 	// StickyMaxAgeSeconds returns service max connection age | ||||
| 	StickyMaxAgeSeconds() int | ||||
| 	// ExternalIPStrings returns service ExternalIPs as a string array. | ||||
| 	ExternalIPStrings() []string | ||||
| 	// LoadBalancerVIPStrings returns service LoadBalancerIPs which are VIP mode as a string array. | ||||
| 	LoadBalancerVIPStrings() []string | ||||
| 	// Protocol returns service protocol. | ||||
| 	Protocol() v1.Protocol | ||||
| 	// LoadBalancerSourceRanges returns service LoadBalancerSourceRanges if present empty array if not | ||||
| 	LoadBalancerSourceRanges() []string | ||||
| 	// HealthCheckNodePort returns service health check node port if present.  If return 0, it means not present. | ||||
| 	HealthCheckNodePort() int | ||||
| 	// NodePort returns a service Node port if present. If return 0, it means not present. | ||||
| 	NodePort() int | ||||
| 	// ExternalPolicyLocal returns if a service has only node local endpoints for external traffic. | ||||
| 	ExternalPolicyLocal() bool | ||||
| 	// InternalPolicyLocal returns if a service has only node local endpoints for internal traffic. | ||||
| 	InternalPolicyLocal() bool | ||||
| 	// HintsAnnotation returns the value of the v1.DeprecatedAnnotationTopologyAwareHints annotation. | ||||
| 	HintsAnnotation() string | ||||
| 	// ExternallyAccessible returns true if the service port is reachable via something | ||||
| 	// other than ClusterIP (NodePort/ExternalIP/LoadBalancer) | ||||
| 	ExternallyAccessible() bool | ||||
| 	// UsesClusterEndpoints returns true if the service port ever sends traffic to | ||||
| 	// endpoints based on "Cluster" traffic policy | ||||
| 	UsesClusterEndpoints() bool | ||||
| 	// UsesLocalEndpoints returns true if the service port ever sends traffic to | ||||
| 	// endpoints based on "Local" traffic policy | ||||
| 	UsesLocalEndpoints() bool | ||||
| } | ||||
| 
 | ||||
| // BaseServicePortInfo contains base information that defines a service. | ||||
| // This could be used directly by proxier while processing services, | ||||
| // or can be used for constructing a more specific ServiceInfo struct | ||||
| @@ -240,220 +274,3 @@ func newBaseServiceInfo(service *v1.Service, ipFamily v1.IPFamily, port *v1.Serv | ||||
| 
 | ||||
| 	return info | ||||
| } | ||||
| 
 | ||||
| type makeServicePortFunc func(*v1.ServicePort, *v1.Service, *BaseServicePortInfo) ServicePort | ||||
| 
 | ||||
| // This handler is invoked by the apply function on every change. This function should not modify the | ||||
| // ServicePortMap's but just use the changes for any Proxier specific cleanup. | ||||
| type processServiceMapChangeFunc func(previous, current ServicePortMap) | ||||
| 
 | ||||
| // serviceChange contains all changes to services that happened since proxy rules were synced.  For a single object, | ||||
| // changes are accumulated, i.e. previous is state from before applying the changes, | ||||
| // current is state after applying all of the changes. | ||||
| type serviceChange struct { | ||||
| 	previous ServicePortMap | ||||
| 	current  ServicePortMap | ||||
| } | ||||
| 
 | ||||
| // ServiceChangeTracker carries state about uncommitted changes to an arbitrary number of | ||||
| // Services, keyed by their namespace and name. | ||||
| type ServiceChangeTracker struct { | ||||
| 	// lock protects items. | ||||
| 	lock sync.Mutex | ||||
| 	// items maps a service to its serviceChange. | ||||
| 	items map[types.NamespacedName]*serviceChange | ||||
| 	// makeServiceInfo allows proxier to inject customized information when processing service. | ||||
| 	makeServiceInfo         makeServicePortFunc | ||||
| 	processServiceMapChange processServiceMapChangeFunc | ||||
| 	ipFamily                v1.IPFamily | ||||
| 
 | ||||
| 	recorder events.EventRecorder | ||||
| } | ||||
| 
 | ||||
| // NewServiceChangeTracker initializes a ServiceChangeTracker | ||||
| func NewServiceChangeTracker(makeServiceInfo makeServicePortFunc, ipFamily v1.IPFamily, recorder events.EventRecorder, processServiceMapChange processServiceMapChangeFunc) *ServiceChangeTracker { | ||||
| 	return &ServiceChangeTracker{ | ||||
| 		items:                   make(map[types.NamespacedName]*serviceChange), | ||||
| 		makeServiceInfo:         makeServiceInfo, | ||||
| 		recorder:                recorder, | ||||
| 		ipFamily:                ipFamily, | ||||
| 		processServiceMapChange: processServiceMapChange, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // Update updates given service's change map based on the <previous, current> service pair.  It returns true if items changed, | ||||
| // otherwise return false.  Update can be used to add/update/delete items of ServiceChangeMap.  For example, | ||||
| // Add item | ||||
| //   - pass <nil, service> as the <previous, current> pair. | ||||
| // | ||||
| // Update item | ||||
| //   - pass <oldService, service> as the <previous, current> pair. | ||||
| // | ||||
| // Delete item | ||||
| //   - pass <service, nil> as the <previous, current> pair. | ||||
| func (sct *ServiceChangeTracker) Update(previous, current *v1.Service) bool { | ||||
| 	// This is unexpected, we should return false directly. | ||||
| 	if previous == nil && current == nil { | ||||
| 		return false | ||||
| 	} | ||||
| 
 | ||||
| 	svc := current | ||||
| 	if svc == nil { | ||||
| 		svc = previous | ||||
| 	} | ||||
| 	metrics.ServiceChangesTotal.Inc() | ||||
| 	namespacedName := types.NamespacedName{Namespace: svc.Namespace, Name: svc.Name} | ||||
| 
 | ||||
| 	sct.lock.Lock() | ||||
| 	defer sct.lock.Unlock() | ||||
| 
 | ||||
| 	change, exists := sct.items[namespacedName] | ||||
| 	if !exists { | ||||
| 		change = &serviceChange{} | ||||
| 		change.previous = sct.serviceToServiceMap(previous) | ||||
| 		sct.items[namespacedName] = change | ||||
| 	} | ||||
| 	change.current = sct.serviceToServiceMap(current) | ||||
| 	// if change.previous equal to change.current, it means no change | ||||
| 	if reflect.DeepEqual(change.previous, change.current) { | ||||
| 		delete(sct.items, namespacedName) | ||||
| 	} else { | ||||
| 		klog.V(4).InfoS("Service updated ports", "service", klog.KObj(svc), "portCount", len(change.current)) | ||||
| 	} | ||||
| 	metrics.ServiceChangesPending.Set(float64(len(sct.items))) | ||||
| 	return len(sct.items) > 0 | ||||
| } | ||||
| 
 | ||||
| // UpdateServiceMapResult is the updated results after applying service changes. | ||||
| type UpdateServiceMapResult struct { | ||||
| 	// UpdatedServices lists the names of all services added/updated/deleted since the | ||||
| 	// last Update. | ||||
| 	UpdatedServices sets.Set[types.NamespacedName] | ||||
| 
 | ||||
| 	// DeletedUDPClusterIPs holds stale (no longer assigned to a Service) Service IPs | ||||
| 	// that had UDP ports. Callers can use this to abort timeout-waits or clear | ||||
| 	// connection-tracking information. | ||||
| 	DeletedUDPClusterIPs sets.Set[string] | ||||
| } | ||||
| 
 | ||||
| // HealthCheckNodePorts returns a map of Service names to HealthCheckNodePort values | ||||
| // for all Services in sm with non-zero HealthCheckNodePort. | ||||
| func (sm ServicePortMap) HealthCheckNodePorts() map[types.NamespacedName]uint16 { | ||||
| 	// TODO: If this will appear to be computationally expensive, consider | ||||
| 	// computing this incrementally similarly to svcPortMap. | ||||
| 	ports := make(map[types.NamespacedName]uint16) | ||||
| 	for svcPortName, info := range sm { | ||||
| 		if info.HealthCheckNodePort() != 0 { | ||||
| 			ports[svcPortName.NamespacedName] = uint16(info.HealthCheckNodePort()) | ||||
| 		} | ||||
| 	} | ||||
| 	return ports | ||||
| } | ||||
| 
 | ||||
| // ServicePortMap maps a service to its ServicePort. | ||||
| type ServicePortMap map[ServicePortName]ServicePort | ||||
| 
 | ||||
| // serviceToServiceMap translates a single Service object to a ServicePortMap. | ||||
| // | ||||
| // NOTE: service object should NOT be modified. | ||||
| func (sct *ServiceChangeTracker) serviceToServiceMap(service *v1.Service) ServicePortMap { | ||||
| 	if service == nil { | ||||
| 		return nil | ||||
| 	} | ||||
| 
 | ||||
| 	if proxyutil.ShouldSkipService(service) { | ||||
| 		return nil | ||||
| 	} | ||||
| 
 | ||||
| 	clusterIP := proxyutil.GetClusterIPByFamily(sct.ipFamily, service) | ||||
| 	if clusterIP == "" { | ||||
| 		return nil | ||||
| 	} | ||||
| 
 | ||||
| 	svcPortMap := make(ServicePortMap) | ||||
| 	svcName := types.NamespacedName{Namespace: service.Namespace, Name: service.Name} | ||||
| 	for i := range service.Spec.Ports { | ||||
| 		servicePort := &service.Spec.Ports[i] | ||||
| 		svcPortName := ServicePortName{NamespacedName: svcName, Port: servicePort.Name, Protocol: servicePort.Protocol} | ||||
| 		baseSvcInfo := newBaseServiceInfo(service, sct.ipFamily, servicePort) | ||||
| 		if sct.makeServiceInfo != nil { | ||||
| 			svcPortMap[svcPortName] = sct.makeServiceInfo(servicePort, service, baseSvcInfo) | ||||
| 		} else { | ||||
| 			svcPortMap[svcPortName] = baseSvcInfo | ||||
| 		} | ||||
| 	} | ||||
| 	return svcPortMap | ||||
| } | ||||
| 
 | ||||
| // Update updates ServicePortMap base on the given changes, returns information about the | ||||
| // diff since the last Update, triggers processServiceMapChange on every change, and | ||||
| // clears the changes map. | ||||
| func (sm ServicePortMap) Update(sct *ServiceChangeTracker) UpdateServiceMapResult { | ||||
| 	sct.lock.Lock() | ||||
| 	defer sct.lock.Unlock() | ||||
| 
 | ||||
| 	result := UpdateServiceMapResult{ | ||||
| 		UpdatedServices:      sets.New[types.NamespacedName](), | ||||
| 		DeletedUDPClusterIPs: sets.New[string](), | ||||
| 	} | ||||
| 
 | ||||
| 	for nn, change := range sct.items { | ||||
| 		if sct.processServiceMapChange != nil { | ||||
| 			sct.processServiceMapChange(change.previous, change.current) | ||||
| 		} | ||||
| 		result.UpdatedServices.Insert(nn) | ||||
| 
 | ||||
| 		sm.merge(change.current) | ||||
| 		// filter out the Update event of current changes from previous changes | ||||
| 		// before calling unmerge() so that can skip deleting the Update events. | ||||
| 		change.previous.filter(change.current) | ||||
| 		sm.unmerge(change.previous, result.DeletedUDPClusterIPs) | ||||
| 	} | ||||
| 	// clear changes after applying them to ServicePortMap. | ||||
| 	sct.items = make(map[types.NamespacedName]*serviceChange) | ||||
| 	metrics.ServiceChangesPending.Set(0) | ||||
| 
 | ||||
| 	return result | ||||
| } | ||||
| 
 | ||||
| // merge adds other ServicePortMap's elements to current ServicePortMap. | ||||
| // If collision, other ALWAYS win. Otherwise add the other to current. | ||||
| // In other words, if some elements in current collisions with other, update the current by other. | ||||
| func (sm *ServicePortMap) merge(other ServicePortMap) { | ||||
| 	for svcPortName, info := range other { | ||||
| 		_, exists := (*sm)[svcPortName] | ||||
| 		if !exists { | ||||
| 			klog.V(4).InfoS("Adding new service port", "portName", svcPortName, "servicePort", info) | ||||
| 		} else { | ||||
| 			klog.V(4).InfoS("Updating existing service port", "portName", svcPortName, "servicePort", info) | ||||
| 		} | ||||
| 		(*sm)[svcPortName] = info | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // filter filters out elements from ServicePortMap base on given ports string sets. | ||||
| func (sm *ServicePortMap) filter(other ServicePortMap) { | ||||
| 	for svcPortName := range *sm { | ||||
| 		// skip the delete for Update event. | ||||
| 		if _, ok := other[svcPortName]; ok { | ||||
| 			delete(*sm, svcPortName) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // unmerge deletes all other ServicePortMap's elements from current ServicePortMap and | ||||
| // updates deletedUDPClusterIPs with all of the newly-deleted UDP cluster IPs. | ||||
| func (sm *ServicePortMap) unmerge(other ServicePortMap, deletedUDPClusterIPs sets.Set[string]) { | ||||
| 	for svcPortName := range other { | ||||
| 		info, exists := (*sm)[svcPortName] | ||||
| 		if exists { | ||||
| 			klog.V(4).InfoS("Removing service port", "portName", svcPortName) | ||||
| 			if info.Protocol() == v1.ProtocolUDP { | ||||
| 				deletedUDPClusterIPs.Insert(info.ClusterIP().String()) | ||||
| 			} | ||||
| 			delete(*sm, svcPortName) | ||||
| 		} else { | ||||
| 			klog.ErrorS(nil, "Service port does not exists", "portName", svcPortName) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @@ -18,11 +18,9 @@ package proxy | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"net" | ||||
|  | ||||
| 	v1 "k8s.io/api/core/v1" | ||||
| 	"k8s.io/apimachinery/pkg/types" | ||||
| 	"k8s.io/apimachinery/pkg/util/sets" | ||||
| 	"k8s.io/kubernetes/pkg/proxy/config" | ||||
| ) | ||||
|  | ||||
| @@ -59,75 +57,6 @@ func fmtPortName(in string) string { | ||||
| 	return fmt.Sprintf(":%s", in) | ||||
| } | ||||
|  | ||||
| // ServicePort is an interface which abstracts information about a service. | ||||
| type ServicePort interface { | ||||
| 	// String returns service string.  An example format can be: `IP:Port/Protocol`. | ||||
| 	String() string | ||||
| 	// ClusterIP returns service cluster IP in net.IP format. | ||||
| 	ClusterIP() net.IP | ||||
| 	// Port returns service port if present. If return 0 means not present. | ||||
| 	Port() int | ||||
| 	// SessionAffinityType returns service session affinity type | ||||
| 	SessionAffinityType() v1.ServiceAffinity | ||||
| 	// StickyMaxAgeSeconds returns service max connection age | ||||
| 	StickyMaxAgeSeconds() int | ||||
| 	// ExternalIPStrings returns service ExternalIPs as a string array. | ||||
| 	ExternalIPStrings() []string | ||||
| 	// LoadBalancerVIPStrings returns service LoadBalancerIPs which are VIP mode as a string array. | ||||
| 	LoadBalancerVIPStrings() []string | ||||
| 	// Protocol returns service protocol. | ||||
| 	Protocol() v1.Protocol | ||||
| 	// LoadBalancerSourceRanges returns service LoadBalancerSourceRanges if present empty array if not | ||||
| 	LoadBalancerSourceRanges() []string | ||||
| 	// HealthCheckNodePort returns service health check node port if present.  If return 0, it means not present. | ||||
| 	HealthCheckNodePort() int | ||||
| 	// NodePort returns a service Node port if present. If return 0, it means not present. | ||||
| 	NodePort() int | ||||
| 	// ExternalPolicyLocal returns if a service has only node local endpoints for external traffic. | ||||
| 	ExternalPolicyLocal() bool | ||||
| 	// InternalPolicyLocal returns if a service has only node local endpoints for internal traffic. | ||||
| 	InternalPolicyLocal() bool | ||||
| 	// HintsAnnotation returns the value of the v1.DeprecatedAnnotationTopologyAwareHints annotation. | ||||
| 	HintsAnnotation() string | ||||
| 	// ExternallyAccessible returns true if the service port is reachable via something | ||||
| 	// other than ClusterIP (NodePort/ExternalIP/LoadBalancer) | ||||
| 	ExternallyAccessible() bool | ||||
| 	// UsesClusterEndpoints returns true if the service port ever sends traffic to | ||||
| 	// endpoints based on "Cluster" traffic policy | ||||
| 	UsesClusterEndpoints() bool | ||||
| 	// UsesLocalEndpoints returns true if the service port ever sends traffic to | ||||
| 	// endpoints based on "Local" traffic policy | ||||
| 	UsesLocalEndpoints() bool | ||||
| } | ||||
|  | ||||
| // Endpoint in an interface which abstracts information about an endpoint. | ||||
| // TODO: Rename functions to be consistent with ServicePort. | ||||
| type Endpoint interface { | ||||
| 	// String returns endpoint string.  An example format can be: `IP:Port`. | ||||
| 	// We take the returned value as ServiceEndpoint.Endpoint. | ||||
| 	String() string | ||||
| 	// IP returns IP part of the endpoint. | ||||
| 	IP() string | ||||
| 	// Port returns the Port part of the endpoint. | ||||
| 	Port() int | ||||
|  | ||||
| 	// IsLocal returns true if the endpoint is running on the same host as kube-proxy. | ||||
| 	IsLocal() bool | ||||
| 	// IsReady returns true if an endpoint is ready and not terminating, or | ||||
| 	// if PublishNotReadyAddresses is set on the service. | ||||
| 	IsReady() bool | ||||
| 	// IsServing returns true if an endpoint is ready. It does not account | ||||
| 	// for terminating state. | ||||
| 	IsServing() bool | ||||
| 	// IsTerminating returns true if an endpoint is terminating. For pods, | ||||
| 	// that is any pod with a deletion timestamp. | ||||
| 	IsTerminating() bool | ||||
|  | ||||
| 	// ZoneHints returns the zone hint for the endpoint. This is based on | ||||
| 	// endpoint.hints.forZones[0].name in the EndpointSlice API. | ||||
| 	ZoneHints() sets.Set[string] | ||||
| } | ||||
|  | ||||
| // ServiceEndpoint is used to identify a service and one of its endpoint pair. | ||||
| type ServiceEndpoint struct { | ||||
| 	Endpoint        string | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Dan Winship
					Dan Winship