Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

K8s client as reusable cell #21026

Merged
merged 7 commits into from
Sep 15, 2022
Merged

Conversation

joamaki
Copy link
Contributor

@joamaki joamaki commented Aug 22, 2022

First commit drops the cnp_test.go test that was used to benchmark protobuf vs json serialization for K8s objects. It is no longer needed and would have complicated implementation of the k8s client cell.

Second commit implements K8s client as a reusable cell that provides the Clientset interface to the application, which is a composition of the different client interfaces used by Cilium (Kubernetes, APIExt, Cilium and Slim version of Kubernetes).

The configuration needed by the client is moved from DaemonConfig into client.Config in pkg/k8s/client/config.go and it implements CellFlags to register the command-line flags. This allows easy embedding of the client cell into other applications besides cilium-agent.

Rest of the commits convert uses of the k8s client over to this new abstraction out from k8s.Configure/k8s.Init. There are still some holdouts that use the global accessors (k8s.Client() etc.) that will be addressed in a follow-up.

To make this more concrete, here is an abbreviated example application using the client cell:

func main() {
  hive := hive.New(
    pflag.CommandLine, // Add cell flags to the default flagset
    viper.New(), // Use a new viper instance to bind flags to and read settings from

    client.Cell,
    exampleCell,
    hive.Require[*example](), // Require that *example is constructed.
  )
  pflag.Parse()
  hive.Run()
}

var exampleCell = hive.NewCell("example",  fx.Provide(newExample))

type example struct {
  clientset client.Clientset
}

func newExample(lc fx.Lifecycle, clientset client.Clientset) *example {
  ex := &example{clientset}
  lc.Append(fx.Hook{OnStart: ex.onStart, OnStop: ex.onStop})
  return ex
}

func (ex *example) onStart(context.Context) error {
  node, err := ex.clientset.CoreV1().Nodes().Get(/* ... */)
  ...
}

The application now has the k8s configuration options included, so to run it with kubeconfig file:

$ ./example --k8s-kubeconfig-path=$HOME/.kube/config

@maintainer-s-little-helper maintainer-s-little-helper bot added the dont-merge/needs-release-note-label The author needs to describe the release impact of these changes. label Aug 22, 2022
@joamaki joamaki force-pushed the pr/joamaki/k8s-client-cell branch 3 times, most recently from bde7662 to 7e8c3af Compare August 30, 2022 09:49
@joamaki joamaki added the release-note/minor This PR changes functionality that users may find relevant to operating Cilium. label Aug 30, 2022
@maintainer-s-little-helper maintainer-s-little-helper bot removed the dont-merge/needs-release-note-label The author needs to describe the release impact of these changes. label Aug 30, 2022
@joamaki
Copy link
Contributor Author

joamaki commented Aug 30, 2022

/test

@joamaki joamaki changed the title DRAFT: k8s-client cell K8s client as a reusable cell Aug 30, 2022
@joamaki joamaki changed the title K8s client as a reusable cell K8s client as reusable cell Aug 30, 2022
@joamaki
Copy link
Contributor Author

joamaki commented Aug 30, 2022

Here is how cilium-agent objects looks like when K8s client is in use:

[cilium:~/go/src/github.com/cilium/cilium/daemon]$ ./cilium-agent objects --k8s-kubeconfig-path=$HOME/.kube/config
Supplied objects:

  🎁️ *logrus.Entry

  🎁️ cmd.DaemonCellConfig from daemon

  🎁️ gops.GopsConfig from gops

  🎁️ client.Config from k8s-client

Constructors:

  🛠️  go.uber.org/fx.(*App).dotGraph-fm():
    • fx.DotGraph

  🛠️  go.uber.org/fx.(*App).shutdowner-fm():
    • fx.Shutdowner

  🛠️  go.uber.org/fx.New.func1():
    • fx.Lifecycle

  🛠️  k8s-client (github.com/cilium/cilium/pkg/k8s/client.newClientset()):
    • client.Clientset

Start hooks:

  • github.com/cilium/cilium/pkg/gops.registerGopsHooks.func1 (.../gops/cell.go:41)
  • github.com/cilium/cilium/pkg/k8s/client.(*compositeClientset).onStart-fm (..././<autogenerated>:1)
  • github.com/cilium/cilium/daemon/cmd.registerDaemonHooks.func1 (.../cmd/daemon_main.go:1551)

Stop hooks:

  • github.com/cilium/cilium/daemon/cmd.registerDaemonHooks.func2 (.../cmd/daemon_main.go:1557)
  • github.com/cilium/cilium/pkg/k8s/client.(*compositeClientset).onStop-fm (..././<autogenerated>:1)
  • github.com/cilium/cilium/pkg/gops.registerGopsHooks.func2 (.../gops/cell.go:48)

Configurations:

  ⚙ gops: gops.GopsConfig{GopsPort:0x26a2}
  ⚙ k8s-client: client.Config{K8sAPIServer:"", K8sKubeConfigPath:"/home/jussi/.kube/config", K8sClientQPS:0, K8sClientBurst:0, K8sHeartbeatTimeout:30000000000, EnableK8sAPIDiscovery:false}
  ⚙ daemon: cmd.DaemonCellConfig{SkipDaemon:false}

