add service selector cache in endpoint controller and endpointSlice controller

This commit is contained in:
louisgong
2019-10-22 19:57:28 +08:00
parent 74cbf0dc33
commit 2133f9e9ff
6 changed files with 309 additions and 41 deletions

View File

@@ -14,6 +14,7 @@ go_library(
"//pkg/util/hash:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/api/discovery/v1alpha1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/labels:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//staging/src/k8s.io/client-go/listers/core/v1:go_default_library",
@@ -31,8 +32,11 @@ go_test(
deps = [
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/labels:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/intstr:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//staging/src/k8s.io/client-go/informers:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes/fake:go_default_library",
],
)

View File

@@ -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))
}

View File

@@ -17,10 +17,17 @@ limitations under the License.
package endpoint
import (
"fmt"
"reflect"
"testing"
"time"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes/fake"
)
func TestDetermineNeededServiceUpdates(t *testing.T) {
@@ -224,3 +231,171 @@ func TestShouldPodBeInEndpoints(t *testing.T) {
}
}
}
func TestServiceSelectorCache_GetPodServiceMemberships(t *testing.T) {
fakeInformerFactory := informers.NewSharedInformerFactory(&fake.Clientset{}, 0*time.Second)
for i := 0; i < 3; i++ {
service := &v1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: fmt.Sprintf("service-%d", i),
Namespace: "test",
},
Spec: v1.ServiceSpec{
Selector: map[string]string{
"app": fmt.Sprintf("test-%d", i),
},
},
}
fakeInformerFactory.Core().V1().Services().Informer().GetStore().Add(service)
}
var pods []*v1.Pod
for i := 0; i < 5; i++ {
pod := &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Namespace: "test",
Name: fmt.Sprintf("test-pod-%d", i),
Labels: map[string]string{
"app": fmt.Sprintf("test-%d", i),
"label": fmt.Sprintf("label-%d", i),
},
},
}
pods = append(pods, pod)
}
cache := NewServiceSelectorCache()
tests := []struct {
name string
pod *v1.Pod
expect sets.String
}{
{
name: "get servicesMemberships for pod-0",
pod: pods[0],
expect: sets.NewString("test/service-0"),
},
{
name: "get servicesMemberships for pod-1",
pod: pods[1],
expect: sets.NewString("test/service-1"),
},
{
name: "get servicesMemberships for pod-2",
pod: pods[2],
expect: sets.NewString("test/service-2"),
},
{
name: "get servicesMemberships for pod-3",
pod: pods[3],
expect: sets.NewString(),
},
{
name: "get servicesMemberships for pod-4",
pod: pods[4],
expect: sets.NewString(),
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
services, err := cache.GetPodServiceMemberships(fakeInformerFactory.Core().V1().Services().Lister(), test.pod)
if err != nil {
t.Errorf("Error from cache.GetPodServiceMemberships: %v", err)
} else if !services.Equal(test.expect) {
t.Errorf("Expect service %v, but got %v", test.expect, services)
}
})
}
}
func TestServiceSelectorCache_Update(t *testing.T) {
var selectors []labels.Selector
for i := 0; i < 5; i++ {
selector := labels.Set(map[string]string{"app": fmt.Sprintf("test-%d", i)}).AsSelectorPreValidated()
selectors = append(selectors, selector)
}
tests := []struct {
name string
key string
cache *ServiceSelectorCache
update map[string]string
expect labels.Selector
}{
{
name: "add test/service-0",
key: "test/service-0",
cache: generateServiceSelectorCache(map[string]labels.Selector{}),
update: map[string]string{"app": "test-0"},
expect: selectors[0],
},
{
name: "add test/service-1",
key: "test/service-1",
cache: generateServiceSelectorCache(map[string]labels.Selector{"test/service-0": selectors[0]}),
update: map[string]string{"app": "test-1"},
expect: selectors[1],
},
{
name: "update test/service-2",
key: "test/service-2",
cache: generateServiceSelectorCache(map[string]labels.Selector{"test/service-2": selectors[2]}),
update: map[string]string{"app": "test-0"},
expect: selectors[0],
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
selector := test.cache.Update(test.key, test.update)
if !reflect.DeepEqual(selector, test.expect) {
t.Errorf("Expect selector %v , but got %v", test.expect, selector)
}
})
}
}
func generateServiceSelectorCache(cache map[string]labels.Selector) *ServiceSelectorCache {
return &ServiceSelectorCache{
cache: cache,
}
}
func BenchmarkGetPodServiceMemberships(b *testing.B) {
// init fake service informer.
fakeInformerFactory := informers.NewSharedInformerFactory(&fake.Clientset{}, 0*time.Second)
for i := 0; i < 1000; i++ {
service := &v1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: fmt.Sprintf("service-%d", i),
Namespace: "test",
},
Spec: v1.ServiceSpec{
Selector: map[string]string{
"app": fmt.Sprintf("test-%d", i),
},
},
}
fakeInformerFactory.Core().V1().Services().Informer().GetStore().Add(service)
}
pod := &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Namespace: "test",
Name: "test-pod-0",
Labels: map[string]string{
"app": "test-0",
},
},
}
cache := NewServiceSelectorCache()
expect := sets.NewString("test/service-0")
b.ResetTimer()
for i := 0; i < b.N; i++ {
services, err := cache.GetPodServiceMemberships(fakeInformerFactory.Core().V1().Services().Lister(), pod)
if err != nil {
b.Fatalf("Error from GetPodServiceMemberships(): %v", err)
}
if len(services) != len(expect) {
b.Errorf("Expect services size %d, but got: %v", len(expect), len(services))
}
}
}