使用client-go的模拟客户端来测试创建Kubernetes对象

这是ZOZO Advent Calendar 2023日历Vol.5的第四天的文章。

在本文中,我们将使用client-go的kubernetes/fake包来演示如何实现测试函数。
测试函数将检查被测试函数内的处理是否创建了Kubernetes对象。

假客户是指

通过使用kubernetes/fake包提供的Clientset,可以在执行机器的内存上模拟使用clientset进行对象获取和创建等操作,而无需实际向Kubernetes API发送请求。

Clientset是由client-go提供的Kubernetes包中的一个客户端,它包含了与Kubernetes API请求各种Kubernetes默认资源的功能,并在使用Go语言操作默认资源如Pod和Deployment时被使用。

Kubernetes的Clientset软件包符合Kubernetes软件包的Interface接口要求。同时,kubernetes/fake软件包提供的Clientset也满足Kubernetes软件包的Interface接口要求。

如果在测试对象函数的参数中使用`kubernetes`包的`Interface`接口来接收客户端,那么在传递类型时可以使用`kubernetes`包的`Clientset`和`kubernetes/fake`包的`Clientset`。

使用虚拟客户端的测试实现示例

被测试的函数

作为使用kubernetes/fake包的Clientset进行测试的示例,我们将创建以下程序的测试。
被测试的函数是replaceNameAndCreateNewPod()函数。

在这个函数中,根据传递的Pod对象,使用podName参数的值覆盖Name字段的值,创建一个新的Kubernetes对象。

客户端集参数的类型是kubernetes.Interface,这是关键之处。

package main

import (
	"context"
	"flag"
	"fmt"
	"os"

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

var (
	kubeconfig string
	podName    string
)

func main() {
	flag.StringVar(&kubeconfig, "kubeconfig", "", "kubeconfig path")
	flag.StringVar(&podName, "name", "", "Pod Name")
	flag.Parse()
	config, _ := clientcmd.BuildConfigFromFlags("", kubeconfig)
	clientset, _ := kubernetes.NewForConfig(config)

	base := &corev1.Pod{
		ObjectMeta: metav1.ObjectMeta{
			Name: "<REPLACE_THIS_FIELD>",
		},
		Spec: corev1.PodSpec{
			Containers: []corev1.Container{
				{
					Name:    "sleep-container",
					Image:   "alpine",
					Command: []string{"sleep", "3600"},
				},
			},
		},
	}

	_, err := replaceNameAndCreateNewPod(clientset, base, podName)

	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
}

func replaceNameAndCreateNewPod(clientset kubernetes.Interface, base *corev1.Pod, podName string) (*corev1.Pod, error) {
	newPod := base.DeepCopy()
	newPod.ObjectMeta.Name = podName

	_, err := clientset.CoreV1().Pods("default").Create(context.TODO(), newPod, metav1.CreateOptions{})

	if err != nil {
		return nil, err
	}

	return newPod, nil
}

考试函数

要测试replaceNameAndCreateNewPod()函数的行为,我们有以下三个观点。

    1. 在执行时没有发生错误

 

    1. 新的Pod对象的Name字段的值已经被修改

 

    通过clientset,新的Pod对象被创建了

测试函数的实现如下所示。

package main

import (
	"context"
	"testing"

	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes/fake"

	"github.com/stretchr/testify/assert"
)

func TestReplaceNameAndCreateNewPod(t *testing.T) {
	clientset := fake.NewSimpleClientset()

	original := &corev1.Pod{
		ObjectMeta: metav1.ObjectMeta{
			Name: "<REPLACE_THIS_FIELD>",
		},
		Spec: corev1.PodSpec{
			Containers: []corev1.Container{
				{
					Name:    "sleep-container",
					Image:   "alpine",
					Command: []string{"sleep", "3600"},
				},
			},
		},
	}

	tests := []struct {
		name     string
		input    string
		expected string
	}{
		{
			name:     "check equal to expected name",
			input:    "sample-pod",
			expected: "sample-pod",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			newPod, err := replaceNameAndCreateNewPod(clientset, original, tt.input)
			assert.NoError(t, err)
			assert.Equal(t, tt.expected, newPod.ObjectMeta.Name)

			_, err = clientset.CoreV1().Pods("default").Get(context.TODO(), tt.expected, metav1.GetOptions{})
			assert.NoError(t, err)
		})
	}
}

