元鉴
返回中文阅读流

Kubernetes Blog

使用 clientcmd 统一访问 API 服务器

介绍如何用 Kubernetes 的 clientcmd 在 Go 程序中处理类似 kubectl 的命令行参数。

中文内容

已翻译official company source英文原文2026-01-19

使用 clientcmd 统一访问 API 服务器

By Stephen Kitt (Red Hat) | Monday, January 19, 2026

如果你曾想为 Kubernetes API 开发一个命令行客户端,尤其是考虑让你的客户端可作为 kubectl 插件使用,你可能会想:如何让 kubectl 用户觉得你的客户端用起来很熟悉。快速看一眼 kubectl options 的输出可能会让人有些泄气:“我真的要实现所有这些选项吗?”

别担心,其他人已经为你完成了大量相关工作。事实上,Kubernetes 项目提供了两个库,帮助你在 Go 程序中处理 kubectl 风格的命令行参数:clientcmd 和 cli-runtime(后者使用 clientcmd)。本文将展示如何使用前者。

总体理念

正如可以预期的那样,由于它是 client-go 的一部分,clientcmd 的最终目的是提供一个 restclient.Config 实例,用于向 API 服务器发出请求。

它遵循 kubectl 语义:

  • 默认值取自 ~/.kube 或等效位置;
  • 可以使用 KUBECONFIG 环境变量指定文件;
  • 以上所有设置还可以通过命令行参数进一步覆盖。

它不会设置 --kubeconfig 命令行参数,而你可能希望这样做以与 kubectl 保持一致;你将在“绑定标志”一节中看到如何实现。

可用功能

clientcmd 允许程序处理

  • kubeconfig 选择(使用 KUBECONFIG);
  • 上下文选择;
  • 命名空间选择;
  • 客户端证书和私钥;
  • 用户模拟;
  • HTTP Basic 认证支持(用户名/密码)。

配置合并

在各种场景下,clientcmd 支持合并配置设置:KUBECONFIG 可以指定多个文件,其内容会被组合。这可能令人困惑,因为设置会根据其实现方式向不同方向合并。如果某个设置定义在 map 中,则第一个定义生效,后续定义会被忽略。如果某个设置未定义在 map 中,则最后一个定义生效。

当使用 KUBECONFIG 检索设置时,缺失文件只会产生警告。如果用户显式指定路径(类似 --kubeconfig 的方式),则必须存在对应文件。

如果未定义 KUBECONFIG,则会使用默认配置文件 ~/.kube/config(如果存在)。

总体流程

clientcmd 包文档简洁地表达了一般使用模式:

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)
config, err := kubeConfig.ClientConfig()
if err != nil {
	// Do something
}
client, err := metav1.New(config)
// ...

在本文语境中,共有六个步骤:

  1. 配置加载规则。
  2. 配置覆盖项。
  3. 构建一组标志。
  4. 绑定标志。
  5. 构建合并后的配置。
  6. 获取 API 客户端。

配置加载规则

clientcmd.NewDefaultClientConfigLoadingRules() 会构建加载规则,这些规则将使用 KUBECONFIG 环境变量的内容,或默认配置文件名(~/.kube/config)。此外,如果使用默认配置文件,它还能够从(非常)旧的默认配置文件(~/.kube/.kubeconfig)迁移设置。

你可以构建自己的 ClientConfigLoadingRules,但在大多数情况下默认设置就足够了。

配置覆盖项

clientcmd.ConfigOverrides 是一个结构体,用于存储会覆盖通过加载规则得出的配置中已加载设置的覆盖项。在本文语境中,它的主要用途是存储从命令行参数获取的值。这些参数使用 pflag 库处理;pflag 是 Go 的 flag 包的即插即用替代品,并增加了对带双连字符的长名称参数的支持。

在大多数情况下,覆盖项中无需设置任何内容;我只会将它们绑定到标志。

构建一组标志

在此语境中,标志是命令行参数的一种表示,指定其长名称(如 --namespace)、短名称(如有,例如 -n)、默认值,以及显示在用法信息中的描述。标志存储在 FlagInfo 结构体实例中。

有三组可用标志,分别表示以下命令行参数:

  • 认证参数(证书、令牌、模拟、用户名/密码);
  • 集群参数(API 服务器、证书颁发机构、TLS 配置、代理、压缩)
  • 上下文参数(集群名称、kubeconfig 用户名、命名空间)

推荐的选择包括全部三组,并带有一个具名上下文选择参数和一个超时参数。

这些都可以通过 Recommended…Flags 函数获得。函数接受一个前缀,该前缀会添加到所有参数长名称之前。

