0%

Client-go

Client-go是golang用来操作kubernetes的客户端

k8s官方文档示例

集群内外运行差异

out-of-cluster

此示例演示了在集群外如何使用client-go操作集群,默认读取$HOME/.kube/config

main.go

package main

import (
"context"
"flag"
"fmt"
"path/filepath"
"time"

"github.com/jianglei-w/go-learning/utils/homedir"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
)

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

// use current context in kubeconfig
config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig)
if err != nil {
panic(err.Error())
}

// create the client set
clientset, err := kubernetes.NewForConfig(config)
if err != nil {
panic(err.Error())
}

for {
// kubectl get pods -n default
pods, err := clientset.CoreV1().Pods("default").List(context.TODO(), metav1.ListOptions{})
if err != nil {
panic(err.Error())
}
fmt.Printf("There are %d pods in the cluster\n", len(pods.Items))

namespace := "default"
pod := "httpd-2"

// kubectl get pods xxx -n default
_, err = clientset.CoreV1().Pods(namespace).Get(context.TODO(), pod, metav1.GetOptions{})
if errors.IsNotFound(err) {
fmt.Printf("Pod %s in namespace %s not found\n", pod, namespace)
} else if statuError, isStatus := err.(*errors.StatusError); isStatus {
fmt.Printf("Error getting pods %s in namespace %s: %v\n", pod, namespace, statuError.ErrStatus.Message)
} else if err != nil {
panic(err.Error())
} else {
fmt.Printf("Found pod %s in namespace %s", pod, namespace)
}

time.Sleep(time.Second * 10)
}
}

此示例每十秒请求所有default命名空间pod的数量,并且每10秒检测集群内是否有httpd-2的pod

in-cluster

此示例向您展示如何使用 client-go 配置客户端,以从 Kubernetes 集群内运行的应用程序向 Kubernetes API 进行身份验证。

当使用 rest.InClusterConfig() 时,client-go 使用安装在 Pod 内 /var/run/secrets/kubernetes.io/serviceaccount 路径的serviceaccount

main.go

package main

import (
"context"
"fmt"
"time"

"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
)

func main() {
config, err := rest.InClusterConfig()
if err != nil {
panic(err.Error())
}

clientset, err := kubernetes.NewForConfig(config)
if err != nil {
panic(err.Error())
}

for {
pods, err := clientset.CoreV1().Pods("").List(context.TODO(), metav1.ListOptions{})
if err != nil {
panic(err.Error())
}
fmt.Printf("有 %d 个pod在default命名空间", len(pods.Items))

_, err = clientset.CoreV1().Pods("default").Get(context.TODO(), "example-xxxxx", metav1.GetOptions{})
if err != nil {
panic(err.Error())
}
if errors.IsNotFound(err) {
fmt.Printf("Pod example-xxxxx not found in default namespace\n")
} else if statusError, isStatus := err.(*errors.StatusError); isStatus {
fmt.Printf("Error getting pod %v\n", statusError.ErrStatus.Message)
} else if err != nil {
panic(err.Error())
} else {
fmt.Printf("Found example-xxxxx pod in default namespace\n")
}

time.Sleep(time.Second * 10)
}
}

Dockerfile

FROM debian
COPY ./app /app
ENTRYPOINT /app

首先编译适用于 Linux 的应用程序:

GOOS=linux go build -o ./app .

如果您正在运行 Minikube 集群,则可以直接在 Minikube 节点的 Docker 引擎上构建此映像,而无需将其推送到docker hub。要在 Minikube 上构建镜像:

eval $(minikube docker-env)
docker build -t in-cluster:v1 .

如果您在集群上启用了 RBAC,请使用以下代码片段创建角色绑定,该绑定将授予默认服务帐户查看权限。不然Pod没有list get pod权限

kubectl create clusterrolebinding default-view --clusterrole=view --serviceaccount=default:default

然后,使用在 Pod 中运行该映像:

# kubectl run -i --rm  demo --image=in-cluster:v1 --image-pull-policy=IfNotPresent
There are 8 pods in the cluster
Pod example-xxxxx not found in default namespace
There are 8 pods in the cluster
Pod example-xxxxx not found in default namespace

要停止此示例并清理 pod,使用Ctrl + C ,然后运行:

