Uniform API server access using clientcmd

Published: (January 19, 2026 at 01:00 PM EST)
7 min read

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:

SourceDescription
~/.kube/config (or equivalent)Default configuration
KUBECONFIG environment variableOne or more files whose contents are merged
Command‑line argumentsOverride 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

  • KUBECONFIG may 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 KUBECONFIG generate warnings only.
  • If the user explicitly supplies a path (e.g., --kubeconfig), the file must exist.
  • When KUBECONFIG is unset, ~/.kube/config is 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:

  1. Configure the loading rules
  2. Configure the overrides
  3. Build a set of flags
  4. Bind the flags
  5. Build the merged configuration
  6. Obtain an API client

1️⃣ Configure the Loading Rules

loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
  • Uses KUBECONFIG if 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 GroupTypical 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.

// 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 --timeout flag defaults to 0.
  • The --namespace flag 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

StepCode Snippet
1️⃣ Loading rulesloadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
2️⃣ OverridesconfigOverrides := &clientcmd.ConfigOverrides{}
3️⃣ Flagsflags := clientcmd.RecommendedConfigOverrideFlags("")
4️⃣ Bindflags.BindFlags(fs); flags.BindFlagSet(fs, configOverrides)
5️⃣ MergekubeConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, configOverrides); restConfig, _ := kubeConfig.ClientConfig()
6️⃣ Clientclient, _ := 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.NewInteractiveDeferredLoadingClientConfig
  • clientcmd.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!

Back to Blog

Related posts

Read more »

StatefulSet project

Prerequisites A StatefulSet requires the following components: - Headless Service – provides stable DNS for each pod. - StatefulSet manifest – defines the pods...