client-go 基于不同kubeconfig会创建多条长连接

k8s.io/client-go v0.31.2

问题现象

package main

import (
	"context"
	"test/signals"
	"time"

	core_v1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/tools/clientcmd"
	klog "k8s.io/klog/v2"
)

func addListWatchCfgAndClient(stopCh <-chan struct{}) {
	cfg1, err := clientcmd.BuildConfigFromFlags("", "/root/.kube/config")
	if err != nil {
		klog.Fatalf("Error building kubeconfig: %s", err.Error())
	}

	kubeClient1, err := kubernetes.NewForConfig(cfg1)
	if err != nil {
		klog.Fatalf("Error building kubernetes clientset: %v", err)
	}

	cfg2, err := clientcmd.BuildConfigFromFlags("", "/root/.kube/config")
	if err != nil {
		klog.Fatalf("Error building kubeconfig: %s", err.Error())
	}

	kubeClient2, err := kubernetes.NewForConfig(cfg2)
	if err != nil {
		klog.Fatalf("Error building kubernetes discoveryclient: %v", err)
	}

	ns := &core_v1.Namespace{
		ObjectMeta: metav1.ObjectMeta{
			Name: "test",
		},
	}
	if _, err := kubeClient1.CoreV1().Namespaces().Create(context.Background(), ns, metav1.CreateOptions{}); err != nil {
		klog.Infof("create ns test failed, err is %v", err)
	} else {
		klog.Infof("create ns test success")
	}

	_, err = kubeClient2.ServerResourcesForGroupVersion("v1")
	if err != nil {
		klog.Errorf("get groups and resources failed")
	} else {
		klog.Infof("get groups and resources ok")
	}

	select {
	case <-stopCh:
		return
	case <-time.After(time.Hour):
	}
}

func main() {
	stopCh := signals.SetupSignalHandler()
	addListWatchCfgAndClient(stopCh)
}

只有1条长连接。

package main

import (
	"context"
	"test/signals"
	"time"

	core_v1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/tools/clientcmd"
	klog "k8s.io/klog/v2"
)

func addListWatchCfgAndClient(stopCh <-chan struct{}) {
	cfg1, err := clientcmd.BuildConfigFromFlags("", "/root/.kube/config")
	if err != nil {
		klog.Fatalf("Error building kubeconfig: %s", err.Error())
	}

	kubeClient1, err := kubernetes.NewForConfig(cfg1)
	if err != nil {
		klog.Fatalf("Error building kubernetes clientset: %v", err)
	}

	cfg2, err := clientcmd.BuildConfigFromFlags("", "/root/scheduler.conf")
	if err != nil {
		klog.Fatalf("Error building kubeconfig: %s", err.Error())
	}

	kubeClient2, err := kubernetes.NewForConfig(cfg2)
	if err != nil {
		klog.Fatalf("Error building kubernetes discoveryclient: %v", err)
	}

	ns := &core_v1.Namespace{
		ObjectMeta: metav1.ObjectMeta{
			Name: "test",
		},
	}
	if _, err := kubeClient1.CoreV1().Namespaces().Create(context.Background(), ns, metav1.CreateOptions{}); err != nil {
		klog.Infof("create ns test failed, err is %v", err)
	} else {
		klog.Infof("create ns test success")
	}

	_, err = kubeClient2.ServerResourcesForGroupVersion("v1")
	if err != nil {
		klog.Errorf("get groups and resources failed")
	} else {
		klog.Infof("get groups and resources ok")
	}

	select {
	case <-stopCh:
		return
	case <-time.After(time.Hour):
	}
}

func main() {
	stopCh := signals.SetupSignalHandler()
	addListWatchCfgAndClient(stopCh)
}

有2条长连接。

两者区别只是第2个kubeconfig使用了kube-scheduler的。

问题分析

NewForConfig函数

使用相同根证书、客户端公钥证书、客户端私钥证书的k8s client-go所有客户端,使用相同transport对象即使用同一个连接池,在目的ip+目的端口相同时使用同一条连接。其中,transport是包含自己独属连接池的对象。

tlsTransportCache的get方法
获取Client的transport

场景1:第1次基于文件初始化创建kubeconfig,没有命中map,因为证书相同,第2次命中,生成相同transport。
场景2:第1次基于文件初始化创建kubeconfig,没有命中map,因为证书不同,第2次没命中,生成不同transport。

transport的RoundTripOpt方法
从连接池中获取连接后发送请求并获取响应

场景1:第1次发送请求时没有连接,走后面流程创建连接并放入自己transport的连接池;第2次发送请求时从该transport的连接池中获取连接。
场景2:第1次发送请求时没有连接,走后面流程创建连接并放入自己transport的连接池;第2次发送请求时自己transport(transport对象不同于第1次)没有连接,走后面流程创建连接并放入自己transport的连接池。

clientConnPool的getClientConn方法
连接池中key是目的ip+目的端口。

posted on 2024-10-27 16:57  王景迁  阅读(7)  评论(0编辑  收藏  举报

导航