Uniform API server access using clientcmd
Source: Kubernetes Blog
If you’ve ever wanted to develop a command‑line client for the Kubernetes API—especially one that can be used as a kubectl plugin—you might have wondered how to make your client feel familiar to users of kubectl.
A quick glance at the output of kubectl options can be intimidating:
“Am I really supposed to implement all those options?”
Fear not! The Kubernetes project already provides two libraries that do most of the heavy lifting for you:
- clientcmd – parses kubectl‑style command‑line arguments and builds a
restclient.Config. - cli‑runtime – builds on top of
clientcmd.
This article focuses on the clientcmd library.
General Philosophy
Because clientcmd is part of client‑go, its ultimate purpose is to produce a restclient.Config that can issue requests to an API server. It follows the same semantics as kubectl:
| Source | Description |
|---|---|
~/.kube/config (or equivalent) | Default configuration |
KUBECONFIG environment variable | One or more files whose contents are merged |
| Command‑line arguments | Override any of the above |
clientcmd does not automatically add a --kubeconfig flag; you’ll see how to add it in the “Bind the flags” section.
Available Features
clientcmd lets programs handle:
- kubeconfig selection (via
KUBECONFIG) - context selection
- namespace selection
- client certificates & private keys
- user impersonation
- HTTP Basic authentication (username/password)
Configuration Merging
KUBECONFIGmay contain a colon‑separated list of files; their contents are merged.- Map‑based settings – the first definition wins, later ones are ignored.
- Non‑map settings – the last definition wins.
- Missing files referenced by
KUBECONFIGgenerate warnings only. - If the user explicitly supplies a path (e.g.,
--kubeconfig), the file must exist. - When
KUBECONFIGis unset,~/.kube/configis used (if present).
Overall Process
The typical usage pattern is summarized in the clientcmd package docs:
// 1️⃣ Build loading rules (defaults to KUBECONFIG or ~/.kube/config)
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
// – you can customise the order / files here if needed
// 2️⃣ Prepare overrides (populated from flags later)
configOverrides := &clientcmd.ConfigOverrides{}
// 3️⃣ Create a deferred‑loading client config
kubeConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
loadingRules,
configOverrides,
)
// 4️⃣ Resolve the final REST config
config, err := kubeConfig.ClientConfig()
if err != nil {
// handle error
}
// 5️⃣ Build a client (example using the dynamic client)
client, err := dynamic.NewForConfig(config)
if err != nil {
// handle error
}
In this article we’ll walk through six steps that correspond to the numbered comments above:
- Configure the loading rules
- Configure the overrides
- Build a set of flags
- Bind the flags
- Build the merged configuration
- Obtain an API client
1️⃣ Configure the Loading Rules
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
- Uses
KUBECONFIGif set, otherwise falls back to~/.kube/config. - If the default file is used, it can migrate settings from the very old default (
~/.kube/.kubeconfig).
You can create a custom ClientConfigLoadingRules if you need a non‑standard order or additional files, but the defaults work for most cases.
2️⃣ Configure the Overrides
clientcmd.ConfigOverrides is a struct that stores values that will override whatever is loaded from the kubeconfig files. In this guide the overrides will be populated from command‑line flags (via the pflag library, a drop‑in replacement for Go’s flag package).
configOverrides := &clientcmd.ConfigOverrides{}
Usually you won’t set anything manually here; you’ll bind the struct fields to flags in the next step.
3️⃣ Build a Set of Flags
A flag represents a command‑line argument (e.g., --namespace or -n). clientcmd provides three groups of flags, each wrapped in a FlagInfo struct:
| Flag Group | Typical Arguments |
|---|---|
| Authentication | --certificate-authority, --token, --as, --username, --password |
| Cluster | --server, --certificate-authority, --insecure-skip-tls-verify, --proxy-url, --tls-server-name, --compress |
| Context | --context, --cluster, --user, --namespace |
The recommended set includes all three groups, plus a --timeout flag.
Recommended flag constructors
// No prefix → flags like --context, --namespace, etc.
flags := clientcmd.RecommendedConfigOverrideFlags("")
// With a prefix → flags like --from-context, --from-namespace, etc.
flags := clientcmd.RecommendedConfigOverrideFlags("from-")
- The
--timeoutflag defaults to0. - The
--namespaceflag also gets a short alias-n.
Gotcha: Prefixes do not affect short names. If you create multiple prefixed flag sets, only one can keep the -n alias. Clear the short name on the others:
kflags := clientcmd.RecommendedConfigOverrideFlags(prefix)
kflags.ContextOverrideFlags.Namespace.ShortName = "" // removes -n
4️⃣ Bind the Flags
Now connect the flag definitions to the ConfigOverrides struct so that values entered on the command line populate the overrides.
// Assume you have a pflag.FlagSet called fs
fs := pflag.NewFlagSet("myclient", pflag.ExitOnError)
// Build the flag structs (choose a prefix if you like)
flags := clientcmd.RecommendedConfigOverrideFlags("")
// Register the flags
flags.BindFlags(fs) // registers the flags
flags.BindFlagSet(fs, configOverrides) // populates configOverrides when parsed
If you also want a --kubeconfig flag (to match kubectl), add it manually:
fs.StringVar(&configOverrides.ClusterInfo.KubeConfigPath, "kubeconfig", "", "Path to the kubeconfig file")
Finally, parse the command line:
if err := fs.Parse(os.Args[1:]); err != nil {
// handle parse error
}
5️⃣ Build the Merged Configuration
With loading rules and overrides ready, create the deferred‑loading client config and resolve the final rest.Config:
kubeConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
loadingRules,
configOverrides,
)
restConfig, err := kubeConfig.ClientConfig()
if err != nil {
// handle error (e.g., missing kubeconfig, invalid overrides)
}
restConfig now contains the merged settings from:
- Files referenced by
KUBECONFIG(or the default file) - Overrides supplied via flags
6️⃣ Obtain an API Client
Use the rest.Config to instantiate any Kubernetes client you need. Here are a few common examples:
// CoreV1 client
coreClient, err := kubernetes.NewForConfig(restConfig)
// Dynamic client (works with any resource)
dynClient, err := dynamic.NewForConfig(restConfig)
// Discovery client
discClient, err := discovery.NewDiscoveryClientForConfig(restConfig)
From here you can interact with the cluster just like kubectl does.
TL;DR – The Six‑Step Checklist
| Step | Code Snippet |
|---|---|
| 1️⃣ Loading rules | loadingRules := clientcmd.NewDefaultClientConfigLoadingRules() |
| 2️⃣ Overrides | configOverrides := &clientcmd.ConfigOverrides{} |
| 3️⃣ Flags | flags := clientcmd.RecommendedConfigOverrideFlags("") |
| 4️⃣ Bind | flags.BindFlags(fs); flags.BindFlagSet(fs, configOverrides) |
| 5️⃣ Merge | kubeConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, configOverrides); restConfig, _ := kubeConfig.ClientConfig() |
| 6️⃣ Client | client, _ := kubernetes.NewForConfig(restConfig) |
With these steps you can give your Go CLI the same look‑and‑feel as kubectl without re‑implementing every flag yourself. Happy coding!
Disable Flags
If you want to disable a flag entirely, clear its long name:
kflags.ContextOverrideFlags.Namespace.LongName = ""
Bind the Flags (alternative)
Once a set of flags has been defined, you can bind command‑line arguments to overrides using clientcmd.BindOverrideFlags. This requires a pflag FlagSet (instead of Go’s standard flag package).
If you also want to bind --kubeconfig, do it now by binding ExplicitPath in the loading rules:
flags.StringVarP(&loadingRules.ExplicitPath,
"kubeconfig", "", "", "absolute path(s) to the kubeconfig file(s)")
Build the Merged Configuration (alternative)
Two functions are available to build a merged configuration:
clientcmd.NewInteractiveDeferredLoadingClientConfigclientcmd.NewNonInteractiveDeferredLoadingClientConfig
The interactive version can ask for authentication information interactively (using a provided reader), while the non‑interactive version only uses the information supplied by the caller. Both are “deferred”: you can call them before parsing command‑line arguments; the resulting configuration will incorporate whatever flag values have been parsed by the time it is actually constructed.
Obtain an API Client (alternative)
The merged configuration is returned as a ClientConfig instance. You can obtain an API client from it by calling the ClientConfig() method.
If no configuration is found (e.g., KUBECONFIG is empty or points to non‑existent files, ~/.kube/config doesn’t exist, and no command‑line overrides are provided), the default setup returns an obscure error that mentions KUBERNETES_MASTER. This legacy behaviour is kept only for the --local and --dry-run flags in kubectl.
Tip: Detect “empty configuration” errors with clientcmd.IsEmptyConfig(err) and present a clearer message to the user.
The Namespace() method is also handy—it returns the namespace that should be used and indicates whether the namespace was overridden by the user (via --namespace).
Full Example
Here’s a complete, runnable example:
package main
import (
"context"
"fmt"
"os"
"github.com/spf13/pflag"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
)
func main() {
// Loading rules – no configuration yet
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
// Overrides and flag (command‑line argument) setup
configOverrides := &clientcmd.ConfigOverrides{}
flags := pflag.NewFlagSet("clientcmddemo", pflag.ExitOnError)
// Bind the standard override flags
clientcmd.BindOverrideFlags(configOverrides, flags,
clientcmd.RecommendedConfigOverrideFlags(""))
// Bind the --kubeconfig flag
flags.StringVarP(&loadingRules.ExplicitPath,
"kubeconfig", "", "", "absolute path(s) to the kubeconfig file(s)")
// Parse the command‑line arguments
flags.Parse(os.Args)
// Construct the client configuration (non‑interactive)
kubeConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
loadingRules, configOverrides)
// Get a REST config for the client‑go libraries
config, err := kubeConfig.ClientConfig()
if err != nil {
if clientcmd.IsEmptyConfig(err) {
panic("Please provide a configuration pointing to the Kubernetes API server")
}
panic(err)
}
// Build the typed clientset
client, err := kubernetes.NewForConfig(config)
if err != nil {
panic(err)
}
// Determine which namespace to use
namespace, overridden, err := kubeConfig.Namespace()
if err != nil {
panic(err)
}
fmt.Printf("Chosen namespace: %s; overridden: %t\n", namespace, overridden)
// Use the client – list all nodes
nodeList, err := client.CoreV1().Nodes().List(context.TODO(), v1.ListOptions{})
if err != nil {
panic(err)
}
for _, node := range nodeList.Items {
fmt.Println(node.Name)
}
}
Happy coding, and thank you for your interest in building tools with familiar usage patterns!