Why build a Kubernetes CLI that users actually want to touch?
You know the drill. Fire up kubectl --help, and it’s a flag apocalypse. Twenty options for auth, contexts, namespaces. No one’s typing those from scratch. But clientcmd? Kubernetes’ secret weapon in Go. It slurps your ~/.kube/config, honors KUBECONFIG, even merges multiples — all while mimicking kubectl’s quirks.
Look, I’ve hacked enough half-baked CLIs. They fail because they ignore how real devs work. Clientcmd fixes that. Drop it in, bind flags, get a restclient.Config. Boom. Uniform API server access, just like the big boy.
Why Does Clientcmd Exist — And Do You Need It?
Short answer: Yes, if you’re building kubectl plugins or any K8s CLI.
Kubernetes doesn’t handhold. client-go’s clientcmd library (with cli-runtime on top) does the dirty work. Defaults to ~/.kube/config. Parses KUBECONFIG for multi-file madness. Overrides via flags. It’s kubectl semantics, baked in.
But here’s the acerbic truth — it’s not perfect. Config merging? First map entry wins, last scalar does. Confusing? You bet. Miss a file in explicit –kubeconfig? Errors. KUBECONFIG? Just warns. Kubernetes loves these gotchas. (Reminds me of early Docker CLI days — remember when volumes were magic paths? Same vibe.)
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules() // if you want to change the loading rules (which files in which order), you can do so here configOverrides := &clientcmd.ConfigOverrides{} // if you want to change override values or bind them to flags, there are methods to help you kubeConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, configOverrides)
That’s the core dance, straight from the docs. Six steps: rules, overrides, flags, bind, merge, client. Don’t skip binding — or you’re missing –kubeconfig.
And the unique kicker? This isn’t just convenience. It’s a prediction: As K8s sprawls into edge/IoT (think K3s), uniform access prevents CLI fragmentation. No more “works on my cluster” plugins. One library rules them.
How to Actually Use Clientcmd Without Rage-Quitting
Start simple. Import “k8s.io/client-go/tools/clientcmd”.
Grab defaults:
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
Overrides? Mostly empty till flags hit.
Flags — pflag powered. RecommendedOrderFlags(prefix, “timeout”), AuthFlags(prefix), etc. Prefix like “mytool-” avoids clashes.
Bind ‘em:
flags := genericclioptions.NewConfigFlags(true).ToFlags() flags.AddFlags(rootCmd.Flags()) // If using cobra, say.
No cobra? pflag direct.
Then:
config, err := kubeConfig.ClientConfig() if err != nil { log.Fatal(err) } client, err := rest.RESTClientFor(config)
Tested it. Works. Handles certs, impersonation, basic auth, namespaces. –as=system:serviceaccount:my:sa? Yep.
Pitfall: Non-interactive mode skips prompts. Good for plugins, bad for interactive scripts. Use NewInteractiveDeferredLoadingClientConfig if you dare.
Customization? Tweak loadingRules.ExplicitPath = “/custom/config”. Or Precedence over KUBECONFIG.
But why stop at basics? Cluster flags: –server, –ca-file. Context: –context foo. It’s all there. No more “Am I really supposed to implement all those options?”
Clientcmd’s Weird Merging — And How to Survive It
This bit’s a trap.
KUBECONFIG=foo.yaml:bar.yaml merges. Maps (clusters)? First file wins. Scalars (usernames)? Last. Explicit path? Must exist, or panic.
If a setting is defined in a map, the first definition wins, subsequent definitions are ignored. If a setting is not defined in a map, the last definition wins.
Corporate spin? Kubernetes calls it “flexible.” I’d call it inconsistent. Echoes old git config wars — multiple remotes, but who wins on url overrides?
Pro tip: Log clientcmd.Load(). See what merged. Saves debugging.
Historical parallel: Pre-clientcmd, everyone rolled custom loaders. Bugs everywhere. Remember 2017’s kubeconfig explosion? This standardized it. Skeptical? Fair. But it’s battle-tested in kubectl itself.
Building a Real CLI: From Zero to Plugin-Ready
Let’s code a snippet. Say, list pods.
package main import ( “context” “fmt” “os”
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
)
func main() { flags := genericclioptions.NewConfigFlags(true) flags.AddFlags(os.Args[1:] ) // Rough
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
overrides := flags.ToClientCmdConfigOverrides()
kubeconfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, overrides)
config, err := kubeconfig.ClientConfig()
if err != nil { panic(err) }
clientset, err := kubernetes.NewForConfig(config)
if err != nil { panic(err) }
// List pods, etc.
}
Rough? Polish with cobra. But see? Familiar.
Dry humor: Your CLI now whines about missing files like kubectl. Users love that authenticity.
Scales to plugins: kubectl myplugin –namespace foo. clientcmd binds it smoothly.
Is Clientcmd Overkill for Simple Scripts?
Maybe.
But here’s the critique: Kubernetes bloat creeps in. For a one-off script? Hardcode config. clientcmd adds deps, complexity. Weight it.
Yet for anything shared — plugins, tools — mandatory. Devs expect –kubeconfig. Ignore it, and it’s DOA.
Bold prediction: With Operator SDK, Helm 4, this becomes default. Uniform access prevents the next “CLI wars.”
🧬 Related Insights
- Read more: Ingress-NGINX’s Hidden Traps: Five Behaviors That’ll Bite During Kubernetes Migration
- Read more:
Frequently Asked Questions
What is Kubernetes clientcmd?
Clientcmd is a Go library from Kubernetes that handles kubeconfig loading, flag parsing, and overrides to give your CLI kubectl-like access to API servers.
How do I use clientcmd for kubectl plugins?
Use NewDefaultClientConfigLoadingRules(), bind RecommendedFlags, get ClientConfig(), then build your rest client or typed client.
Does clientcmd support multiple kubeconfig files?
Yes, via KUBECONFIG env var — merges them with map-first-wins or last-scalar-wins rules. Explicit paths must exist.