TIPS之 Kubernetes client-go使用方式

Kubernetes client-go使用方式

Posted by 董江 on Tuesday, January 10, 2023

Kubernetes client-go使用方式

Kubernetes上,通常需要Client来访问Kubernetes中的对象,目前最常用的是RESTClient, DynamicClientClientSet这三种Client

最基础的RESTClient

RESTClientKubernetes最基础的Client,直接负责与Request(RESTClient中的概念)打交道。下面的Demo就描述如何生成一个RESTClient,并用该RESTClient获取某具体Pod的详细信息。

package main
import (
	"flag"
	"fmt"
	"k8s.io/client-go/pkg/runtime"
	"k8s.io/client-go/pkg/runtime/serializer"
	"k8s.io/client-go/pkg/api"
	v1 "k8s.io/client-go/pkg/api/v1"
	"k8s.io/client-go/pkg/api/unversioned"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/clientcmd"
)

func main() {
	kubeconfig := flag.String("kubeconfig", "/root/.kube/config", "Path to a kube config. Only required if out-of-cluster.")
	flag.Parse()
	config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig)
	if err != nil {
		fmt.Println("BuildConfigFromFlags error")
	}
	groupversion := &unversioned.GroupVersion{"", "v1"} // group version
	config.GroupVersion = groupversion
	config.APIPath = "/api"  // api path : api or apis
	config.ContentType = runtime.ContentTypeJSON //content type : json or yaml
	config.NegotiatedSerializer = serializer.DirectCodecFactory{CodecFactory: api.Codecs}
	restClient, err := rest.RESTClientFor(config)
	if err != nil {
		fmt.Println("RESTClientFor error")
	}
	pod := v1.Pod{} // response 数据序列化为 对象object
	err = restClient.Get().Resource("pods").Namespace("default").Name("nginx-153451235-bw4j7").Do().Into(&pod)
	if err != nil {
		fmt.Println("error")
	}
	fmt.Println(pod)
}

DynamicClient

DynamicClient是对RESTClient的封装,支持动态设置访问类型。 解决RESTClient需要自己设置请求各属性,用起来很不方便的痛点

package main
import (
	"flag"
	"fmt"
	"reflect"
	"encoding/json"
	"k8s.io/client-go/pkg/api/v1"
	"k8s.io/client-go/pkg/api/unversioned"
	"k8s.io/client-go/dynamic"
	"k8s.io/client-go/tools/clientcmd"
	"k8s.io/client-go/rest"
)
func main() {
	kubeconfig := flag.String("kubeconfig", "/root/.kube/config", "Path to a kube config. Only required if out-of-cluster.")
	fmt.Println(kubeconfig)
	flag.Parse()
	config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig)
	if err != nil {
		fmt.Println("error1")
	}
	// 生成dynamicClient
	gv := &unversioned.GroupVersion{"", "v1"}
	resource := &unversioned.APIResource{Name: "pods", Namespaced: true}
	config.ContentConfig = rest.ContentConfig{GroupVersion: gv}
	config.APIPath = "/api"
	dynamicClient, err := dynamic.NewClient(config)
	if err != nil {
		fmt.Println("dynamic NewCLient error")
	}
	// 获取所有namespace的pod列表
	obj, err := dynamicClient.Resource(resource, "").List(&v1.ListOptions{})
	if err != nil {
		fmt.Println("dynamicClient Resource error")
	}
	js, err := json.Marshal(reflect.ValueOf(obj).Elem().Interface())
	if err != nil {
		fmt.Println("error")
	}
	podlist := v1.PodList{}
	json.Unmarshal(js, &podlist)
	fmt.Println(podlist)
	fmt.Println("------------------------")
	// 获取具体具体pod
	obj, err = dynamicClient.Resource(resource, "default").Get("nginx-153451235-bw4j7")
    if err != nil {
        fmt.Println("dynamicClient Resource error")
    }
    js, err = json.Marshal(obj)
    if err != nil {
        fmt.Println("error")
    }
    pod := v1.Pod{}
    json.Unmarshal(js, &pod)
    fmt.Println(pod)
}

ClientSet

ClientSet也是对RESTClient的一种封装,与DynamicClient不同的是,ClientSet支持衍生出具体资源的Client,如PodClientNodeClient等。ClientSetKubernetes用的最多的Client类型 。

package main
import (
	"flag"
	"fmt"
	apiv1 "k8s.io/client-go/pkg/api/v1"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/tools/clientcmd"
)
func main() {
	kubeconfig := flag.String("kubeconfig", "/root/.kube/config", "Path to a kube config. Only required if out-of-cluster.")
	fmt.Println(kubeconfig)
	flag.Parse()
	config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig)
	if err != nil {
		fmt.Println("error")
	}
	// 生成clientset
	clientset, err := kubernetes.NewForConfig(config)
	if err != nil {
		fmt.Println("error")
	}
	// 生成podCLient
	podClient := clientset.Core().Pods("")
	pods, err := podClient.List(apiv1.ListOptions{})
	if err != nil {
		fmt.Println("error")
	}
	for _, pod := range pods.Items {
		fmt.Println(pod)
	}
}

