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+目的端口。