Skip to content

36|client-go 基础

HTTP 巡检能回答"入口能不能访问",但 Kubernetes 里的服务状态还会体现在资源对象上。接口返回 200 时,Deployment 可能已经少了副本;Pod 可能频繁重启;Node 可能 NotReady;Service 背后可能没有可用 Endpoint。这些字段都通过 API Server 读写,Go 程序要查询 Pod、Deployment、Node 这些对象,需要 Kubernetes 官方客户端库 client-go。

一、HTTP 检查与 Kubernetes 检查的互补关系

现象HTTP 巡检看到的结果Kubernetes 对象里能看到的信息
副本不足负载均衡还转到幸存 Pod,接口仍然 200Deployment 的 availableReplicas 少于期望值
Pod 频繁重启请求刚好打到正常 Pod,接口仍然 200Pod 的 restartCount 持续增加
节点异常入口层还有缓存或其他副本Node 的 condition 出现 Ready=False
Service 无后端入口可能直接 503EndpointSlice 里没有 ready endpoint

ops-checker 接入 Kubernetes 后,原来的 HTTP 检查仍然保留,再增加 Kubernetes 检查类型。一个工具同时记录入口状态和集群对象状态。

二、连接链路

执行 kubectl get pod 时,kubectl 读取 kubeconfig,找到当前集群地址、认证信息和 namespace,然后向 API Server 发请求。Go 程序走同样的链路:

对象解决的问题
kubeconfig配置文件,记录连哪个集群、用哪个用户、默认 namespace
RESTConfigGo 程序运行时使用的连接配置
ClientSet访问 Kubernetes 内置资源的客户端集合
Informer长时间监听资源变化并维护本地缓存

ops-checker 作为命令行巡检工具,使用 ClientSet 做一次性查询。Informer 属于长期运行的控制器场景,在 Operator 中才会用到。

三、配置扩展

原来的 configs/targets.json 只有 HTTP 目标:

json
[
  {
    "type": "http",
    "name": "api",
    "url": "https://example.com/health",
    "timeout_seconds": 3
  }
]

增加 Kubernetes Pod 检查后,目标仍然放在同一个配置文件里:

json
[
  {
    "type": "http",
    "name": "api",
    "url": "https://example.com/health",
    "timeout_seconds": 3
  },
  {
    "type": "kubernetes_pods",
    "name": "nginx-pods",
    "namespace": "default",
    "label_selector": "app=nginx",
    "min_ready": 2
  }
]

type 表示检查类型。HTTP 目标用 url,Kubernetes Pod 检查用 namespacelabel_selectormin_ready

Target 增加类型和 Kubernetes 字段:

go
type Target struct {
	Type           string `json:"type"`
	Name           string `json:"name"`
	URL            string `json:"url"`
	TimeoutSeconds int    `json:"timeout_seconds"`

	Namespace     string `json:"namespace"`
	LabelSelector string `json:"label_selector"`
	MinReady      int    `json:"min_ready"`
}

字段不是每种检查都会用到。type=http 时,Kubernetes 字段为空;type=kubernetes_pods 时,url 为空。检查类型继续增加后,再拆成更严格的配置结构。

四、依赖包

项目接入 Kubernetes 后,需要新增 client-go 相关依赖:

bash
go get k8s.io/client-go/kubernetes
go get k8s.io/client-go/tools/clientcmd
go get k8s.io/apimachinery/pkg/apis/meta/v1

client-go 版本通常跟 Kubernetes 主版本对齐,例如 Kubernetes 1.30 对应 client-go v0.30.x。版本差太多时,代码可能仍然能编译,但某些新字段、新资源类型或认证插件行为会不一致。

目录比上一版多了 internal/kube/

text
ops-checker/
├── cmd/
│   └── ops-checker/
│       └── main.go
├── configs/
│   └── targets.json
└── internal/
    ├── checker/
    │   ├── http.go
    │   ├── kubernetes.go
    │   ├── result.go
    │   └── runner.go
    ├── config/
    │   └── target.go
    ├── kube/
    │   └── client.go
    └── output/
        ├── json.go
        └── text.go

五、RESTConfig

kubeconfig 是 YAML 文件,常见位置:

场景路径
Linux 普通用户~/.kube/config
kubeadm 管理节点/etc/kubernetes/admin.conf
多配置合并KUBECONFIG 环境变量指定

kubectl 用来验证当前配置:

bash
kubectl config current-context
kubectl get ns

kubectl get ns 能验证三件事:API Server 地址能连通,认证信息能通过,当前身份至少有读取 namespace 的权限。这个命令失败时,Go 程序里创建 ClientSet 也很难成功。

internal/kube/client.go

go
package kube

import (
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/clientcmd"
)

func BuildConfig(kubeconfigPath string) (*rest.Config, error) {
	if kubeconfigPath == "" {
		return clientcmd.BuildConfigFromFlags("", clientcmd.RecommendedHomeFile)
	}
	return clientcmd.BuildConfigFromFlags("", kubeconfigPath)
}

func NewClientSet(kubeconfigPath string) (*kubernetes.Clientset, error) {
	config, err := BuildConfig(kubeconfigPath)
	if err != nil {
		return nil, err
	}
	return kubernetes.NewForConfig(config)
}

RESTConfig 还不是客户端,它只是"怎么连接 API Server"的运行时配置,包含地址、证书、token、QPS、Burst 等信息。NewForConfig 基于这份配置创建 ClientSet。

Pod 内运行的程序通常不用本机 kubeconfig,而是使用 ServiceAccount。Kubernetes 自动把 ServiceAccount 的 token 挂载到 /var/run/secrets/kubernetes.io/serviceaccount/token,把 CA 证书挂载到同目录的 ca.crt,把当前 namespace 写入 namespace 文件。rest.InClusterConfig() 读取这些挂载路径,自动构造连接配置:

go
import "k8s.io/client-go/rest"

config, err := rest.InClusterConfig()
if err != nil {
	return nil, err
}
clientset, err := kubernetes.NewForConfig(config)

本机开发时用 BuildConfigFromFlags(读 kubeconfig),部署到集群内时用 InClusterConfig()。两种方式可以按优先级封装:先尝试集群内配置,失败时回退到 kubeconfig。

六、ClientSet

ClientSet 是访问 Kubernetes 内置资源的类型化客户端。Pod 属于 core group 的 v1,通过 CoreV1() 访问。

给入口加 kubeconfig 参数:

go
kubeconfigPath := flag.String("kubeconfig", "", "kubeconfig path")

创建 ClientSet:

go
clientset, err := kube.NewClientSet(*kubeconfigPath)
if err != nil {
	fmt.Fprintln(os.Stderr, "create kubernetes client:", err)
	os.Exit(2)
}

外部运行时显式传 kubeconfig:

bash
go run ./cmd/ops-checker \
  -config configs/targets.json \
  -kubeconfig ~/.kube/config

ClientSet 创建失败属于工具自身错误,用退出码 2。常见原因是 kubeconfig 路径错误、认证插件不可用、证书不匹配、API Server 地址不可达。

七、Pod 检查

查询 Pod 对应的 kubectl 命令:

bash
kubectl get pod -n default -l app=nginx

client-go 里同一件事写成:

go
pods, err := clientset.CoreV1().Pods(target.Namespace).List(ctx, metav1.ListOptions{
	LabelSelector: target.LabelSelector,
})

target.Namespace 对应 -n defaultLabelSelector 对应 -l app=nginxListOptions 里还能放字段选择器、分页参数和 resourceVersion;当前 Pod 检查只用标签选择器。

internal/checker/kubernetes.go

go
package checker

import (
	"context"
	"fmt"

	"example.com/ops-checker/internal/config"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
)

func CheckKubernetesPods(ctx context.Context, clientset *kubernetes.Clientset, target config.Target) Result {
	pods, err := clientset.CoreV1().Pods(target.Namespace).List(ctx, metav1.ListOptions{
		LabelSelector: target.LabelSelector,
	})
	if err != nil {
		return Result{Name: target.Name, OK: false, Message: err.Error()}
	}

	ready := 0
	for _, pod := range pods.Items {
		if isPodReady(pod) {
			ready++
		}
	}

	minReady := target.MinReady
	if minReady <= 0 {
		minReady = 1
	}

	ok := ready >= minReady
	return Result{
		Name:    target.Name,
		OK:      ok,
		Message: fmt.Sprintf("ready=%d total=%d min_ready=%d", ready, len(pods.Items), minReady),
	}
}

