Limit ResourceQuota LIST requests to times when informer is not synced

This should reduce the number of slow (100ms) LIST requests when there
are no ResourceQuota objects present in the namespace. The behavior
stays virtually the same.
This commit is contained in:
Aleksander Mistewicz 2025-02-12 13:34:09 +01:00
parent 1d723e3849
commit c1cddd5fff
3 changed files with 13 additions and 3 deletions

View File

@ -114,7 +114,9 @@ func (a *QuotaAdmission) SetExternalKubeClientSet(client kubernetes.Interface) {
// SetExternalKubeInformerFactory registers an informer factory into QuotaAdmission // SetExternalKubeInformerFactory registers an informer factory into QuotaAdmission
func (a *QuotaAdmission) SetExternalKubeInformerFactory(f informers.SharedInformerFactory) { func (a *QuotaAdmission) SetExternalKubeInformerFactory(f informers.SharedInformerFactory) {
a.quotaAccessor.lister = f.Core().V1().ResourceQuotas().Lister() quotas := f.Core().V1().ResourceQuotas()
a.quotaAccessor.lister = quotas.Lister()
a.quotaAccessor.hasSynced = quotas.Informer().HasSynced
} }
// SetQuotaConfiguration assigns and initializes configuration and evaluator for QuotaAdmission // SetQuotaConfiguration assigns and initializes configuration and evaluator for QuotaAdmission
@ -144,6 +146,9 @@ func (a *QuotaAdmission) ValidateInitialization() error {
if a.quotaAccessor.lister == nil { if a.quotaAccessor.lister == nil {
return fmt.Errorf("missing quotaAccessor.lister") return fmt.Errorf("missing quotaAccessor.lister")
} }
if a.quotaAccessor.hasSynced == nil {
return fmt.Errorf("missing quotaAccessor.hasSynced")
}
if a.quotaConfiguration == nil { if a.quotaConfiguration == nil {
return fmt.Errorf("missing quotaConfiguration") return fmt.Errorf("missing quotaConfiguration")
} }

View File

@ -48,6 +48,9 @@ type quotaAccessor struct {
// lister can list/get quota objects from a shared informer's cache // lister can list/get quota objects from a shared informer's cache
lister corev1listers.ResourceQuotaLister lister corev1listers.ResourceQuotaLister
// hasSynced indicates whether the lister has completed its initial sync
hasSynced func() bool
// liveLookups holds the last few live lookups we've done to help ammortize cost on repeated lookup failures. // liveLookups holds the last few live lookups we've done to help ammortize cost on repeated lookup failures.
// This lets us handle the case of latent caches, by looking up actual results for a namespace on cache miss/no results. // This lets us handle the case of latent caches, by looking up actual results for a namespace on cache miss/no results.
// We track the lookup result here so that for repeated requests, we don't look it up very often. // We track the lookup result here so that for repeated requests, we don't look it up very often.
@ -112,8 +115,8 @@ func (e *quotaAccessor) GetQuotas(namespace string) ([]corev1.ResourceQuota, err
return nil, fmt.Errorf("error resolving quota: %v", err) return nil, fmt.Errorf("error resolving quota: %v", err)
} }
// if there are no items held in our indexer, check our live-lookup LRU, if that misses, do the live lookup to prime it. // if there are no items held in our unsynced lister, check our live-lookup LRU, if that misses, do the live lookup to prime it.
if len(items) == 0 { if len(items) == 0 && !e.hasSynced() {
lruItemObj, ok := e.liveLookupCache.Get(namespace) lruItemObj, ok := e.liveLookupCache.Get(namespace)
if !ok || lruItemObj.(liveLookupEntry).expiry.Before(time.Now()) { if !ok || lruItemObj.(liveLookupEntry).expiry.Before(time.Now()) {
// use singleflight.Group to avoid flooding the apiserver with repeated // use singleflight.Group to avoid flooding the apiserver with repeated

View File

@ -97,6 +97,7 @@ func TestLRUCacheLookup(t *testing.T) {
accessor, _ := newQuotaAccessor() accessor, _ := newQuotaAccessor()
accessor.client = kubeClient accessor.client = kubeClient
accessor.lister = informerFactory.Core().V1().ResourceQuotas().Lister() accessor.lister = informerFactory.Core().V1().ResourceQuotas().Lister()
accessor.hasSynced = func() bool { return false }
accessor.liveLookupCache = liveLookupCache accessor.liveLookupCache = liveLookupCache
for _, q := range tc.cacheInput { for _, q := range tc.cacheInput {
@ -151,6 +152,7 @@ func TestGetQuotas(t *testing.T) {
accessor, _ := newQuotaAccessor() accessor, _ := newQuotaAccessor()
accessor.client = kubeClient accessor.client = kubeClient
accessor.lister = informerFactory.Core().V1().ResourceQuotas().Lister() accessor.lister = informerFactory.Core().V1().ResourceQuotas().Lister()
accessor.hasSynced = func() bool { return false }
kubeClient.AddReactor("list", "resourcequotas", func(action core.Action) (bool, runtime.Object, error) { kubeClient.AddReactor("list", "resourcequotas", func(action core.Action) (bool, runtime.Object, error) {
switch action.GetNamespace() { switch action.GetNamespace() {