Skip to content

[Umbrella] Make client-go lighter and easier to consume #127888

Open
@aojea

Description

@aojea

Description

The current structure of client-go, with its dependencies on k8s.io/api and k8s.io/apimachinery present some significant challenges:

  • Dependency bloat: projects end up pulling in a large number of transitive dependencies, increasing binary size and potentially leading to conflicts.
  • Versioning headaches: Updating client-go often necessitates updating the dependent projects as well, which can be complex and time-consuming, especially with the need for coordinated releases.
  • Tight coupling: This structure creates tight coupling between client-go and the underlying API definitions, making necessary to update the clients to be able to use the new API types.

How to repro

Using the workqueue example https://github.com/kubernetes/kubernetes/tree/master/staging/src/k8s.io/client-go/examples/workqueue and creating a new project, after doing go mod tidy the go.mod file looks like:

go.mod
module client-go-example

go 1.23

require (
        k8s.io/api v0.31.1
        k8s.io/apimachinery v0.31.1
        k8s.io/client-go v0.31.1
        k8s.io/klog/v2 v2.130.1
)

require (
        github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
        github.com/emicklei/go-restful/v3 v3.11.0 // indirect
        github.com/fxamacker/cbor/v2 v2.7.0 // indirect
        github.com/go-logr/logr v1.4.2 // indirect
        github.com/go-openapi/jsonpointer v0.19.6 // indirect
        github.com/go-openapi/jsonreference v0.20.2 // indirect
        github.com/go-openapi/swag v0.22.4 // indirect
        github.com/gogo/protobuf v1.3.2 // indirect
        github.com/golang/protobuf v1.5.4 // indirect
        github.com/google/gnostic-models v0.6.8 // indirect
        github.com/google/go-cmp v0.6.0 // indirect
        github.com/google/gofuzz v1.2.0 // indirect
        github.com/google/uuid v1.6.0 // indirect
        github.com/imdario/mergo v0.3.6 // indirect
        github.com/josharian/intern v1.0.0 // indirect
        github.com/json-iterator/go v1.1.12 // indirect
        github.com/mailru/easyjson v0.7.7 // indirect
        github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
        github.com/modern-go/reflect2 v1.0.2 // indirect
        github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
        github.com/spf13/pflag v1.0.5 // indirect
        github.com/x448/float16 v0.8.4 // indirect
        golang.org/x/net v0.26.0 // indirect
        golang.org/x/oauth2 v0.21.0 // indirect
        golang.org/x/sys v0.21.0 // indirect
        golang.org/x/term v0.21.0 // indirect
        golang.org/x/text v0.16.0 // indirect
        golang.org/x/time v0.3.0 // indirect
        google.golang.org/protobuf v1.34.2 // indirect
        gopkg.in/inf.v0 v0.9.1 // indirect
        gopkg.in/yaml.v2 v2.4.0 // indirect
        gopkg.in/yaml.v3 v3.0.1 // indirect
        k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect
        k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect
        sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
        sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
        sigs.k8s.io/yaml v1.4.0 // indirect
)

and its size is

ls -lah main
-rwxr-x--- 1 aojea primarygroup 51M Oct  6 08:41 main

Proposal

  • Modularization: Breaking down client-go into smaller, more focused modules would allow users to import only the functionalities they need, reducing dependency bloat and improving maintainability.
  • Generic clients and Versioned APIs: Providing generic clients that can use versioned API clients would enable users to target specific Kubernetes versions without being forced to update the entire client-go library.
  • Reducing dependencies: eliminate unnecessary dependencies within client-go and its related projects.

Design Details

k8s.io/api

Ideally this should be a self contained module without dependencies, but it depends on apimachinery for the generated code and registering the types

module k8s.io/api
go 1.23.0
godebug default=go1.23
require (
github.com/gogo/protobuf v1.3.2
github.com/stretchr/testify v1.9.0
k8s.io/apimachinery v0.0.0
)

It requires k8s.io/apimachinery because of:

  • schema
register.go:     "k8s.io/apimachinery/pkg/runtime"
register.go:     "k8s.io/apimachinery/pkg/runtime/schema"
zz_generated.deepcopy.go:        runtime "k8s.io/apimachinery/pkg/runtime"
  • metav1
register.go:     metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
zz_generated.deepcopy.go:        v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  • utils for types
"k8s.io/apimachinery/pkg/util/validation"
zz_generated.deepcopy.go:  intstr "k8s.io/apimachinery/pkg/util/intstr"
resource/v1alpha3/types.go:     "k8s.io/apimachinery/pkg/util/validation"

NOTE This validation can be easily avoided and it seems is only to match a constant (cc: @pohly )

const PoolNameMaxLength = validation.DNS1123SubdomainMaxLength // Same as for a single node name.