因此,调用 clientcmd.RecommendedConfigOverrideFlags("") 会产生诸如 --context、--namespace 等命令行参数。--timeout 参数的默认值为 0,--namespace 参数有对应的短形式 -n。添加前缀(例如 "from-")会产生诸如 --from-context、--from-namespace 等命令行参数。在只涉及单个 API 服务器的命令中,这看起来可能并不特别有用,但在涉及多个 API 服务器时(例如多集群场景)会很方便。

这里有一个潜在陷阱:前缀不会修改短名称,因此如果使用多个前缀,--namespace 需要谨慎处理:只有其中一个前缀可以与 -n 短名称关联。你必须清除其他前缀的 --namespace 所关联的短名称;如果没有合理的 -n 关联,也许需要清除所有前缀的短名称。可以按如下方式清除短名称:

kflags := clientcmd.RecommendedConfigOverrideFlags(prefix)
kflags.ContextOverrideFlags.Namespace.ShortName = ""

类似地,也可以通过清除长名称来完全禁用标志:

kflags.ContextOverrideFlags.Namespace.LongName = ""

绑定标志

定义好一组标志后,可以使用 clientcmd.BindOverrideFlags 将命令行参数绑定到覆盖项。这需要 pflag FlagSet,而不是 Go 的 flag 包中的 FlagSet。

如果你还想绑定 --kubeconfig,应在此时通过绑定加载规则中的 ExplicitPath 来完成:

flags.StringVarP(&loadingRules.ExplicitPath, "kubeconfig", "", "", "absolute path(s) to the kubeconfig file(s)")

构建合并后的配置

有两个函数可用于构建合并后的配置:

  • 正文:clientcmd.NewInteractiveDeferredLoadingClientConfig
  • 正文:clientcmd.NewNonInteractiveDeferredLoadingClientConfig

顾名思义,两者的区别在于:第一个可以使用提供的 reader 以交互方式请求认证信息,而第二个只处理调用方提供给它的信息。

这些函数名中的“deferred”指最终配置会尽可能晚地确定。这意味着这些函数可以在解析命令行参数之前调用,而生成的配置会使用到实际构建时已经解析出的任何值。

获取 API 客户端

合并后的配置会以 ClientConfig 实例形式返回。可以通过调用其 ClientConfig() 方法从中获取 API 客户端。

如果未给出任何配置(KUBECONFIG 为空或指向不存在的文件,~/.kube/config 不存在,且未通过命令行参数提供配置),默认设置会返回一个晦涩的错误,其中提到 KUBERNETES_MASTER。这是遗留行为;曾多次尝试移除它,但为了 --kubectl 中的 --local 和 --dry-run 命令行参数而保留。你应通过调用 clientcmd.IsEmptyConfig() 检查“空配置”错误,并提供更明确的错误消息。

Namespace() 方法也很有用:它会返回应使用的命名空间。它还会指示该命名空间是否由用户(使用 --namespace)覆盖。

完整示例

下面是一个完整示例。

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
	loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()

	// Overrides and flag (command line argument) setup
	configOverrides := &clientcmd.ConfigOverrides{}
	flags := pflag.NewFlagSet("clientcmddemo", pflag.ExitOnError)
	clientcmd.BindOverrideFlags(configOverrides, flags,
		clientcmd.RecommendedConfigOverrideFlags(""))
	flags.StringVarP(&loadingRules.ExplicitPath, "kubeconfig", "", "", "absolute path(s) to the kubeconfig file(s)")
	flags.Parse(os.Args)

	// Client construction
	kubeConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, configOverrides)
	config, err := kubeConfig.ClientConfig()
	if err != nil {
		if clientcmd.IsEmptyConfig(err) {
			panic("Please provide a configuration pointing to the Kubernetes API server")
		}
		panic(err)
	}
	client, err := kubernetes.NewForConfig(config)
	if err != nil {
		panic(err)
	}

	// How to find out what namespace to use
	namespace, overridden, err := kubeConfig.Namespace()
	if err != nil {
		panic(err)
	}
	fmt.Printf("Chosen namespace: %s; overridden: %t\n", namespace, overridden)

	// Let's use the client
	nodeList, err := client.CoreV1().Nodes().List(context.TODO(), v1.ListOptions{})
	if err != nil {
		panic(err)
	}
	for _, node := range nodeList.Items {
		fmt.Println(node.Name)
	}
}

祝编码愉快,感谢你有兴趣实现具有熟悉使用模式的工具!

  • ← 上一篇
  • 下一篇 →
Last modified January 18, 2026 at 1:46 PM PST: Reorganize 2026 blog content (b81b14ba1b)

原文标题

Uniform API server access using clientcmd