在TestReplaceNameAndCreateNewPod()函数的开头,调用了kubernetes/fake包中的NewSimpleClientset()函数,以初始化客户端。
在调用replaceNameAndCreateNewPod()时,可以看到我们传递了在这里初始化的客户端。

在测试中,首先要检查replaceNameAndCreateNewPod()函数的返回值和错误对象是否返回了预期的值。

  		newPod, err := replaceNameAndCreateNewPod(clientset, original, tt.input)
  		assert.NoError(t, err)
  		assert.Equal(t, tt.expected, newPod.ObjectMeta.Name)

下一步我们在以下部分进行检查,检查是否创建了预期的Pod对象。
fake.Clientset在这一部分中发挥了作用。

			_, err = clientset.CoreV1().Pods("default").Get(context.TODO(), tt.expected, metav1.GetOptions{})
			assert.NoError(t, err)

执行以上代码时,测试将会成功。

go test .                                                                                                                                                      
ok      main/create-pod 0.638s

對「假客戶」功能進行實驗。

当调用kubernetes/fake包的Clientset的Create()方法时,我们将尝试在内存中真实地创建对象。

create_pod.goのreplaceNameAndCreateNewPod()関数の一部実装をコメントアウトしてテストを実行します。(contextパッケージの参照もなくなるためimport文もコメントアウトしてください)

由于这个更改,将不再调用clientset的Create()方法,因此假设上述的第三个测试位置将失败。

func replaceNameAndCreateNewPod(clientset kubernetes.Interface, base *corev1.Pod, podName string) (*corev1.Pod, error) {
	newPod := base.DeepCopy()
	newPod.ObjectMeta.Name = podName

	// _, err := clientset.CoreV1().Pods("default").Create(context.TODO(), newPod, metav1.CreateOptions{})

	// if err != nil {
	// 	return nil, err
	// }

	return newPod, nil
}

変更後にテストを実行すると以下の出力を得ます。
出力から、3つ目の観点のチェック箇所でnot foundなエラーが返され、テストに失敗していることがわかります。

go test .                                                                                                                                                               19:22:34
--- FAIL: TestReplaceNameAndCreateNewPod (0.00s)
    --- FAIL: TestReplaceNameAndCreateNewPod/check_equal_to_expected_name (0.00s)
        create_pod_test.go:51: 
                Error Trace:    /Users/yutookamoto/work/create_k8s_custom_resource/create-pod/create_pod_test.go:51
                Error:          Received unexpected error:
                                pods "sample-pod" not found
                Test:           TestReplaceNameAndCreateNewPod/check_equal_to_expected_name
FAIL
FAIL    main/create-pod 0.549s
FAIL

このことから、kubernetes/fakeパッケージのClientsetはCreate()メソッドによるKubernetes APIへの操作をメモリ上で記録できており、テスト内でkubernetesパッケージのClientsetの動きを模倣できていることがわかりました。

Custom Resourceを扱う場合のfake client

今回は例としてPodオブジェクトの操作を題材にしため、kubernetes/fakeパッケージのClientsetを利用しました。
一方で、Custom Resourceのオブジェクトを扱う際にはclient-goが提供するdynamic/fakeパッケージのFakeDynamicClientを利用可能です。
こちらを利用することでdynamicパッケージのDynamicClientを模倣できます。

关于DynamicClient,请参考另一篇名为client-go的文章,其中详细解释了如何使用GET方法获取自定义资源对象。

总结

本記事では、client-goのkubernetes/fakeパッケージを使ってKubernetesオブジェクトの作成をテストする方法を紹介しました。

通过使用kubernetes/fake包中的Clientset对象,我们能够模拟kubernetes包中的Clientset对象的行为并进行测试。

需要注意的是,当使用kubernetes/fake包中的Clientset时,对于资源的写入到etcd以及Admission Webhook中的验证和变异将不会对目标资源起作用。请注意这一点。

请参考

    Kubernetes Programming with Go
广告
将在 10 秒后关闭
bannerAds