# kubectl delete deployment demo

k8s-Deployment-CURD

main.go

package main

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

"github.com/jianglei-w/go-learning/utils/homedir"
appsv1 "k8s.io/api/apps/v1"
apiv1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
"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()

config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig)

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

deploymentsClient := clientset.AppsV1().Deployments(apiv1.NamespaceDefault)
// 构造deployment对象
deployment := &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: "demo-deployment",
},
Spec: appsv1.DeploymentSpec{
Replicas: int32Ptr(2),
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"app": "demo",
},
},
Template: apiv1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"app": "demo",
},
},
Spec: apiv1.PodSpec{
Containers: []apiv1.Container{
{
Name: "web",
Image: "nginx:1.12",
Ports: []apiv1.ContainerPort{
{
Name: "http",
Protocol: "TCP",
ContainerPort: 80,
},
},
},
},
},
},
},
}

// Create Deployment
fmt.Println("Creating deployment...")
result, err := deploymentsClient.Create(context.TODO(), deployment, metav1.CreateOptions{})
if err != nil {
panic(err)
}

fmt.Printf("Created deployment %q.\n", result.GetObjectMeta().GetName())

// Update Deployment
prompt()
fmt.Println("Updating deployment...")

retryErr := retry.RetryOnConflict(retry.DefaultRetry, func() error {

result, getErr := deploymentsClient.Get(context.TODO(), "demo-deployment", metav1.GetOptions{})
if getErr != nil {
panic(fmt.Errorf("Failed to get latest version of Deployment: %v", getErr))
}

result.Spec.Replicas = int32Ptr(1)
result.Spec.Template.Spec.Containers[0].Image = "nginx:1.13"
_, updateErr := deploymentsClient.Update(context.TODO(), result, metav1.UpdateOptions{})
return updateErr
})
if retryErr != nil {
panic(fmt.Errorf("Update failed: %v", retryErr))
}
fmt.Println("Updated deployment...")
// List Deployments
prompt()
fmt.Printf("Listing deployments in namespace %q:\n", apiv1.NamespaceDefault)
list, err := deploymentsClient.List(context.TODO(), metav1.ListOptions{})
if err != nil {
panic(err)
}

for _, d := range list.Items {
fmt.Printf(" * %s (%d replicas)\n", d.Name, *d.Spec.Replicas)
}

prompt()
fmt.Println("Deleting deployment...")
deletePolicy := metav1.DeletePropagationForeground
if err := deploymentsClient.Delete(context.TODO(), "demo-deployment", metav1.DeleteOptions{
PropagationPolicy: &deletePolicy,
}); err != nil {
panic(err)
}
fmt.Println("Deleted deployment.")
}

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

func int32Ptr(i int32) *int32 { return &i }

编译运行

# go build -o ./app
# ./app
Creating deployment...
Created deployment "demo-deployment".
-> Press Return key to continue.

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

Listing deployments in namespace "default":
* demo-deployment (1 replicas)
-> Press Return key to continue.

Deleting deployment...
Deleted deployment.

使用Dynamic包的CURD

dynamic 包使用简单类型 unstructured.Unstructured 来表示来自 API 服务器的所有对象值。类型 Unstructured 使用嵌套 map[string]interface{} 值的集合来创建与服务器中的 REST 负载非常相似的内部结构。

main.go

package main

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

"github.com/jianglei-w/go-learning/utils/homedir"
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/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()

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

gvr := schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployments"}

deployment := &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": map[string]interface{}{
"name": "demo-deployment",
},
"spec": map[string]interface{}{
"replicas": 2,
"selector": map[string]interface{}{
"matchLabels": map[string]interface{}{
"app": "demo",
},
},
"template": map[string]interface{}{
"metadata": map[string]interface{}{
"labels": map[string]interface{}{
"app": "demo",
},
},
"spec": map[string]interface{}{
"containers": []map[string]interface{}{
{
"name": "web",
"image": "nginx:1.12",
"ports": []map[string]interface{}{
{
"name": "http",
"protocol": "TCP",
"containerPort": 80,
},
},
},
},
},
},
},
},
}

