Change:
Ran kube-controller-manager with and observed this in the log every 30 seconds: This is also happening in 1.20, 1.19, and 1.18 The GC and quota clients look up discovery information every 30 seconds. They have a QPS of 20 requests per second and a burst of 30 requests. Default configuration of kube-controller-manager runs without constant throttling warnings. /sig api-machinery
/assign /triage accepted

Discussion:
} // Use a discovery client capable of being refreshed. <del> discoveryClient := rootClientBuilder.ClientOrDie("controller-discovery") cachedClient := cacheddiscovery.NewMemCacheClient(discoveryClient.Discovery()) </del> <ins> discoveryClient := rootClientBuilder.DiscoveryClientOrDie("controller-discovery") cachedClient := cacheddiscovery.NewMemCacheClient(discoveryClient) </ins> restMapper := restmapper.NewDeferredDiscoveryRESTMapper(cachedClient) go wait.Until(func() { restMapper.Reset()

func startResourceQuotaController(ctx ControllerContext) (http.Handler, bool, error) { resourceQuotaControllerClient := ctx.ClientBuilder.ClientOrDie("resourcequota-controller") <del> discoveryFunc := resourceQuotaControllerClient.Discovery().ServerPreferredNamespacedResources </del> <ins> resourceQuotaControllerDiscoveryClient := ctx.ClientBuilder.DiscoveryClientOrDie("resourcequota-controller") discoveryFunc := resourceQuotaControllerDiscoveryClient.ServerPreferredNamespacedResources </ins> listerFuncForResource := generic.ListerFuncForResourceFunc(ctx.InformerFactory.ForResource) quotaConfiguration := quotainstall.NewQuotaConfigurationForControllers(listerFuncForResource)

} gcClientset := ctx.ClientBuilder.ClientOrDie("generic-garbage-collector") <ins> discoveryClient := ctx.ClientBuilder.DiscoveryClientOrDie("generic-garbage-collector") </ins> config := ctx.ClientBuilder.ConfigOrDie("generic-garbage-collector") metadataClient, err := metadata.NewForConfig(config)

// Periodically refresh the RESTMapper with new discovery information and sync // the garbage collector. <del> go garbageCollector.Sync(gcClientset.Discovery(), 30*time.Second, ctx.Stop) </del> <ins> go garbageCollector.Sync(discoveryClient, 30*time.Second, ctx.Stop) </ins> return garbagecollector.NewDebugHandler(garbageCollector), true, nil }

return m.clientset } <ins> func (m TestClientBuilder) DiscoveryClient(name string) (discovery.DiscoveryInterface, error) { return m.clientset.Discovery(), nil } func (m TestClientBuilder) DiscoveryClientOrDie(name string) discovery.DiscoveryInterface { ret, err := m.DiscoveryClient(name) if err != nil { panic(err) } return ret } </ins> // FakeDiscoveryWithError inherits DiscoveryInterface(via FakeDiscovery) with some methods accepting testing data. type FakeDiscoveryWithError struct { fakediscovery.FakeDiscovery

package clientbuilder import ( <ins> "k8s.io/client-go/discovery" </ins> clientset "k8s.io/client-go/kubernetes" restclient "k8s.io/client-go/rest" "k8s.io/klog/v2"

ConfigOrDie(name string) *restclient.Config Client(name string) (clientset.Interface, error) ClientOrDie(name string) clientset.Interface <ins> DiscoveryClient(name string) (discovery.DiscoveryInterface, error) DiscoveryClientOrDie(name string) discovery.DiscoveryInterface </ins> } // SimpleControllerClientBuilder returns a fixed client with different user agents

} return client } <ins> // DiscoveryClientOrDie returns a discovery.DiscoveryInterface built from the ClientBuilder // Discovery is special because it will artificially pump the burst quite high to handle the many discovery requests. func (b SimpleControllerClientBuilder) DiscoveryClient(name string) (discovery.DiscoveryInterface, error) { clientConfig, err := b.Config(name) if err != nil { return nil, err } // Discovery makes a lot of requests infrequently.  This allows the burst to succeed and refill to happen // in just a few seconds. clientConfig.Burst = 200 clientConfig.QPS = 20 return clientset.NewForConfig(clientConfig) } // DiscoveryClientOrDie returns a discovery.DiscoveryInterface built from the ClientBuilder with no error. // Discovery is special because it will artificially pump the burst quite high to handle the many discovery requests. // If it gets an error getting the client, it will log the error and kill the process it's running in. func (b SimpleControllerClientBuilder) DiscoveryClientOrDie(name string) discovery.DiscoveryInterface { client, err := b.DiscoveryClient(name) if err != nil { klog.Fatal(err) } return client } </ins>

"k8s.io/apimachinery/pkg/util/clock" "k8s.io/apimachinery/pkg/util/wait" apiserverserviceaccount "k8s.io/apiserver/pkg/authentication/serviceaccount" <ins> "k8s.io/client-go/discovery" </ins> clientset "k8s.io/client-go/kubernetes" v1core "k8s.io/client-go/kubernetes/typed/core/v1" restclient "k8s.io/client-go/rest"

return client } <ins> func (t *DynamicControllerClientBuilder) DiscoveryClient(name string) (discovery.DiscoveryInterface, error) { clientConfig, err := t.Config(name) if err != nil { return nil, err } // Discovery makes a lot of requests infrequently.  This allows the burst to succeed and refill to happen // in just a few seconds. clientConfig.Burst = 200 clientConfig.QPS = 20 return clientset.NewForConfig(clientConfig) } func (t *DynamicControllerClientBuilder) DiscoveryClientOrDie(name string) discovery.DiscoveryInterface { client, err := t.DiscoveryClient(name) if err != nil { klog.Fatal(err) } return client } </ins> type tokenSourceImpl struct { namespace          string serviceAccountName string