Skip to content

Feat: Run inside a cluster by evaluating incluster config #3088

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

Open
wants to merge 6 commits into
base: master
Choose a base branch
from

Conversation

blaubaer
Copy link

@blaubaer blaubaer commented Jan 28, 2025

Background

Assuming you want to execute k9s inside a POD directly inside the Kubernetes cluster you want to manage with k9s, currently you need to create a dedicated kubeconfig file for this.

But if the POD (k9s is running inside) is configured with a ServiceAccount, also the incluster config is available. If the Go client library is used (as k8s does), this is also automatically evaluated.

k9s has the feature to switch contexts. For this internal/client/config.go is doing currently some non standard logic to makes this possible. It assumes that the fields Clusters, AuthInfos, Contexts and CurrentContext of k8s.io/client-go/tools/clientcmd/api.Config has always values, which is not the case in case of incluster config; the same applies for mostly all fields of k8s.io/cli-runtime/genericclioptions/ConfigFlags.

Summary of changes

This adjustment detect if all of those fields are empty and creates in this case a kind of mock context to be compatible with the regular behavior of k9s. It is important to note: The Go client library only offers the possibility to either run with a regular kubeconfig file OR incluster config; it cannot both together.

Note on testing

The Go client library evaluates the fixed environment variables KUBERNETES_SERVICE_HOST and KUBERNETES_SERVICE_PORT, this is not a problem to set inside tests. But it also evaluates the following files:

  1. /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
  2. /var/run/secrets/kubernetes.io/serviceaccount/token
  3. /var/run/secrets/kubernetes.io/serviceaccount/namespace (in some cases also environment variable POD_NAMESPACE applies)

... the problem with this files, for regular testing they're located in areas where a regular user cannot write to and tests cannot be executed concurrently - and you cannot changes this location, they are hardcoded. Therefore I've tested everything locally and ensured all the other behaviors (with kubeconfig files) works as before.

User noticeable change

As a consequence, if now k9s is started inside a POD which does have a correct configured ServiceAccount, it works without any configurations.

@blaubaer blaubaer force-pushed the incluster branch 2 times, most recently from c3b44c3 to 067ee0e Compare February 2, 2025 18:30
@blaubaer
Copy link
Author

@derailed Hi. Do you need some more information from my side regarding this PR? 🙂

Copy link
Owner

@derailed derailed left a comment

Choose a reason for hiding this comment

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

@blaubaer Thank you for this update!
I think this needs a bit of TLC. Also we need docs to show users how to correctly configure and deploy their k9s pod and note that any context related actions are off the table.
Also we need to think about what happens when you run k9s in a pod related to any k9s customizations i.e skins, plugins, aliases, etc... How would one configure/load those in a given cluster and potentially reuse those across clusters?

}

func (c *Config) isIncluster(cfg api.Config) bool {
if (cfg.CurrentContext == "" || cfg.CurrentContext == incluster) &&
Copy link
Owner

Choose a reason for hiding this comment

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

Might as well return the bool expression. Also we could use a helper to check for empty i.e isEmpty(*string) bool

Copy link
Author

Choose a reason for hiding this comment

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

Ok.

func (c *Config) newInclusterContext() *api.Context {
ns, _, _ := c.clientConfig().Namespace()
if ns == "" {
ns = "default"
Copy link
Owner

Choose a reason for hiding this comment

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

Use const DefaultNamespace

Copy link
Author

Choose a reason for hiding this comment

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

Ok.

if ns == "" {
ns = "default"
}
nc := api.NewContext()
Copy link
Owner

Choose a reason for hiding this comment

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

nit: could just build the object and return it i.e return api.Context{Cluster: inCluster, Namespace: ns, ...}

Copy link
Author

Choose a reason for hiding this comment

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

Ok.

@@ -23,6 +23,8 @@ const (

// UsePersistentConfig caches client config to avoid reloads.
UsePersistentConfig = true

incluster = "incluster"
Copy link
Owner

Choose a reason for hiding this comment

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

nit: inClusterConfig vs incluster

Copy link
Author

Choose a reason for hiding this comment

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

Ok.

if c, ok := cfg.Contexts[n]; ok {
return c, nil
}

if n == incluster {
nc := c.newInclusterContext()
Copy link
Owner

Choose a reason for hiding this comment

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

no need to create a var here just return it

Copy link
Author

Choose a reason for hiding this comment

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

Ok.

@derailed derailed added enhancement New feature or request needs-tlc Pr needs additional updates labels Mar 30, 2025
@blaubaer blaubaer requested a review from derailed March 30, 2025 20:37
@blaubaer
Copy link
Author

@derailed: Also we need docs to show users how to correctly configure and deploy their k9s pod and note that any context related actions are off the table.

Kubernetes itself documents everything required on the Service Account documentation page. I would link to this site. This change of the code just makes k9s works like every other library which is using the go kubernetes SDK. k9s is just special because of the context switch stuff, which leads to that this does not work in the first place. With this adjustments it should work transparently, like a kubeconfig with just once context inside.

I've added a proposal to the README.md. What do you think?

@blaubaer
Copy link
Author

@derailed Also we need to think about what happens when you run k9s in a pod related to any k9s customizations i.e skins, plugins, aliases, etc... How would one configure/load those in a given cluster and potentially reuse those across clusters?

Good question. I suppose this is like running k9s inside a Docker container.

Context: Currently I work in environments where people are creating base images (like with kubectl and customized ones with k9s, because k9s does currently not support incluster) with all common stuff are already added to the customized image. Everyone who is accessing them have all those configuration and features are available. Nothing is persisted, like on a local computer - with intention. The people should rely on to have always a fresh and proofed environment.

Therefore, the most recommend way might be the same here, for me: Build a derivate of the image with all customizations the people want and/or (if this is really required) mount persistent storage.

Did I address your point, or did I get something wrong?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request needs-tlc Pr needs additional updates
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants