目录
什么是 ClientSet
ClientSet
顾名思义,就是一堆 Client 的集合。
在 client-go 中,ClientSet 声明了对于 Kubernetes 内置资源的操作接口的集合,代码位于 kubernetes/client-go
Fake ClientSet
顾名思义就是 “假” 的 Client 集合,实现了 ClientSet
的接口,可以对 client-go 的代码进行模拟操作,可直接用于业务代码的单元测试,代码在 kubneretes/client-go 中。
实践环节
接下来我们将实际操作一下常见的几种 fake clientset 的用法
我们构造一个简单的场景,比如我们在业务逻辑中需要对 Node
进行更新标签的操作,逻辑如下:
import ( "context"metav1"k8s.io/apimachinery/pkg/apis/meta/v1""k8s.io/client-go/kubernetes") funcLabelNode(ctxcontext.Context, cskubernetes.Interface, nodeNamestring, labelsmap[string]string) error { node, err :=cs.CoreV1().Nodes().Get(ctx, nodeName, metav1.GetOptions{}) iferr!=nil { returnerr } for_, l :=rangelabels { node.Labels[l] =labels[l] } _, err=cs.CoreV1().Nodes().Update(ctx, node, metav1.UpdateOptions{}) iferr!=nil { returnerr } returnnil}
其中 kubernetes.Interface
就是 ClientSet
实现的标准接口,比如常见的通过 ~/.kube/config
加载 restConfig 再创建 ClientSet
,得到的ClientSet
就实现了 kubernetes.Interface
rc, err :=clientcmd.BuildConfigFromFlags("", "~/.kube/config") iferr!=nil { panic(err) } cs, err :=kubernetes.NewForConfig(rc) iferr!=nil { panic(err) }
接下来我们编写上述逻辑的单元测试,首先我们先来看一下完整的代码
提示:
这里只做简单单元测试的演示,并不涉及到单元测试结构的合理性
packagenodesimport ( "testing""reflect""context"corev1"k8s.io/api/core/v1"metav1"k8s.io/apimachinery/pkg/apis/meta/v1"fakeclientset"k8s.io/client-go/kubernetes/fake") funcTest_LabelNode(t*testing.T) { cs :=fakeclientset.NewSimpleClientset(&corev1.Node{ ObjectMeta: metav1.ObjectMeta{ Name: "fake-node-1", Labels: map[string]string{ "fake.label.kubernetes.io": "fake", }, }, }) newLabels :=map[string]string{ "fake.label.kubernetes.io": "new-value", } err :=LabelNode(context.Background(), cs, "fake-node-1", newLabels) iferr!=nil { t.Error(err) return } got, err :=cs.CoreV1().Nodes().Get(context.Background(), "fake-node-1", metav1.GetOptions{}) iferr!=nil { t.Error(err) return } if!reflect.DeepEqual(got.Labels, newLabels) { t.Error(err) return } }
可以通过 NewSimpleClientset
来生成 fake clientSet
,这里的入参是 objects ...runtime.Object
,其中 runtime.Object
接口是 Kubernetes 资源都会实现的接口,也就是我们需要传入期望可以进行 CRUD 的假资源对象列表。
提示:
这里
runtime.Object
不仅限于内置资源外,自定义资源也可以
cs :=fakeclientset.NewSimpleClientset(&corev1.Node{ ObjectMeta: metav1.ObjectMeta{ Name: "fake-node-1", Labels: map[string]string{ "fake.label.kubernetes.io": "fake", }, }, })
获取到 ClientSet
后,我们可以将 Client
作为入参传入到逻辑中,并且按照业务逻辑传入需要更新的 Labels
newLabels :=map[string]string{ "fake.label.kubernetes.io": "new-value", } err :=LabelNode(context.Background(), cs, "fake-node-1", newLabels) iferr!=nil { t.Error(err) return}
此时我们再通过这个 ClientSet
去调用,获取的资源就是经过逻辑处理后的了,通过 reflect.DeepEqual 进行比较。
同样的,如果我们在处理逻辑中对初始化时传入的资源进行了 Delete
等操作,资源同样会被销毁掉。
在我们对 Kubernetes 资源的操作中,fakeClient 有些逻辑操作是无法实现的,比如对于 fieldSelector
的筛选,是不支持的,详细可以参考这个 isuue:https://github.com/kubernetes/kubernetes/issues/78824
如下逻辑中,如果对 DeploymentList 通过 fakeClient 进行过滤,无法生效, labelSelector
是可以的。
// parse field selectordefaultSelector := []string{ fmt.Sprintf("metadata.namespace!=%s", metav1.NamespaceDefault), fmt.Sprintf("metadata.namespace!=%s", metav1.NamespaceSystem), } fieldSelector, err :=fields.ParseSelector(strings.Join(defaultSelector, ",")) iferr!=nil { returnnil, nil, err} options.FieldSelector=fieldSelector.String() deployList, err :=d.cs.AppsV1().Deployments(metav1.NamespaceNone).List(context.Background(), options) iferr!=nil { returnnil, nil, err}
除了上述我们提到的NewSimpleClientset
,快速创建一个 FakeClient,还可以通过添加 AddReactor 来对 action 进行判断,增加更详细的判断逻辑, 比如如下案例:
cs :=&fakeclientset.Clientset{} cs.AddReactor("*", "deployments", func(actionclientgotesting.Action) (handledbool, retruntime.Object, errerror) { returntrue, &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{ Name: name, Namespace: namespace, }, Spec: appsv1.DeploymentSpec{ Selector: &metav1.LabelSelector{ MatchLabels: map[string]string{ "app": "busybox", }, }, Template: corev1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Labels: map[string]string{ "app": "busybox", }, }, Spec: corev1.PodSpec{ Containers: []corev1.Container{ { Name: "container-1", Image: "busybox", }, }, }, }, }, }, nil})
相关链接: