K8s二开之 client-go 初探

一、client-go简介

client-go 可以快速方便的对k8s的一些数据进行自定义整合,在k8s 运维中,我们可以使用kubectl、科幻库或者REST请求来访问K8S API。而实际上,无论是kubectl 还是客户端,都是分装了REST请求的工具。client-go作为一个客户端库,能够调用K8S API, 实现对K8S集群中资源对象(包括deployment、service、ingress、replicaSet、pod、namespace、node等)的增删改查等操作。

二、简介

结构图

从上图可以看到client-go 中包含四种cient,ClientSet,DynamicClinet,DiscoveryClient 都是RestClient的具体实现。

目录结构

# tree client-go/ -L 1
client-go/
  ├── discovery
  ├── dynamic
  ├── informers
  ├── kubernetes
  ├── listers
  ├── plugin
  ├── rest
  ├── scale
  ├── tools
  ├── transport
  └── util
  • discovery:提供DiscoveryClient 发现客户端
  • dynmic:提供DynamicClient 动态客户端
  • informers:每种kubernetes资源的informer 实现
  • kubernetes:提供ClinetSet 客户端
  • listers:为每一个 Kubernetes 资源提供 Lister 功能,该功能对 Get 和 List 请求提供只读的缓存数据
  • plugin:提供 OpenStack、GCP 和 Azure 等云服务商授权插件
  • rest:提供 RESTClient 客户端,对 Kubernetes API Server 执行 RESTful 操作
  • scale:提供 ScaleClient 客户端,用于扩容或缩容 Deployment、ReplicaSet、Relication Controller 等资源对象
  • tools:提供常用工具,例如 SharedInformer、Reflector、DealtFIFO 及 Indexers。提供 Client 查询和缓存机制,以减少向 kube-apiserver 发起的请求数等
  • transport:提供安全的 TCP 连接,支持 Http Stream,某些操作需要在客户端和容器之间传输二进制流,例如 exec、attach 等操作。该功能由内部的 spdy 包提供支持
  • util:提供常用方法,例如 WorkQueue 功能队列、Certificate 证书管理等

三 认证

这里值得一提的是go mod通过go client 对接k8s的时候有个小坑那么就是我们需要在go mod文件中指定k8s版本 不然会默认拉去最新的k8s版本的包 我们也知道不同的k8s版本的api会出现差异

# APISERVER=$(kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}')
# TOKEN=$(kubectl get secret $(kubectl get serviceaccount default -o jsonpath='{.secrets[0].name}') -o # jsonpath='{.data.token}' | base64 --decode )
# curl $APISERVER/api --header "Authorization: Bearer $TOKEN" --insecure

四 client-go四种类型

4.1 RestClient

RestClient 是最基础的客户端,RestClient基于http request 进行了封装,实现了restful的api,可以直接通过 是RESTClient提供的RESTful方法如Get(), Put(), Post(), Delete() 进行交互,同事支持Json 和 protobuf,支持所有原生资源和CRDs,但是,一般而言,为了更为优雅的处理,需要进一步封装,通过Clientset封装RESTClient,然后在对外提供结构和服务。

package main

import (
  "fmt"
  corev1 "k8s.io/api/core/v1"
  metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  "k8s.io/client-go/kubernetes/scheme"
  "k8s.io/client-go/rest"
  "k8s.io/client-go/tools/clientcmd"
)

var configFile = "../config"
var ApiPath = "api"
var nameSpace = "kube-system"
var resouce = "pods"

func main() {
  // 生成config
  config, err := clientcmd.BuildConfigFromFlags("", configFile)
  if err != nil {
    panic(err)
  }
  config.APIPath = ApiPath
  config.GroupVersion = &corev1.SchemeGroupVersion
  config.NegotiatedSerializer = scheme.Codecs

  // 生成restClient
  restClient, err := rest.RESTClientFor(config)
  if err != nil {
    panic(err)
  }
  // 声明空结构体
  rest := &corev1.PodList{}
  if err = restClient.Get().Namespace(nameSpace).Resource("pods").VersionedParams(&metav1.ListOptions{Limit: 500},
    scheme.ParameterCodec).Do().Into(rest); err != nil {
    panic(err)
  }
  for _, v := range rest.Items {
    fmt.Printf("NameSpace: %v  Name: %v  Status: %v \n", v.Namespace, v.Name, v.Status.Phase)
  }
}

4.2 ClientSet

ClientSet在RestClient的基础上封装了对Resouorce和Version的管理方法一个Resource可以理解为一个客户端,而ClientSet是多个客户端的集合

其操作资源对象时需要指定Group、指定Version,然后根据Resource获取,但是clientset不支持自定义crd。

package main

import (
  "flag"
  "fmt"
  metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  "k8s.io/client-go/kubernetes"
  "k8s.io/client-go/tools/clientcmd"
  "k8s.io/client-go/util/homedir"
  "path/filepath"
)

// ~/.kube/config
func ParseConfig(configPath string) (*kubernetes.Clientset, error) {
  var kubeconfigPath *string
  if home := homedir.HomeDir(); home != "" {
    kubeconfigPath = flag.String("kubeconfig", filepath.Join(home, ".kube", "config"), "(optional) absolute path to the kubeconfig file")
  } else {
    kubeconfigPath = flag.String("kubeconfig", configPath, "absolute path to the kubeconfig file")
  }
  flag.Parse()
  config, err := clientcmd.BuildConfigFromFlags("", *kubeconfigPath)
  if err != nil {
    return nil, err
  }
  // 生成clientSet
  clientSet, err := kubernetes.NewForConfig(config)
  if err != nil {
    return clientSet, err
  }
  return clientSet, nil
}

func ListCm(c *kubernetes.Clientset, ns string) error {
  configMaps, err := c.CoreV1().ConfigMaps(ns).List(metav1.ListOptions{})
  if err != nil {
    return err
  }
  for _, cm := range configMaps.Items {
    fmt.Printf("configName: %v, configData: %v \n", cm.Name, cm.Data)
  }
  return nil
}

func ListNodes(c *kubernetes.Clientset) error {
  nodeList, err := c.CoreV1().Nodes().List(metav1.ListOptions{})
  if err != nil {
    return err
  }
  for _, node := range nodeList.Items {
    fmt.Printf("nodeName: %v, status: %v", node.GetName(), node.GetCreationTimestamp())
  }
  return nil
}

func ListPods(c *kubernetes.Clientset, ns string) {
  pods, err := c.CoreV1().Pods(ns).List(metav1.ListOptions{})
  if err != nil {
    panic(err)
  }
  for _, v := range pods.Items {
    fmt.Printf("namespace: %v podname: %v podstatus: %v \n", v.Namespace, v.Name, v.Status.Phase)
  }
}

func ListDeployment(c *kubernetes.Clientset, ns string) error {
  deployments, err := c.AppsV1().Deployments(ns).List(metav1.ListOptions{})
  if err != nil {
    return err
  }
  for _, v := range deployments.Items {
    fmt.Printf("deploymentname: %v, available: %v, ready: %v", v.GetName(), v.Status.AvailableReplicas, v.Status.ReadyReplicas)
  }
  return nil
}

func main() {
  var namespace = "kube-system"
  configPath := "../config"
  config, err := ParseConfig(configPath)
  if err != nil {
    fmt.Printf("load config error: %v\n", err)
  }
  fmt.Println("list pods")
  ListPods(config, namespace)
  fmt.Println("list cm")
  if err = ListCm(config, namespace); err != nil {
    fmt.Printf("list cm error: %v", err)
  }
  fmt.Println("list nodes")
  if err = ListNodes(config); err != nil {
    fmt.Printf("list nodes error: %v", err)
  }
  fmt.Println("list deployment")
  if err = ListDeployment(config, namespace); err != nil {
    fmt.Printf("list deployment error: %v", err)
  }
}

4.3 DynamicClient

DynamicClient是一种动态客户端它可以对任何资源进行restful操作包括crd自定义资源,不同于 clientset,dynamic client 返回的对象是一个 map[string]interface{},如果一个 controller 中需要控制所有的 API,可以使用dynamic client,目前它在 garbage collector 和 namespace controller中被使用。DynamicClient的处理过程将Resource例如podlist转换为unstructured类型,k8s的所有resource都可以转换为这个结构类型,处理完之后在转换为podlist,整个转换过程类似于接口转换就是通过interface{}的断言。

Dynamic client 是一种动态的client,他能处理kubernetes 所有的资源支持JSON

package main

import (
  "fmt"
  apiv1 "k8s.io/api/core/v1"
  metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  "k8s.io/apimachinery/pkg/runtime"
  "k8s.io/apimachinery/pkg/runtime/schema"
  "k8s.io/client-go/dynamic"
  "k8s.io/client-go/tools/clientcmd"
)

var namespace = "kube-system"

func main() {

  config, err := clientcmd.BuildConfigFromFlags("", "../config")
  if err != nil {
    panic(err)
  }

  dynamicClient, err := dynamic.NewForConfig(config)
  if err != nil {
    panic(err)
  }
  // 定义组版本资源
  gvr := schema.GroupVersionResource{Version: "v1", Resource: "pods"}
  unStructObj, err := dynamicClient.Resource(gvr).Namespace(namespace).List(metav1.ListOptions{})
  if err != nil {
    panic(err)
  }
  podList := &apiv1.PodList{}

  if err = runtime.DefaultUnstructuredConverter.FromUnstructured(unStructObj.UnstructuredContent(), podList); err != nil {
    panic(err)
  }

  for _, v := range podList.Items {
    fmt.Printf("namespaces:%v  name:%v status:%v \n", v.Namespace, v.Name, v.Status.Phase)
  }
}

4.4 DiscoveryClient

DiscoveryClient是发现客户端,主要用于发现api server支持的资源组 资源版本 资源信息,k8s api server 支持很多资源组 资源版本,资源信息,此时可以通过DiscoveryClient来查看

kubectl的api-version和api-resource也是通过DiscoveryClient来实现的,还可以将信息缓存在本地cache,以减轻api的访问压力,默认在./kube/cache和./kube/http-cache下

package main

import (
  "fmt"
  "k8s.io/apimachinery/pkg/runtime/schema"
  "k8s.io/client-go/discovery"
  "k8s.io/client-go/tools/clientcmd"
)

func main() {
  config, err := clientcmd.BuildConfigFromFlags("", "../config")
  if err != nil {
    panic(err)
  }
  discoverClient, err := discovery.NewDiscoveryClientForConfig(config)
  if err != nil {
    panic(err)
  }
  _, apiResourceList, err := discoverClient.ServerGroupsAndResources()
  for _, v := range apiResourceList {
    gv, err := schema.ParseGroupVersion(v.GroupVersion)
    if err != nil {
      panic(err)
    }
    for _, resource := range v.APIResources {
      fmt.Println("name:", resource.Name, "    ", "group:", gv.Group, "    ", "version:", gv.Version)
    }
  }

}