案例:如何访问自定义CRD

customlimitranges CRD 为例: 技术方案之 对 Kubernetes Pod进程网络带宽 流量控制

部署 自定义CRD: customlimitranges.custom.cmss.com

dongjiang@MacBook Pro:crds $ kubectl get crds customlimitranges.custom.cmss.com 
NAME                                CREATED AT
customlimitranges.custom.cmss.com   2023-01-10T05:17:47Z

dongjiang@MacBook Pro:crds $ kubectl describe crds customlimitranges.custom.cmss.com 
Name:         customlimitranges.custom.cmss.com
Namespace:    
Labels:       <none>
Annotations:  <none>
API Version:  apiextensions.k8s.io/v1
Kind:         CustomResourceDefinition
Metadata:
  Creation Timestamp:  2023-01-10T05:17:47Z
  Generation:          1
  Managed Fields:
    API Version:  apiextensions.k8s.io/v1
    Fields Type:  FieldsV1
    fieldsV1:
      f:status:
....

dongjiang@MacBook Pro:crd $ kubectl api-resources | grep "cmss"
customlimitranges                 clr          custom.cmss.com/v1                     true         CustomLimitRange

使用 DynamicClient 操作 CRD

package main

import (
	"bufio"
	"context"
	"flag"
	"fmt"
	"os"
	"path/filepath"

	apiv1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/client-go/dynamic"
	"k8s.io/client-go/tools/clientcmd"
	"k8s.io/client-go/util/homedir"
	"k8s.io/client-go/util/retry"
)

func main() {
	var kubeconfig *string
	if home := homedir.HomeDir(); home != "" {
		kubeconfig = flag.String("kubeconfig", filepath.Join(home, ".kube", "config"), "(optional) absolute path to the kubeconfig file")
	} else {
		kubeconfig = flag.String("kubeconfig", "", "absolute path to the kubeconfig file")
	}
	flag.Parse()

	namespace := "default"

	config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig)
	if err != nil {
		panic(err)
	}
	client, err := dynamic.NewForConfig(config)
	if err != nil {
		panic(err)
	}

	CRDRes := schema.GroupVersionResource{Group: "custom.cmss.com", Version: "v1", Resource: "customlimitranges"}

	crd := &unstructured.Unstructured{
		Object: map[string]interface{}{
			"apiVersion": "custom.cmss.com/v1",
			"kind":       "CustomLimitRange",
			"metadata": map[string]interface{}{
				"name": "demo-crd",
			},
			"spec": map[string]interface{}{
				"limitrange": map[string]interface{}{
					"type": "pod",
					"max": map[string]interface{}{
						"ingress-bandwidth": "1G",
						"egress-bandwidth":  "1G",
					},
					"min": map[string]interface{}{
						"ingress-bandwidth": "10M",
						"egress-bandwidth":  "10M",
					},
					"default": map[string]interface{}{
						"ingress-bandwidth": "128M",
						"egress-bandwidth":  "128M",
					},
				},
			},
		},
	}

	// Create CRD
	fmt.Println("Creating CRD...")
	result, err := client.Resource(CRDRes).Namespace(namespace).Create(context.TODO(), crd, metav1.CreateOptions{})
	if err != nil {
		fmt.Printf("result: %v, err: %v", result, err)
		panic(err)
	}
	fmt.Printf("Created CRD %q.\n", result.GetName())

	// Update CRD
	prompt()
	fmt.Println("Updating CRD...")

	retryErr := retry.RetryOnConflict(retry.DefaultRetry, func() error {
		result, getErr := client.Resource(CRDRes).Namespace(namespace).Get(context.TODO(), "demo-crd", metav1.GetOptions{})
		if getErr != nil {
			panic(fmt.Errorf("failed to get latest version of CRD: %v", getErr))
		}

		// update limitrange to max.ingress-bandwidth = 2G
		if err := unstructured.SetNestedField(result.Object, "2G", "spec", "limitrange", "max", "ingress-bandwidth"); err != nil {
			panic(fmt.Errorf("failed to set max.ingress-bandwidth value: %v", err))
		}

		// update limitrange to min.egress-bandwidth = 1M
		if err := unstructured.SetNestedField(result.Object, "1M", "spec", "limitrange", "min", "egress-bandwidth"); err != nil {
			panic(fmt.Errorf("failed to set max.ingress-bandwidth value: %v", err))
		}

		_, updateErr := client.Resource(CRDRes).Namespace(namespace).Update(context.TODO(), result, metav1.UpdateOptions{})
		return updateErr
	})

	if retryErr != nil {
		panic(fmt.Errorf("update failed: %v", retryErr))
	}
	fmt.Println("Updated CRD...")

	// List CRD
	prompt()
	fmt.Printf("Listing CRD in namespace %q:\n", apiv1.NamespaceDefault)
	list, err := client.Resource(CRDRes).Namespace(namespace).List(context.TODO(), metav1.ListOptions{})
	if err != nil {
		panic(err)
	}
	for _, d := range list.Items {
		fmt.Printf(" * %s (%v)\n", d.GetName(), d)
	}

	// Delete CRD
	prompt()
	fmt.Println("Deleting CRD ...")
	deletePolicy := metav1.DeletePropagationForeground
	deleteOptions := metav1.DeleteOptions{
		PropagationPolicy: &deletePolicy,
	}
	if err := client.Resource(CRDRes).Namespace(namespace).Delete(context.TODO(), "demo-crd", deleteOptions); err != nil {
		panic(err)
	}

	fmt.Println("Deleted CRD.")
}

