使用 clientcmd 实现统一的 API 服务器访问
Source: Kubernetes Blog
如果你曾想为 Kubernetes API 开发一个命令行客户端——尤其是可以作为 kubectl 插件使用的——你可能会想如何让你的客户端对 kubectl 用户来说感觉熟悉。
快速浏览 kubectl options 的输出可能会让人望而生畏:
“我真的需要实现所有这些选项吗?”
别担心!Kubernetes 项目已经提供了两个库,帮你完成大部分繁重的工作:
- clientcmd – 解析类似 kubectl 的命令行参数并构建
restclient.Config。 - cli‑runtime – 基于
clientcmd构建。
本文聚焦于 clientcmd 库。
通用理念
因为 clientcmd 是 client‑go 的一部分,它的最终目标是生成一个可以向 API 服务器发送请求的 restclient.Config。它遵循与 kubectl 相同的语义:
| Source | Description |
|---|---|
~/.kube/config (or equivalent) | 默认配置 |
KUBECONFIG environment variable | KUBECONFIG 环境变量——一个或多个文件,其内容将被合并 |
| Command‑line arguments | 命令行参数——覆盖上述任意设置 |
clientcmd 不会 自动添加 --kubeconfig 标志;您将在 “绑定标志” 部分看到如何添加它。
可用功能
clientcmd 让程序能够处理:
- kubeconfig 选择(via
KUBECONFIG) - 上下文选择
- 命名空间选择
- 客户端证书 & 私钥
- 用户冒充
- HTTP Basic 认证(username/password)
配置合并
KUBECONFIG可以包含一个 冒号分隔的文件列表;它们的内容会被合并。- 基于映射的设置 – 第一次 定义生效,后续的会被忽略。
- 非映射设置 – 最后一次 定义生效。
KUBECONFIG中引用的缺失文件仅会产生 警告。- 如果用户显式提供路径(例如
--kubeconfig),则该文件 必须存在。 - 当
KUBECONFIG未设置时,使用~/.kube/config(如果存在)。
整体流程
典型的使用模式在 clientcmd 包文档中有概述:
// 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
}
在本文中,我们将逐步演示 六个步骤,对应上面代码中的编号注释:
- 配置加载规则
- 配置覆盖项
- 构建一组标志(flags)
- 绑定这些标志
- 生成合并后的配置
- 获取 API 客户端
1️⃣ 配置加载规则
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
- 如果已设置
KUBECONFIG,则使用它;否则回退到~/.kube/config。 - 如果使用默认文件,它还能从非常旧的默认位置(
~/.kube/.kubeconfig)迁移设置。
如果需要非标准的顺序或额外的文件,可以创建自定义的 ClientConfigLoadingRules,但默认设置已能满足大多数情况。
2️⃣ 配置覆盖
clientcmd.ConfigOverrides 是一个结构体,用于存储将 覆盖 从 kubeconfig 文件加载的任何值。在本指南中,覆盖将通过命令行标志(使用 pflag 库,它是 Go 的 flag 包的直接替代)进行填充。
configOverrides := &clientcmd.ConfigOverrides{}
通常你不需要在这里手动设置任何内容;你将在下一步将结构体字段绑定到标志。
3️⃣ 构建一组 Flag
Flag 代表一个命令行参数(例如 --namespace 或 -n)。clientcmd 提供了三组 Flag,每组都封装在 FlagInfo 结构体中:
| Flag 组别 | 常用参数 |
|---|---|
| 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 |
推荐 的 Flag 集合包括 全部三组,再加上 --timeout Flag。
推荐的 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-")
--timeoutFlag 的默认值为0。--namespaceFlag 还会获得一个短别名-n。
注意: 前缀 不会 影响短名称。如果创建多个带前缀的 Flag 集合,只有 一个 能保留 -n 别名。需要在其他集合上清除短名称:
kflags := clientcmd.RecommendedConfigOverrideFlags(prefix)
kflags.ContextOverrideFlags.Namespace.ShortName = "" // removes -n
4️⃣ 绑定标志
现在将标志定义连接到 ConfigOverrides 结构体,使得在命令行中输入的值能够填充到覆盖配置中。
// 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
如果你还想要一个 --kubeconfig 标志(与 kubectl 保持一致),可以手动添加:
fs.StringVar(&configOverrides.ClusterInfo.KubeConfigPath, "kubeconfig", "", "Path to the kubeconfig file")
最后,解析命令行参数:
if err := fs.Parse(os.Args[1:]); err != nil {
// handle parse error
}
5️⃣ 构建合并后的配置
在加载规则和覆盖已准备好的情况下,创建延迟加载的客户端配置并解析最终的 rest.Config:
kubeConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
loadingRules,
configOverrides,
)
restConfig, err := kubeConfig.ClientConfig()
if err != nil {
// handle error (e.g., missing kubeconfig, invalid overrides)
}
restConfig 现在包含了以下来源的合并设置:
- 由
KUBECONFIG(或默认文件)引用的文件 - 通过标志提供的覆盖项
6️⃣ 获取 API 客户端
使用 rest.Config 实例化所需的任意 Kubernetes 客户端。以下是一些常见示例:
// 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)
从这里你就可以像 kubectl 那样与集群交互。
TL;DR – 六步检查清单
| 步骤 | 代码片段 |
|---|---|
| 1️⃣ 加载规则 | loadingRules := clientcmd.NewDefaultClientConfigLoadingRules() |
| 2️⃣ 覆盖 | configOverrides := &clientcmd.ConfigOverrides{} |
| 3️⃣ 标志 | flags := clientcmd.RecommendedConfigOverrideFlags("") |
| 4️⃣ 绑定 | flags.BindFlags(fs); flags.BindFlagSet(fs, configOverrides) |
| 5️⃣ 合并 | kubeConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, configOverrides); restConfig, _ := kubeConfig.ClientConfig() |
| 6️⃣ 客户端 | client, _ := kubernetes.NewForConfig(restConfig) |
使用这些步骤,你可以让你的 Go CLI 拥有与 kubectl 相同的外观和体验,而无需自行重新实现每个标志。祝编码愉快!
禁用标志
如果你想完全禁用某个标志,请清空它的长名称:
kflags.ContextOverrideFlags.Namespace.LongName = ""
Bind the Flags (alternative)
一旦定义了一组标志,就可以使用 clientcmd.BindOverrideFlags 将命令行参数绑定到覆盖。这需要一个 pflag FlagSet(而不是 Go 标准的 flag 包)。
如果你还想绑定 --kubeconfig,现在就通过在加载规则中绑定 ExplicitPath 来实现:
flags.StringVarP(&loadingRules.ExplicitPath,
"kubeconfig", "", "", "absolute path(s) to the kubeconfig file(s)")
Build the Merged Configuration (alternative)
有两个函数可用于构建合并后的配置:
clientcmd.NewInteractiveDeferredLoadingClientConfigclientcmd.NewNonInteractiveDeferredLoadingClientConfig
交互式版本可以通过提供的读取器交互式地请求身份验证信息,而 非交互式版本仅使用调用者提供的信息。两者都是“延迟”的:你可以在解析命令行参数之前调用它们;实际构建时,生成的配置会合并截至那时已解析的所有标志值。
获取 API 客户端(替代方案)
合并后的配置会作为 ClientConfig 实例返回。您可以通过调用 ClientConfig() 方法从中获取 API 客户端。
如果未找到配置(例如 KUBECONFIG 为空或指向不存在的文件,~/.kube/config 不存在,且未提供命令行覆盖),默认设置会返回一个提及 KUBERNETES_MASTER 的模糊错误。此遗留行为仅在 kubectl 的 --local 和 --dry-run 标志下保留。
提示: 使用 clientcmd.IsEmptyConfig(err) 检测 “空配置” 错误,并向用户展示更清晰的提示信息。
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 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)
}
}
祝编码愉快,感谢您对使用熟悉的使用模式构建工具的兴趣!