fmt.Println("Creating deployment...")
result, err := client.Resource(gvr).Namespace(apiv1.NamespaceDefault).Create(context.TODO(), deployment, metav1.CreateOptions{})
if err != nil {
panic(err)
}
if err != nil {
panic(err)
}
fmt.Printf("Created deployment %q.\n", result.GetName())
prompt()
fmt.Println("Updating deployment...")
retryErr := retry.RetryOnConflict(retry.DefaultRetry, func() error {
// Retrieve the latest version of Deployment before attempting update
// RetryOnConflict uses exponential backoff to avoid exhausting the apiserver
result, getErr := client.Resource(gvr).Namespace(apiv1.NamespaceDefault).Get(context.TODO(), "demo-deployment", metav1.GetOptions{})
if getErr != nil {
panic(fmt.Errorf("failed to get latest version of Deployment: %v", getErr))
}

// update replicas to 1
if err := unstructured.SetNestedField(result.Object, int64(1), "spec", "replicas"); err != nil {
panic(fmt.Errorf("failed to set replica value: %v", err))
}

// extract spec containers
containers, found, err := unstructured.NestedSlice(result.Object, "spec", "template", "spec", "containers")
if err != nil || !found || containers == nil {
panic(fmt.Errorf("deployment containers not found or error in spec: %v", err))
}

// update container[0] image
if err := unstructured.SetNestedField(containers[0].(map[string]interface{}), "nginx:1.13", "image"); err != nil {
panic(err)
}
if err := unstructured.SetNestedField(result.Object, containers, "spec", "template", "spec", "containers"); err != nil {
panic(err)
}

_, updateErr := client.Resource(gvr).Namespace(apiv1.NamespaceDefault).Update(context.TODO(), result, metav1.UpdateOptions{})
return updateErr
})
if retryErr != nil {
panic(fmt.Errorf("update failed: %v", retryErr))
}
fmt.Println("Updated deployment...")

prompt()
fmt.Printf("Listing deployments in namespace %q:\n", apiv1.NamespaceDefault)
list, err := client.Resource(gvr).Namespace(apiv1.NamespaceDefault).List(context.TODO(), metav1.ListOptions{})
if err != nil {
panic(err)
}
for _, d := range list.Items {
replicas, found, err := unstructured.NestedInt64(d.Object, "spec", "replicas")
if err != nil || !found {
fmt.Printf("Replicas not found for deployment %s: error=%s", d.GetName(), err)
continue
}
fmt.Printf(" * %s (%d replicas)\n", d.GetName(), replicas)
}

prompt()
fmt.Println("Deleting deployment...")
deletePolicy := metav1.DeletePropagationForeground
deleteOptions := metav1.DeleteOptions{
PropagationPolicy: &deletePolicy,
}
if err := client.Resource(gvr).Namespace(apiv1.NamespaceDefault).Delete(context.TODO(), "demo-deployment", deleteOptions); err != nil {
panic(err)
}
fmt.Println("Deleted deployment.")
}

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

运行输出如下

Creating deployment...
Created deployment "demo-deployment".
-> Press Return key to continue.

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

Listing deployments in namespace "default":
* demo-deployment (1 replicas)
-> Press Return key to continue.

Deleting deployment...
Deleted deployment.

workerqueue

main.go

package main

import (
"flag"
"fmt"
"time"

v1 "k8s.io/api/core/v1"
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/util/workqueue"
"k8s.io/klog/v2"
)

type Controller struct {
indexer cache.Indexer
queue workqueue.RateLimitingInterface
informer cache.Controller
}

// NewController create a new controller
func NewController(queue workqueue.RateLimitingInterface, informer cache.Controller, indexer cache.Indexer) *Controller {
return &Controller{
indexer: indexer,
queue: queue,
informer: informer,
}
}

func (c *Controller) processNextItem() bool {
key, quit := c.queue.Get()
if quit {
return false
}

defer c.queue.Done(key)

err := c.syncToStdout(key.(string))
c.handleErr(err, key)
return true

}

func (c *Controller) syncToStdout(key string) error {
obj, exists, err := c.indexer.GetByKey(key)

if err != nil {
klog.Errorf("Fetching object with key %s from store failed with %v", key, err)
return err
}

if !exists {
fmt.Printf("Pod %s does not exist anymore\n", key)
} else {
fmt.Printf("Sync/Add/Update for Pod %s\n", obj.(*v1.Pod).GetName())
}

return nil
}

func (c *Controller) handleErr(err error, key interface{}) {
if err == nil {
c.queue.Forget(key)
return
}

if c.queue.NumRequeues(key) < 5 {
klog.Infof("Error syncing pod %v:%v\n", key, err)

c.queue.AddRateLimited(key)
return
}

c.queue.Forget(key)

runtime.HandleError(err)

klog.Infof("Dropping pod %q out of the queue: %v", key, err)
}

func (c *Controller) Run(workers int, stopCh chan struct{}) {
defer runtime.HandleCrash()

defer c.queue.ShutDown()

klog.Info("Starting Pod controller")

go c.informer.Run(stopCh)

if !cache.WaitForCacheSync(stopCh, c.informer.HasSynced) {
runtime.HandleError(fmt.Errorf("Timed out waiting for caches to sync"))
return
}

for i := 0; i < workers; i++ {
go wait.Until(c.runWorker, time.Second, stopCh)
}

<-stopCh
klog.Info("Stopping Pod Controller")
}

func (c *Controller) runWorker() {
for c.processNextItem() {

}
}

func main() {
var kubeconfig string
var master string

flag.StringVar(&kubeconfig, "kubeconfig", "", "absolute path to the kubeconfig file")
flag.StringVar(&master, "master", "", "master url")
flag.Parse()

// creates the connection
config, err := clientcmd.BuildConfigFromFlags(master, kubeconfig)
if err != nil {
klog.Fatal(err)
}

// creates the clientset
clientset := kubernetes.NewForConfigOrDie(config)

// create the watcher
podListWatcher := cache.NewListWatchFromClient(clientset.CoreV1().RESTClient(), "pods", "default", fields.Everything())

// create the workqueue
queue := workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter())

indexer, infomer := cache.NewIndexerInformer(podListWatcher, &v1.Pod{}, 0, cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
key, err := cache.MetaNamespaceKeyFunc(obj)
if err == nil {
queue.Add(key)
fmt.Println("Add...")
}
},
UpdateFunc: func(oldObj, newObj interface{}) {
key, err := cache.MetaNamespaceKeyFunc(newObj)
if err == nil {
queue.Add(key)
fmt.Println("update...")

}
},
DeleteFunc: func(obj interface{}) {
key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj)
if err == nil {
queue.Add(key)
fmt.Println("Delete...")

}
},
}, cache.Indexers{})

controller := NewController(queue, infomer, indexer)

indexer.Add(&v1.Pod{
ObjectMeta: meta_v1.ObjectMeta{
Name: "myPod",
Namespace: "default",
},
})

stop := make(chan struct{})
defer close(stop)
go controller.Run(1, stop)

select {}
}

Client种类

RestClient

RESTClient是所有客户端的父类,底层调用了Go语言net\http库,访问API Server的RESTful接口。查询pod为例

package main

import (
"context"
"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"
)

// 使用 Pod 名称获取 Pod 信息。

func main() {

// 从本机加载kubeconfig所以第一个参数为空字符串
config, err := clientcmd.BuildConfigFromFlags("", "/root/.kube/config")
if err != nil {
panic(err)
}
config.APIPath = "api"
config.GroupVersion = &corev1.SchemeGroupVersion
config.NegotiatedSerializer = scheme.Codecs

restClient, err := rest.RESTClientFor(config)
if err != nil {
panic(err)
}

pods := &corev1.PodList{}
err = restClient.Get().
Namespace(corev1.NamespaceDefault).
Resource("pods").
VersionedParams(&metav1.ListOptions{}, scheme.ParameterCodec).
Do(context.TODO()).
Into(pods)
if err != nil {
panic(err)
}
for _, v := range pods.Items {
fmt.Println(v.GetName())
}

}

Clientset

clientset是一个经过restclient封装的client,但是只能调用集群标准资源,不能调用CRD

package main

import (
"context"
"fmt"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
)

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

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

configMap, err := clientset.CoreV1().ConfigMaps("default").List(context.TODO(), metav1.ListOptions{})
if err != nil {
panic(err)
}