k8s.io/apimachinery

This is the more tricky, and it is bringing a lot of dependencies

module k8s.io/apimachinery
go 1.23.0
godebug default=go1.23
require (
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc
github.com/fxamacker/cbor/v2 v2.7.0
github.com/gogo/protobuf v1.3.2
github.com/golang/protobuf v1.5.4
github.com/google/gnostic-models v0.6.8
github.com/google/go-cmp v0.6.0
github.com/google/gofuzz v1.2.0
github.com/google/uuid v1.6.0
github.com/moby/spdystream v0.4.0
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f
github.com/onsi/ginkgo/v2 v2.19.0
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.9.0
golang.org/x/net v0.28.0
golang.org/x/time v0.3.0
gopkg.in/evanphx/json-patch.v4 v4.12.0
gopkg.in/inf.v0 v0.9.1
k8s.io/klog/v2 v2.130.1
k8s.io/kube-openapi v0.0.0-20240827152857-f7e401e7b4c2
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd
sigs.k8s.io/structured-merge-diff/v4 v4.4.1
sigs.k8s.io/yaml v1.4.0

k8s.io/client-go

These module contains all the generated code, the typed clients and the dynamic client. Thanks to @skitt we already have Generics to share code in client-go

In addition to the dynamic client we could have a Generic client, that will be able to work with different versions of k8s.io/api, allowing users to work with skew between k8s.io/client-go and k8s.io/api, though this requires a very high bar on these modules to avoid breaking clients and provide stability across the ecosystem.

module k8s.io/client-go
go 1.23.0
godebug default=go1.23
require (
github.com/gogo/protobuf v1.3.2
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da
github.com/golang/protobuf v1.5.4
github.com/google/gnostic-models v0.6.8
github.com/google/go-cmp v0.6.0
github.com/google/gofuzz v1.2.0
github.com/google/uuid v1.6.0
github.com/gorilla/websocket v1.5.0
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7
github.com/peterbourgon/diskv v2.0.1+incompatible
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.9.0
go.uber.org/goleak v1.3.0
golang.org/x/net v0.28.0
golang.org/x/oauth2 v0.21.0
golang.org/x/term v0.23.0
golang.org/x/time v0.3.0
google.golang.org/protobuf v1.34.2
gopkg.in/evanphx/json-patch.v4 v4.12.0
k8s.io/api v0.0.0
k8s.io/apimachinery v0.0.0
k8s.io/klog/v2 v2.130.1
k8s.io/kube-openapi v0.0.0-20240827152857-f7e401e7b4c2
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd
sigs.k8s.io/structured-merge-diff/v4 v4.4.1
sigs.k8s.io/yaml v1.4.0
)

Ideally we should be able to split the API types from the tooling and allow consumers to use generics, something like

import (
  corev1 "k8s.io/api/core/v1"
	k8s.io/client-go/generic
)


func main(
	client := generic.NewClient[corev1.Pod]
	list, err := client.Namespace("foo").List()

)

and use generic controllers, we already have some intree

type Controller[T runtime.Object] interface {
// Meant to be run inside a goroutine
// Waits for and reacts to changes in whatever type the controller
// is concerned with.
//
// Returns an error always non-nil explaining why the worker stopped
Run(ctx context.Context) error
// Retrieves the informer used to back this controller
Informer() Informer[T]
// Returns true if the informer cache has synced, and all the objects from
// the initial list have been reconciled at least once.
HasSynced() bool
}
type NamespacedLister[T any] interface {
// List lists all ValidationRuleSets in the indexer for a given namespace.
// Objects returned here must be treated as read-only.
List(selector labels.Selector) (ret []T, err error)
// Get retrieves the ValidationRuleSet from the indexer for a given namespace and name.
// Objects returned here must be treated as read-only.
Get(name string) (T, error)
}
type Informer[T any] interface {
cache.SharedIndexInformer
Lister[T]
}
// Lister[T] helps list Ts.
// All objects returned here must be treated as read-only.
type Lister[T any] interface {

Tasks

References

Notes

I don't create a KEP because there are a considerable number of dimensions in this problem, so as first step I just want to gather feedback and take a more iterative approach, there are things like dependency pruning that can be worked out without a KEP, once we reach a point we need to implement some user facing change I will open the corresponding KEP

/area code-organization
/sig architecture
/sig api-machinery

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/code-organizationIssues or PRs related to kubernetes code organizationsig/api-machineryCategorizes an issue or PR as relevant to SIG API Machinery.sig/architectureCategorizes an issue or PR as relevant to SIG Architecture.triage/acceptedIndicates an issue or PR is ready to be actively worked on.

    Type

    No type

    Projects

    Status

    Triage

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions