K8s二开之 client-go 初探

一 client-go简介

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

二 简介

2.1 结构图

K8s二开之 client-go 初探

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

2.2 目录结构

# tree client-go/ -L 1
client-go/
  ├── discovery
  ├── dynamic
  ├── informers
  ├── kubernetes
  ├── listers
  ├── plugin
  ├── rest
  ├── scale
  ├── tools
  ├── transport
  └── util
  • discovery:提供 DiscoveryClient 发现客户端
  • dynamic:提供 DynamicClient 动态客户端
  • informers:每种 kubernetes 资源的 Informer 实现
  • kubernetes:提供 ClientSet 客户端
  • 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会出现差异

3.1 认证

3.1.1 token

# 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

K8s二开之 client-go 初探

四 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)
    }
}

K8s二开之 client-go 初探

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)
    }
}

K8s二开之 client-go 初探

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)
    }
}

K8s二开之 client-go 初探

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)
        }
    }

}

K8s二开之 client-go 初探

五 其他

学习client-go,可以非常方便的利用其对k8s集群资源进行操作,kubeconfig→rest.config→clientset→具体的client(CoreV1Client)→具体的资源对象(pod)→RESTClient→http.Client→HTTP请求的发送及响应

通过clientset中不同的client和client中不同资源对象的方法实现对kubernetes中资源对象的增删改查等操作,常用的client有CoreV1Client、AppsV1beta1Client、ExtensionsV1beta1Client等。

本篇为简单利用client-go 实现简单的k8s资源操作,后期利用例如 kubebuilder 和 operator-SDK 编写operator也需要深入的理解和学习client-go。后期继续继续深入学习。

参考链接

上一篇:Python自定义阿里云RDS备份策略


下一篇:Cobra + Client-go实现K8s 自定义插件开发