for _, cm := range configMap.Items {
fmt.Printf("ConfigMap Name: %v, configData: %v", cm.Name, cm.Data)
}

}

Dynamic

package main

import (
"context"
"log"

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

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

dynamic, err := dynamic.NewForConfig(config)
if err != nil {
panic(err)
}
gvr := schema.GroupVersionResource{
Group: "apps",
Version: "v1",
Resource: "deployments",
}
deploy := &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": map[string]interface{}{
"name": "demo-deployment",
},
"spec": map[string]interface{}{
"replicas": 2,
"selector": map[string]interface{}{
"matchLabels": map[string]interface{}{
"app": "demo",
},
},
"template": map[string]interface{}{
"metadata": map[string]interface{}{
"labels": map[string]interface{}{
"app": "demo",
},
},
"spec": map[string]interface{}{
"containers": []map[string]interface{}{
{
"name": "web",
"image": "nginx:1.12",
"ports": []map[string]interface{}{
{
"name": "http",
"protocol": "TCP",
"containerPort": 80,
},
},
},
},
},
},
},
},
}
_, err = dynamic.Resource(gvr).Namespace("default").Create(context.TODO(), deploy, metav1.CreateOptions{})

if err != nil {
panic(err)
}

log.Println("deployments created")
}

结尾

clientset 和 dynamic 是 Kubernetes 客户端库中两个不同的 API。clientset 使用预定义的资源类型和 API 版本来访问 Kubernetes API 服务器。dynamic 使用动态 API 来访问 Kubernetes API 服务器。
RESTClient 是 Kubernetes 客户端库中的基础 API。它提供了一个通用的 API 来访问任何 RESTful API 服务器。RESTClient 可以用来构建 clientset 和 dynamic 等更高级的 API。

RESTClient 的优点包括:

  • 通用性:RESTClient 可以用来访问任何 RESTful API 服务器,这提供了最大的灵活性。
  • 性能:RESTClient 使用 HTTP 协议来访问 API 服务器,这通常具有良好的性能。

RESTClient 的缺点包括:

  • 复杂性:RESTClient 比 clientset 和 dynamic 更复杂,需要更多的代码来使用。
  • 支持:RESTClient 可能不支持所有 Kubernetes API 版本。

clientset

clientset 是 Kubernetes 客户端库中的一个 API,它使用预定义的资源类型和 API 版本来访问 Kubernetes API 服务器。clientset 提供了一个简单的 API 来访问 Kubernetes 资源,包括创建、获取、更新和删除资源。

clientset 的优点包括:

  • 易于使用:clientset 提供了一个简单的 API 来访问 Kubernetes 资源。
  • 性能:clientset 使用预定义的资源类型和 API 版本来访问 Kubernetes API 服务器,这可以提高性能。

clientset 的缺点包括:

  • 灵活性:clientset 使用预定义的资源类型和 API 版本,这可能会限制灵活性。
  • 支持:clientset 可能不支持所有 Kubernetes API 版本。

dynamic

dynamic 是 Kubernetes 客户端库中的一个 API,它使用动态 API 来访问 Kubernetes API 服务器。dynamic 允许用户使用任意资源类型和 API 版本来访问 Kubernetes API 服务器。

dynamic 的优点包括:

  • 灵活性:dynamic 允许用户使用任意资源类型和 API 版本,这提供了最大的灵活性。
  • 支持:dynamic 支持所有 Kubernetes API 版本。

dynamic 的缺点包括:

  • 复杂性:dynamic 比 clientset 更复杂,需要更多的代码来使用。
  • 性能:dynamic 使用动态 API 来访问 Kubernetes API 服务器,这可能降低性能。

总结

clientset 和 dynamic 是 Kubernetes 客户端库中两个不同的 API。clientset 使用预定义的资源类型和 API 版本来访问 Kubernetes API 服务器,而 dynamic 使用动态 API 来访问 Kubernetes API 服务器。

clientset 的优点是易于使用和性能。dynamic 的优点是灵活性和支持。

选择哪种 API 取决于您的需求。如果您需要一个简单易用的 API,并且性能不是关键,那么 clientset 是一个不错的选择。如果您需要一个灵活的 API,并且需要支持所有 Kubernetes API 版本,那么 dynamic 是一个更好的选择。