From d8bd150784bb4825ae891dd0ec84625bdba0f2b8 Mon Sep 17 00:00:00 2001 From: ahutsunshine Date: Wed, 29 Nov 2023 15:51:24 +0800 Subject: [PATCH] support pod namespace indexer fix comments optimize code small optimization for the namespace scope check --- pkg/features/kube_features.go | 8 ++ pkg/registry/core/pod/strategy.go | 21 ++++- .../apiserver/pkg/storage/cacher/cacher.go | 2 +- .../pkg/storage/selection_predicate.go | 18 ++++- .../pkg/storage/selection_predicate_test.go | 80 ++++++++++++++++++- 5 files changed, 124 insertions(+), 5 deletions(-) diff --git a/pkg/features/kube_features.go b/pkg/features/kube_features.go index 21990f2919ac..4576a3e4a745 100644 --- a/pkg/features/kube_features.go +++ b/pkg/features/kube_features.go @@ -975,6 +975,12 @@ const ( // will not graduate or be enabled by default in future Kubernetes // releases. UserNamespacesPodSecurityStandards featuregate.Feature = "UserNamespacesPodSecurityStandards" + + // owner: @ahutsunshine + // beta: v1.29 + // + // Allows namespace indexer for namespace scope resources in apiserver cache to accelerate list operations. + StorageNamespaceIndex featuregate.Feature = "StorageNamespaceIndex" ) func init() { @@ -1281,4 +1287,6 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS // features that enable backwards compatibility but are scheduled to be removed // ... HPAScaleToZero: {Default: false, PreRelease: featuregate.Alpha}, + + StorageNamespaceIndex: {Default: true, PreRelease: featuregate.Beta}, } diff --git a/pkg/registry/core/pod/strategy.go b/pkg/registry/core/pod/strategy.go index c31ee7b01805..c34568abd223 100644 --- a/pkg/registry/core/pod/strategy.go +++ b/pkg/registry/core/pod/strategy.go @@ -289,11 +289,15 @@ func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) { // MatchPod returns a generic matcher for a given label and field selector. func MatchPod(label labels.Selector, field fields.Selector) storage.SelectionPredicate { + var indexFields = []string{"spec.nodeName"} + if utilfeature.DefaultFeatureGate.Enabled(features.StorageNamespaceIndex) { + indexFields = append(indexFields, "metadata.namespace") + } return storage.SelectionPredicate{ Label: label, Field: field, GetAttrs: GetAttrs, - IndexFields: []string{"spec.nodeName"}, + IndexFields: indexFields, } } @@ -311,11 +315,24 @@ func NodeNameIndexFunc(obj interface{}) ([]string, error) { return []string{pod.Spec.NodeName}, nil } +// NamespaceIndexFunc return value name of given object. +func NamespaceIndexFunc(obj interface{}) ([]string, error) { + pod, ok := obj.(*api.Pod) + if !ok { + return nil, fmt.Errorf("not a pod") + } + return []string{pod.Namespace}, nil +} + // Indexers returns the indexers for pod storage. func Indexers() *cache.Indexers { - return &cache.Indexers{ + var indexers = cache.Indexers{ storage.FieldIndex("spec.nodeName"): NodeNameIndexFunc, } + if utilfeature.DefaultFeatureGate.Enabled(features.StorageNamespaceIndex) { + indexers[storage.FieldIndex("metadata.namespace")] = NamespaceIndexFunc + } + return &indexers } // ToSelectableFields returns a field set that represents the object diff --git a/staging/src/k8s.io/apiserver/pkg/storage/cacher/cacher.go b/staging/src/k8s.io/apiserver/pkg/storage/cacher/cacher.go index 4f408044197c..3778a83c0fc3 100644 --- a/staging/src/k8s.io/apiserver/pkg/storage/cacher/cacher.go +++ b/staging/src/k8s.io/apiserver/pkg/storage/cacher/cacher.go @@ -746,7 +746,7 @@ func (c *Cacher) listItems(ctx context.Context, listRV uint64, key string, pred } return nil, readResourceVersion, "", nil } - return c.watchCache.WaitUntilFreshAndList(ctx, listRV, pred.MatcherIndex()) + return c.watchCache.WaitUntilFreshAndList(ctx, listRV, pred.MatcherIndex(ctx)) } // GetList implements storage.Interface diff --git a/staging/src/k8s.io/apiserver/pkg/storage/selection_predicate.go b/staging/src/k8s.io/apiserver/pkg/storage/selection_predicate.go index a0a14366f2a2..e652845c28f9 100644 --- a/staging/src/k8s.io/apiserver/pkg/storage/selection_predicate.go +++ b/staging/src/k8s.io/apiserver/pkg/storage/selection_predicate.go @@ -17,10 +17,13 @@ limitations under the License. package storage import ( + "context" + "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apiserver/pkg/endpoints/request" ) // AttrFunc returns label and field sets and the uninitialized flag for List or Watch to match. @@ -145,11 +148,16 @@ func (s *SelectionPredicate) Empty() bool { // For any index defined by IndexFields, if a matcher can match only (a subset) // of objects that return for a given index, a pair (, ) // wil be returned. -func (s *SelectionPredicate) MatcherIndex() []MatchValue { +func (s *SelectionPredicate) MatcherIndex(ctx context.Context) []MatchValue { var result []MatchValue for _, field := range s.IndexFields { if value, ok := s.Field.RequiresExactMatch(field); ok { result = append(result, MatchValue{IndexName: FieldIndex(field), Value: value}) + } else if field == "metadata.namespace" { + // list pods in the namespace. i.e. /api/v1/namespaces/default/pods + if namespace, isNamespaceScope := isNamespaceScopedRequest(ctx); isNamespaceScope { + result = append(result, MatchValue{IndexName: FieldIndex(field), Value: namespace}) + } } } for _, label := range s.IndexLabels { @@ -160,6 +168,14 @@ func (s *SelectionPredicate) MatcherIndex() []MatchValue { return result } +func isNamespaceScopedRequest(ctx context.Context) (string, bool) { + re, _ := request.RequestInfoFrom(ctx) + if re == nil || len(re.Namespace) == 0 { + return "", false + } + return re.Namespace, true +} + // LabelIndex add prefix for label index. func LabelIndex(label string) string { return "l:" + label diff --git a/staging/src/k8s.io/apiserver/pkg/storage/selection_predicate_test.go b/staging/src/k8s.io/apiserver/pkg/storage/selection_predicate_test.go index fc030cc99043..1fc9499fab9f 100644 --- a/staging/src/k8s.io/apiserver/pkg/storage/selection_predicate_test.go +++ b/staging/src/k8s.io/apiserver/pkg/storage/selection_predicate_test.go @@ -17,6 +17,7 @@ limitations under the License. package storage import ( + "context" "errors" "reflect" "testing" @@ -25,6 +26,7 @@ import ( "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apiserver/pkg/endpoints/request" ) type Ignored struct { @@ -127,12 +129,14 @@ func TestSelectionPredicateMatcherIndex(t *testing.T) { indexLabels []string indexFields []string expected []MatchValue + ctx context.Context }{ "Match nil": { labelSelector: "name=foo", fieldSelector: "uid=12345", indexLabels: []string{"bar"}, indexFields: []string{}, + ctx: context.Background(), expected: nil, }, "Match field": { @@ -140,13 +144,83 @@ func TestSelectionPredicateMatcherIndex(t *testing.T) { fieldSelector: "uid=12345", indexLabels: []string{}, indexFields: []string{"uid"}, + ctx: context.Background(), expected: []MatchValue{{IndexName: FieldIndex("uid"), Value: "12345"}}, }, + "Match field for listing namespace pods without metadata.namespace field selector": { + labelSelector: "", + fieldSelector: "", + indexLabels: []string{}, + indexFields: []string{"metadata.namespace"}, + ctx: request.WithRequestInfo(context.Background(), &request.RequestInfo{ + IsResourceRequest: true, + Path: "/api/v1/namespaces/default/pods", + Verb: "list", + APIPrefix: "api", + APIGroup: "", + APIVersion: "v1", + Namespace: "default", + Resource: "pods", + }), + expected: []MatchValue{{IndexName: FieldIndex("metadata.namespace"), Value: "default"}}, + }, + "Match field for listing namespace pods with metadata.namespace field selector": { + labelSelector: "", + fieldSelector: "metadata.namespace=kube-system", + indexLabels: []string{}, + indexFields: []string{"metadata.namespace"}, + ctx: request.WithRequestInfo(context.Background(), &request.RequestInfo{ + IsResourceRequest: true, + Path: "/api/v1/namespaces/default/pods", + Verb: "list", + APIPrefix: "api", + APIGroup: "", + APIVersion: "v1", + Namespace: "default", + Resource: "pods", + }), + expected: []MatchValue{{IndexName: FieldIndex("metadata.namespace"), Value: "kube-system"}}, + }, + "Match field for listing all pods without metadata.namespace field selector": { + labelSelector: "", + fieldSelector: "", + indexLabels: []string{}, + indexFields: []string{"metadata.namespace"}, + ctx: request.WithRequestInfo(context.Background(), &request.RequestInfo{ + IsResourceRequest: true, + Path: "/api/v1/pods", + Verb: "list", + APIPrefix: "api", + APIGroup: "", + APIVersion: "v1", + Namespace: "", + Resource: "pods", + }), + expected: nil, + }, + "Match field for listing all pods with metadata.namespace field selector": { + labelSelector: "", + fieldSelector: "metadata.namespace=default", + indexLabels: []string{}, + indexFields: []string{"metadata.namespace"}, + ctx: request.WithRequestInfo(context.Background(), &request.RequestInfo{ + IsResourceRequest: true, + Path: "/api/v1/pods", + Verb: "list", + APIPrefix: "api", + APIGroup: "", + APIVersion: "v1", + Namespace: "default", + Resource: "pods", + }), + expected: []MatchValue{{IndexName: FieldIndex("metadata.namespace"), Value: "default"}}, + }, "Match label": { labelSelector: "name=foo", fieldSelector: "uid=12345", indexLabels: []string{"name"}, indexFields: []string{}, + ctx: context.Background(), expected: []MatchValue{{IndexName: LabelIndex("name"), Value: "foo"}}, }, "Match field and label": { @@ -154,6 +228,7 @@ func TestSelectionPredicateMatcherIndex(t *testing.T) { fieldSelector: "uid=12345", indexLabels: []string{"name"}, indexFields: []string{"uid"}, + ctx: context.Background(), expected: []MatchValue{{IndexName: FieldIndex("uid"), Value: "12345"}, {IndexName: LabelIndex("name"), Value: "foo"}}, }, "Negative match field and label": { @@ -161,6 +236,7 @@ func TestSelectionPredicateMatcherIndex(t *testing.T) { fieldSelector: "uid!=12345", indexLabels: []string{"name"}, indexFields: []string{"uid"}, + ctx: context.Background(), expected: nil, }, "Negative match field and match label": { @@ -168,6 +244,7 @@ func TestSelectionPredicateMatcherIndex(t *testing.T) { fieldSelector: "uid!=12345", indexLabels: []string{"name"}, indexFields: []string{"uid"}, + ctx: context.Background(), expected: []MatchValue{{IndexName: LabelIndex("name"), Value: "foo"}}, }, "Negative match label and match field": { @@ -175,6 +252,7 @@ func TestSelectionPredicateMatcherIndex(t *testing.T) { fieldSelector: "uid=12345", indexLabels: []string{"name"}, indexFields: []string{"uid"}, + ctx: context.Background(), expected: []MatchValue{{IndexName: FieldIndex("uid"), Value: "12345"}}, }, } @@ -194,7 +272,7 @@ func TestSelectionPredicateMatcherIndex(t *testing.T) { IndexLabels: testCase.indexLabels, IndexFields: testCase.indexFields, } - actual := sp.MatcherIndex() + actual := sp.MatcherIndex(testCase.ctx) if !reflect.DeepEqual(testCase.expected, actual) { t.Errorf("%v: expected %v, got %v", name, testCase.expected, actual) }