Listing all resources with client-go is slow

316 views Asked by At

I created some Go code to list all resources in all namespaces.

package main

import (
    "context"
    "fmt"

    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    "k8s.io/apimachinery/pkg/runtime/schema"
    "k8s.io/client-go/dynamic"
    "k8s.io/client-go/kubernetes"
    "k8s.io/client-go/tools/clientcmd"
)

func main() {
    loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
    configOverrides := &clientcmd.ConfigOverrides{}
    kubeconfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, configOverrides)

    config, err := kubeconfig.ClientConfig()
    if err != nil {
        panic(err.Error())
    }

    clientset, err := kubernetes.NewForConfig(config)
    if err != nil {
        panic(err.Error())
    }

    dynClient, err := dynamic.NewForConfig(config)
    if err != nil {
        panic(err.Error())
    }

    discoveryClient := clientset.Discovery()

    // Get the list of all API resources available
    serverResources, err := discoveryClient.ServerPreferredResources()
    if err != nil {
        panic(err.Error())
    }

    for _, group := range serverResources {
        for _, resource := range group.APIResources {
            // Skip subresources like pod/logs, pod/status
            if containsSlash(resource.Name) {
                continue
            }
            gvr := schema.GroupVersionResource{
                Group:    group.GroupVersion,
                Version:  resource.Version,
                Resource: resource.Name,
            }
            if gvr.Group == "v1" {
                gvr.Version = gvr.Group
                gvr.Group = ""
            }
            // if resource.Name != "machines" {
            //  continue
            // }
            var list *unstructured.UnstructuredList
            if resource.Namespaced {
                list, err = dynClient.Resource(gvr).List(context.TODO(), metav1.ListOptions{})

                if err != nil {
                    fmt.Printf("..Error listing %s: %v. group %q version %q resource %q\n", resource.Name, err,
                        gvr.Group, gvr.Version, gvr.Resource)
                    continue
                }
                printResources(list, resource.Name, gvr)

            } else {
                list, err = dynClient.Resource(gvr).List(context.TODO(), metav1.ListOptions{})
                if err != nil {
                    fmt.Printf("..Error listing %s: %v\n", resource.Name, err)
                    continue
                }
                printResources(list, resource.Name, gvr)
            }
        }
    }
}

func containsSlash(s string) bool {
    return len(s) > 0 && s[0] == '/'
}

func printResources(list *unstructured.UnstructuredList, resourceName string, gvr schema.GroupVersionResource) {
    fmt.Printf("Found %d resources of type %s. group %q version %q resource %q\n", len(list.Items), resourceName, gvr.Group, gvr.Version, gvr.Resource)
}

Unfortunately it takes more than 20 seconds in my small development cluster.

I guess I am doing something wrong.

Is there a way to reduce the number of API calls (or other ways to make it faster)?

2

There are 2 answers

1
Achref Nasri On

you can use goroutines to asynchronsly call the apis and print them (not in order) :

var wg sync.WaitGroup
  for _, group := range serverResources {
      wg.Add(1)
   // goroutine for executing the seconde loop for
     go func() {
        defer wg.Done()
     for _, resource := range group.APIResources {
        // the code  seconde for here....
       }
       }()
}
wg.Wait()
0
guettli On

The rest client in client-go throttles itself!

After adding:

        config.QPS = 100
        config.Burst = 100

The time is reduced from 14s to 320ms.

With concurrency, it can be further reduced to 120ms.

See: https://github.com/guettli/check-conditions/commit/2266dd2d89856b5534757ac06b86cba3d6e1afde

BTW: It doesn't get faster than 10 concurrent requests.

Related docs: Config.QPS