From the above we see that the client.Clientset is available to use in the application, and
we can see from "Start hooks" that gops is started first, followed by the k8s client and finally the
daemon start hook.

We can use for example go doc to explore what it contains:

$ go doc client.Clientset
package client // import "github.com/cilium/cilium/pkg/k8s/client"

type Clientset interface {
        kubernetes.Interface
        apiext_clientset.Interface
        cilium_clientset.Interface

        // Slim returns the slim client, which contains some of the same APIs as the
        // normal kubernetes client, but with slimmed down messages to reduce memory
        // usage. Prefer the slim version when caching messages.
        Slim() slim_clientset.Interface

        // IsEnabled returns true if Kubernetes support is enabled and the
        // clientset can be used.
        IsEnabled() bool

        // Config returns the configuration used to create this client.
        Config() Config
}
    Clientset is a composition of the different client sets used by Cilium.

$ go doc client.Config
package client // import "github.com/cilium/cilium/pkg/k8s/client"

type Config struct {
        // K8sAPIServer is the kubernetes api address server (for https use --k8s-kubeconfig-path instead)
        K8sAPIServer string

        // K8sKubeConfigPath is the absolute path of the kubernetes kubeconfig file
        K8sKubeConfigPath string

        // K8sClientQPSLimit is the queries per second limit for the K8s client. Defaults to k8s client defaults.
        K8sClientQPS float32

        // K8sClientBurst is the burst value allowed for the K8s client. Defaults to k8s client defaults.
        K8sClientBurst int

        // K8sHeartbeatTimeout configures the timeout for apiserver heartbeat
        K8sHeartbeatTimeout time.Duration

        // K8sEnableAPIDiscovery enables Kubernetes API discovery
        EnableK8sAPIDiscovery bool
}

func (cfg Config) CellFlags(flags *pflag.FlagSet)
func (cfg Config) K8sAPIDiscoveryEnabled() bool
func (cfg Config) K8sLeasesFallbackDiscoveryEnabled() bool

@joamaki
Copy link
Contributor Author

joamaki commented Aug 30, 2022

Closing and reopening to rerun CI.

@joamaki joamaki closed this Aug 30, 2022
@joamaki joamaki reopened this Aug 30, 2022
@joamaki joamaki force-pushed the pr/joamaki/k8s-client-cell branch 3 times, most recently from 85e3b93 to a7ff5e3 Compare September 2, 2022 08:56
@joamaki
Copy link
Contributor Author

joamaki commented Sep 2, 2022

/test

@joamaki joamaki force-pushed the pr/joamaki/k8s-client-cell branch 7 times, most recently from f1f23dc to 22ac15f Compare September 5, 2022 14:42
@joamaki
Copy link
Contributor Author

joamaki commented Sep 6, 2022

/test

@joamaki joamaki marked this pull request as ready for review September 6, 2022 08:18
@joamaki joamaki requested review from a team as code owners September 6, 2022 08:18
Copy link
Member

@pippolo84 pippolo84 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great! 🎉
A couple of things left inline (one naming preference and a possible missing initialization).

Copy link
Member

@christarazi christarazi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM besides a few minor comments. Awesome PR! 🚀

daemon/cmd/daemon_main.go Outdated Show resolved Hide resolved
Documentation/cmdref/cilium-agent.md Show resolved Hide resolved
test/controlplane/suite/testcase.go Outdated Show resolved Hide resolved
cilium/cmd/preflight_identity_crd_migrate.go Outdated Show resolved Hide resolved
daemon/cmd/daemon_main.go Outdated Show resolved Hide resolved
Copy link
Member

@pippolo84 pippolo84 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice PR! LGTM 💯

