add service selector cache in endpoint controller and endpointSlice controller
This commit is contained in:
@@ -22,9 +22,11 @@ import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
discovery "k8s.io/api/discovery/v1alpha1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
v1listers "k8s.io/client-go/listers/core/v1"
|
||||
@@ -34,6 +36,76 @@ import (
|
||||
"k8s.io/kubernetes/pkg/util/hash"
|
||||
)
|
||||
|
||||
// ServiceSelectorCache is a cache of service selectors to avoid high CPU consumption caused by frequent calls to AsSelectorPreValidated (see #73527)
|
||||
type ServiceSelectorCache struct {
|
||||
lock sync.RWMutex
|
||||
cache map[string]labels.Selector
|
||||
}
|
||||
|
||||
// NewServiceSelectorCache init ServiceSelectorCache for both endpoint controller and endpointSlice controller.
|
||||
func NewServiceSelectorCache() *ServiceSelectorCache {
|
||||
return &ServiceSelectorCache{
|
||||
cache: map[string]labels.Selector{},
|
||||
}
|
||||
}
|
||||
|
||||
// Get return selector and existence in ServiceSelectorCache by key.
|
||||
func (sc *ServiceSelectorCache) Get(key string) (labels.Selector, bool) {
|
||||
sc.lock.RLock()
|
||||
selector, ok := sc.cache[key]
|
||||
// fine-grained lock improves GetPodServiceMemberships performance(16.5%) than defer measured by BenchmarkGetPodServiceMemberships
|
||||
sc.lock.RUnlock()
|
||||
return selector, ok
|
||||
}
|
||||
|
||||
// Update can update or add a selector in ServiceSelectorCache while service's selector changed.
|
||||
func (sc *ServiceSelectorCache) Update(key string, rawSelector map[string]string) labels.Selector {
|
||||
sc.lock.Lock()
|
||||
defer sc.lock.Unlock()
|
||||
selector := labels.Set(rawSelector).AsSelectorPreValidated()
|
||||
sc.cache[key] = selector
|
||||
return selector
|
||||
}
|
||||
|
||||
// Delete can delete selector which exist in ServiceSelectorCache.
|
||||
func (sc *ServiceSelectorCache) Delete(key string) {
|
||||
sc.lock.Lock()
|
||||
defer sc.lock.Unlock()
|
||||
delete(sc.cache, key)
|
||||
}
|
||||
|
||||
// GetPodServiceMemberships returns a set of Service keys for Services that have
|
||||
// a selector matching the given pod.
|
||||
func (sc *ServiceSelectorCache) GetPodServiceMemberships(serviceLister v1listers.ServiceLister, pod *v1.Pod) (sets.String, error) {
|
||||
set := sets.String{}
|
||||
services, err := serviceLister.Services(pod.Namespace).List(labels.Everything())
|
||||
if err != nil {
|
||||
return set, err
|
||||
}
|
||||
|
||||
var selector labels.Selector
|
||||
for _, service := range services {
|
||||
if service.Spec.Selector == nil {
|
||||
// if the service has a nil selector this means selectors match nothing, not everything.
|
||||
continue
|
||||
}
|
||||
key, err := controller.KeyFunc(service)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if v, ok := sc.Get(key); ok {
|
||||
selector = v
|
||||
} else {
|
||||
selector = sc.Update(key, service.Spec.Selector)
|
||||
}
|
||||
|
||||
if selector.Matches(labels.Set(pod.Labels)) {
|
||||
set.Insert(key)
|
||||
}
|
||||
}
|
||||
return set, nil
|
||||
}
|
||||
|
||||
// EndpointsMatch is a type of function that returns true if pod endpoints match.
|
||||
type EndpointsMatch func(*v1.Pod, *v1.Pod) bool
|
||||
|
||||
@@ -100,29 +172,9 @@ func PodChanged(oldPod, newPod *v1.Pod, endpointChanged EndpointsMatch) (bool, b
|
||||
return endpointChanged(newPod, oldPod), labelsChanged
|
||||
}
|
||||
|
||||
// GetPodServiceMemberships returns a set of Service keys for Services that have
|
||||
// a selector matching the given pod.
|
||||
func GetPodServiceMemberships(serviceLister v1listers.ServiceLister, pod *v1.Pod) (sets.String, error) {
|
||||
set := sets.String{}
|
||||
services, err := serviceLister.GetPodServices(pod)
|
||||
if err != nil {
|
||||
// don't log this error because this function makes pointless
|
||||
// errors when no services match
|
||||
return set, nil
|
||||
}
|
||||
for i := range services {
|
||||
key, err := controller.KeyFunc(services[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
set.Insert(key)
|
||||
}
|
||||
return set, nil
|
||||
}
|
||||
|
||||
// GetServicesToUpdateOnPodChange returns a set of Service keys for Services
|
||||
// that have potentially been affected by a change to this pod.
|
||||
func GetServicesToUpdateOnPodChange(serviceLister v1listers.ServiceLister, old, cur interface{}, endpointChanged EndpointsMatch) sets.String {
|
||||
func GetServicesToUpdateOnPodChange(serviceLister v1listers.ServiceLister, selectorCache *ServiceSelectorCache, old, cur interface{}, endpointChanged EndpointsMatch) sets.String {
|
||||
newPod := cur.(*v1.Pod)
|
||||
oldPod := old.(*v1.Pod)
|
||||
if newPod.ResourceVersion == oldPod.ResourceVersion {
|
||||
@@ -138,14 +190,14 @@ func GetServicesToUpdateOnPodChange(serviceLister v1listers.ServiceLister, old,
|
||||
return sets.String{}
|
||||
}
|
||||
|
||||
services, err := GetPodServiceMemberships(serviceLister, newPod)
|
||||
services, err := selectorCache.GetPodServiceMemberships(serviceLister, newPod)
|
||||
if err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("Unable to get pod %s/%s's service memberships: %v", newPod.Namespace, newPod.Name, err))
|
||||
return sets.String{}
|
||||
}
|
||||
|
||||
if labelsChanged {
|
||||
oldServices, err := GetPodServiceMemberships(serviceLister, oldPod)
|
||||
oldServices, err := selectorCache.GetPodServiceMemberships(serviceLister, oldPod)
|
||||
if err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("Unable to get pod %s/%s's service memberships: %v", newPod.Namespace, newPod.Name, err))
|
||||
}
|
||||
|
Reference in New Issue
Block a user