func isPodReady(pod corev1.Pod) bool {
	if pod.Status.Phase != corev1.PodRunning {
		return false
	}
	for _, condition := range pod.Status.Conditions {
		if condition.Type == corev1.PodReady && condition.Status == corev1.ConditionTrue {
			return true
		}
	}
	return false
}

代码没有把 Running 直接当作健康。Pod 进入 Running 只说明容器已经启动,Ready condition 才说明它已经通过探针并能接流量。

八、检查分发

HTTP 和 Kubernetes 检查按 target.Type 分发。原来的 worker 只调用 checkHTTP,现在改成统一入口。

internal/checker/runner.go

go
func RunOne(ctx context.Context, clientset *kubernetes.Clientset, target config.Target) Result {
	switch target.Type {
	case "http":
		return checkHTTP(ctx, target)
	case "kubernetes_pods":
		return CheckKubernetesPods(ctx, clientset, target)
	default:
		return Result{Name: target.Name, OK: false, Message: "unknown target type: " + target.Type}
	}
}

worker 里把 checkHTTP 替换成 RunOne

go
for target := range jobs {
	results <- RunOne(ctx, clientset, target)
}

函数签名也要带上 ClientSet:

go
func Run(ctx context.Context, clientset *kubernetes.Clientset, targets []config.Target, concurrency int) []Result

HTTP 检查、Kubernetes 检查仍然走同一个结果模型、同一套输出和同一套退出码。新增检查类型时,主要是增加一个检查函数和一个 case

九、RBAC

client-go 最终仍然受 Kubernetes RBAC 控制。程序是否能 list Pod,取决于 kubeconfig 里的用户或 Pod 里的 ServiceAccount 有没有权限。

本机 kubeconfig 验证:

bash
kubectl auth can-i list pods -n default
kubectl auth can-i list pods -A

Pod 内 ServiceAccount 验证:

bash
kubectl auth can-i list pods \
  --as=system:serviceaccount:ops-system:ops-checker \
  -n default

常见错误:

错误含义排查入口
connection refusedAPI Server 地址不通或端口错kubeconfig、网络、代理
x509: certificate signed by unknown authorityCA 证书不匹配kubeconfig 里的 cluster CA
Unauthorized认证信息无效或过期token、证书、exec 登录插件
forbidden认证通过,但 RBAC 不允许kubectl auth can-i
context deadline exceeded请求超时网络、API Server 负载、DNS

forbiddenUnauthorized 不一样。Unauthorized 是身份没通过;forbidden 是身份通过了,但权限不够。

十、Informer

ops-checker 当前是一次性巡检命令:启动、读取配置、查询 API Server、输出结果、退出。这种模式用 ClientSet 的 List 就够了。

Informer 属于长期运行的程序。它会先 List 当前对象,再 Watch 后续变化,把对象放到本地缓存里:

只 Watch 不 List,会漏掉程序启动前已经存在的对象;只 List 不 Watch,又需要反复全量查询。Informer 把这两个动作合在一起,并处理断线重连和本地缓存。

ops-checker 处在命令行巡检形态时,ClientSet 的一次性查询已经够用。常驻进程或控制器形态再进入 Informer。

十一、kubectl 对照

client-go 代码里的问题,常用 kubectl 复现同一件事:

Go 程序动作kubectl 对照
读取 kubeconfigkubectl config view --minify
测试连接kubectl get ns
查询 Podkubectl get pod -n default -l app=nginx
看 Pod Readykubectl get pod -n default -o wide
看完整字段kubectl get pod <name> -n default -o yaml
查权限kubectl auth can-i list pods -n default

kubectl 使用同一份 kubeconfig、同一个 namespace、同一个 label selector 能查到对象,再回到程序里看 RESTConfig、ClientSet 调用和错误处理,能缩小问题范围。