func prompt() {
	fmt.Printf("-> Press Return key to continue.")
	scanner := bufio.NewScanner(os.Stdin)
	for scanner.Scan() {
		break
	}
	if err := scanner.Err(); err != nil {
		panic(err)
	}
	fmt.Println()
}

结果:

dongjiang@MacBook Pro:dynamicclient $ ./dynamicclient -kubeconfig=/Users/dongjiang/Library/Application\ Support/Lens/kubeconfigs/510622b4-0ca1-4448-991d-ca4a0dcf89fd
Creating CRD...
Created CRD "demo-crd".
-> Press Return key to continue.

Updating CRD...
Updated CRD...
-> Press Return key to continue.

Listing CRD in namespace "default":
 * demo-crd ({map[apiVersion:custom.cmss.com/v1 kind:CustomLimitRange metadata:map[creationTimestamp:2023-01-10T06:31:19Z generation:2 managedFields:[map[apiVersion:custom.cmss.com/v1 fieldsType:FieldsV1 fieldsV1:map[f:spec:map[.:map[] f:limitrange:map[.:map[] f:default:map[.:map[] f:egress-bandwidth:map[] f:ingress-bandwidth:map[]] f:max:map[.:map[] f:egress-bandwidth:map[] f:ingress-bandwidth:map[]] f:min:map[.:map[] f:egress-bandwidth:map[] f:ingress-bandwidth:map[]] f:type:map[]]]] manager:dynamicclient operation:Update time:2023-01-10T06:31:19Z]] name:demo-crd namespace:default resourceVersion:48348363 selfLink:/apis/custom.cmss.com/v1/namespaces/default/customlimitranges/demo-crd uid:ab3e8e1b-6640-47c5-99ad-1422f34c8b6b] spec:map[limitrange:map[default:map[egress-bandwidth:128M ingress-bandwidth:128M] max:map[egress-bandwidth:1G ingress-bandwidth:2G] min:map[egress-bandwidth:1M ingress-bandwidth:10M] type:pod]]]})
 * test-rangelimit ({map[apiVersion:custom.cmss.com/v1 kind:CustomLimitRange metadata:map[annotations:map[kubectl.kubernetes.io/last-applied-configuration:{"apiVersion":"custom.cmss.com/v1","kind":"CustomLimitRange","metadata":{"annotations":{},"name":"test-rangelimit","namespace":"default"},"spec":{"limitrange":{"default":{"egress-bandwidth":"128000k","ingress-bandwidth":"500M"},"max":{"egress-bandwidth":"1G","ingress-bandwidth":"1G"},"min":{"egress-bandwidth":"100M","ingress-bandwidth":"100M"},"type":"Pod"}}}
] creationTimestamp:2023-01-10T06:09:55Z generation:1 managedFields:[map[apiVersion:custom.cmss.com/v1 fieldsType:FieldsV1 fieldsV1:map[f:metadata:map[f:annotations:map[.:map[] f:kubectl.kubernetes.io/last-applied-configuration:map[]]] f:spec:map[.:map[] f:limitrange:map[.:map[] f:default:map[.:map[] f:egress-bandwidth:map[] f:ingress-bandwidth:map[]] f:max:map[.:map[] f:egress-bandwidth:map[] f:ingress-bandwidth:map[]] f:min:map[.:map[] f:egress-bandwidth:map[] f:ingress-bandwidth:map[]] f:type:map[]]]] manager:kubectl-client-side-apply operation:Update time:2023-01-10T06:09:55Z]] name:test-rangelimit namespace:default resourceVersion:48340023 selfLink:/apis/custom.cmss.com/v1/namespaces/default/customlimitranges/test-rangelimit uid:09fe7ad8-1efe-4742-85c1-919645476be1] spec:map[limitrange:map[default:map[egress-bandwidth:128000k ingress-bandwidth:500M] max:map[egress-bandwidth:1G ingress-bandwidth:1G] min:map[egress-bandwidth:100M ingress-bandwidth:100M] type:Pod]]]})
-> Press Return key to continue.

Deleting CRD ...
Deleted CRD.

「如果这篇文章对你有用,请随意打赏」

Kubeservice博客

如果这篇文章对你有用,请随意打赏

使用微信扫描二维码完成支付