func init() {
cobra.OnInitialize(option.InitConfig(RootCmd, "cilium-agent", "cilium", Vp))
setupSleepBeforeFatal()
registerBootstrapMetrics()
initializeFlags()

flags := pflag.NewFlagSet("hive", pflag.ContinueOnError)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This all is a bit confusing. there's initializeFlags() which takes no arguments, then there's the flags var, then there's RootCmd.Flags()? And commands

Is there any way to make this simpler? I would expect that creating the Hive would make the flags Just Work.

Copy link
Contributor Author

@joamaki joamaki Sep 7, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason for this is that the root, "dot-graph" and "objects" commands in cilium-agent want all the same flags, but then we have "cmdref" which shouldn't. But now that you pointed this out I realized I can just made cmdref a hidden command and then we don't have Documentation/cmdref/cilium-agenc_cmdref.md and it doesn't matter what flags are there (unless we start adding mandatory flags at some point etc.). Will push the fix in a sec...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I get it now. I realized as I was walking for coffee that a Cell is different from a subcommand. Nevertheless, any way you can make flags simpler would be good; I've personally had poor experiences with other "do-it-all" cmd frameworks making it unergonomic to set flags the way you want.

@@ -53,6 +54,33 @@ func Invoke(fn any) *Cell {
return NewCell("", fx.Invoke(fn))
}

// OnStart registers a function to run on start up.
func OnStart(fn any) *Cell {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey look, it's generics (not really) :-p.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay but really, this is a bit too much black magic for me. So, this is basically a decorator -- by doing evil reflection magic to ensure args are passed through. Cute... but...

For the sake of simplicity, what if this just took a func() and left it at that? Yes, it means a closure, but that is certainly more idiomatic.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And if there's a reason I'm missing that a closure wouldn't work, can you at least document the magic?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I got annoyed by having to do fx.Invoke(func(lc fx.Lifecycle) { lc.Append(fx.Hook{OnStart: func(context.Context) error { thisOneThing() } } ) }) :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah the reason why this can’t just take “func()” is that the input arguments actually matter. E.g. if you give this a “func(a A)” then fx will go and construct “A” and pass it in. The trade-off here is between having to do this start hook the long way via fx.Invoke etc. or this magic. I’m fine either way, but though that for the command-line utility use-cases where we just want to construct and start few things and then run a function that uses them it makes sense to provide a simple helper.

I’ll document this better though!

Copy link
Contributor Author

@joamaki joamaki Sep 13, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Documented and changed it to take a function that returns an error so it's more general. Does this seem reasonable now? What we could also do, if you think it'd be preferred, is to write this as func OnStart[T any](func(T) error) *Cell. It'd constrain the function to a single input argument, but one could then use fx.In: type Inputs struct { fx.In; Foo Foo; ...}.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Documenting the magic is fine for now. If it winds up needing changes later, we can do that.

It is cute :-)

Copy link
Member

@nbusseneau nbusseneau left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed test changes, LGTM, thanks!

@joamaki joamaki force-pushed the pr/joamaki/k8s-client-cell branch 3 times, most recently from 80add65 to ae81615 Compare September 13, 2022 12:25
@joamaki
Copy link
Contributor Author

joamaki commented Sep 13, 2022

/test

operator/cmd/root.go Outdated Show resolved Hide resolved
@squeed
Copy link
Contributor

squeed commented Sep 13, 2022

One minor question, then lgtm.

@joamaki
Copy link
Contributor Author

joamaki commented Sep 14, 2022

/test-1.16-4.9

Job 'Cilium-PR-K8s-1.16-kernel-4.9' failed:

Click to show.

Test Name

K8sUpdates Tests upgrade and downgrade from a Cilium stable image to master

Failure Output

FAIL: migrate-svc restart count values do not match

If it is a flake and a GitHub issue doesn't already exist to track it, comment /mlh new-flake Cilium-PR-K8s-1.16-kernel-4.9 so I can create one.


Doh, moving the option.Config.Populate() before SetMapElementSizes screwed up the dynamic map size calculation. Fixed.

Signed-off-by: Jussi Maki <jussi@isovalent.com>
This test was used to benchmark json versus protobuf for the
k8s client and has served its purpose. To simplify refactoring
of the k8s client, remove this test ahead of time.

Signed-off-by: Jussi Maki <jussi@isovalent.com>
This adds a cell for the kubernetes client that provides a Clientset
for the application for accessing Kubernetes resources.

The integration of the new clientset to the codebase is done gradually.
In this commit the APIs provided by pkg/k8s are kept mostly as is, with
Configure and Init still functioning. Daemon is modified to take in the
clientset as parameter which is then used to populate the existing
global client variables via k8s.SetClients. Minimal changes were made
to cilium command and the operator.

New command-line flag, --skip-daemon, was added to allow experimenting
with new cells without having the daemon start up.

Signed-off-by: Jussi Maki <jussi@isovalent.com>
This switches the operator to use the k8-client provided via the k8s-client cell
instead of going via the global variables in pkg/k8s. Code is refactored to pass
around the clientset.

Signed-off-by: Jussi Maki <jussi@isovalent.com>
This refactors clustermesh-apiserver to use the k8s-client provided clientset.

The health API server was refactored as a hive cell as it was a low-hanging
fruit.

Signed-off-by: Jussi Maki <jussi@isovalent.com>
This switches the two uses of k8s client in the cilium (agent) cli utility
to use the k8s-client.

Signed-off-by: Jussi Maki <jussi@isovalent.com>
Drop the now unused k8s.Init and k8s.Configure methods, and
configuration types and methods related to them.

Drop unused call to k8s.Configure in informer benchmark.
The benchmark setup called k8s.Configure if INTEGRATION environment variable
was set, but none of the code in the benchmark made any use of it.

Signed-off-by: Jussi Maki <jussi@isovalent.com>
@joamaki
Copy link
Contributor Author

joamaki commented Sep 14, 2022

/test

@joamaki
Copy link
Contributor Author

joamaki commented Sep 14, 2022

ci-l4lb failure fixed by just merged #21302. Earlier run of ci-l4lb passed. Marking ready-to-merge.

@joamaki joamaki added the ready-to-merge This PR has passed all tests and received consensus from code owners to merge. label Sep 14, 2022
@pchaigno pchaigno merged commit 00e40bb into cilium:master Sep 15, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
ready-to-merge This PR has passed all tests and received consensus from code owners to merge. release-note/minor This PR changes functionality that users may find relevant to operating Cilium.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants