You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

单元测试中K8S NewBuilder查询Fake CR资源失败问题咨询

Kubectl自定义资源CR单元测试问题解决方法

问题背景

通过k8s.io/kubectl/pkg/cmd/util/factory.go中的Factory.NewBuilder().Do()查询K8S自定义资源(CR),使用k8s.io/kubectl/pkg/cmd/testing/fake.go中的TestFactory编写单元测试时遇到两个问题:

  • 用FakeDynamicClient创建的Fake资源无法被NewBuilder().Do()查询到
  • 改用UnstructuredClient伪造响应时,提示error: the server doesn't have a resource type "myCRDresource"

解决方案

方案一:正确配置FakeDynamicClient(推荐)

Kubectl的NewBuilder()处理CR时依赖动态客户端和发现机制,仅设置FakeDynamicClient不够,还需注册CR API到Scheme并配置Fake发现客户端,让Builder识别资源类型。

示例代码:

import (
    "testing"
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
    fakedynamic "k8s.io/client-go/dynamic/fake"
    "k8s.io/client-go/kubernetes/scheme"
    fakeopenapi "k8s.io/client-go/openapi/fake"
    "k8s.io/kubectl/pkg/apis/openapi"
    appsv1alpha1 "your/module/api/v1alpha1" // 替换为你的CR API包路径
)

func TestCRQueryWithFakeDynamicClient(t *testing.T) {
    // 1. 注册CR API到全局Scheme
    if err := appsv1alpha1.AddToScheme(scheme.Scheme); err != nil {
        t.Fatalf("注册Scheme失败: %v", err)
    }

    // 2. 创建测试用CR实例
    cv1 := &appsv1alpha1.ClusterVersion{
        ObjectMeta: metav1.ObjectMeta{Name: "cv-1", Namespace: "default"},
        // 按需填充spec/status字段
    }
    cv2 := &appsv1alpha1.ClusterVersion{
        ObjectMeta: metav1.ObjectMeta{Name: "cv-2", Namespace: "default"},
    }

    // 3. 初始化FakeDynamicClient并注入测试资源
    fakeDynamicClient := fakedynamic.NewSimpleDynamicClient(scheme.Scheme, cv1, cv2)

    // 4. 配置TestFactory
    tf := cmdtesting.NewTestFactory()
    defer tf.Cleanup()

    // 配置FakeOpenAPIGetter,让Builder识别CR资源类型
    tf.FakeOpenAPIGetter = &fakeopenapi.FakeOpenAPIGetter{
        Resources: openapi.Resources{
            "clusterversions.apps.yourdomain.com": { // 替换为你的CR的GroupVersionResource标识
                Group:   "apps.yourdomain.com",
                Version: "v1alpha1",
                Kind:    "ClusterVersion",
            },
        },
    }
    tf.FakeDynamicClient = fakeDynamicClient

    // 5. 执行查询逻辑
    builder := tf.NewBuilder().
        WithScheme(scheme.Scheme).
        Namespace("default").
        Resource("clusterversions"). // 替换为你的CR资源名称
        Do()

    // 6. 验证查询结果
    var result appsv1alpha1.ClusterVersionList
    if err := builder.Unstructured().Into(&result); err != nil {
        t.Fatalf("反序列化结果失败: %v", err)
    }
    if len(result.Items) != 2 {
        t.Fatalf("期望2条结果,实际得到%d条", len(result.Items))
    }
}

方案二:修复UnstructuredClient配置

若坚持使用UnstructuredClient,需同时配置Fake发现客户端,让Kubectl确认CR资源类型存在:

import (
    "net/http"
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    "k8s.io/apimachinery/pkg/runtime/schema"
    "k8s.io/apimachinery/pkg/runtime/serializer"
    "k8s.io/client-go/discovery/fake"
    "k8s.io/client-go/rest"
    cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
    "k8s.io/kubectl/pkg/resource"
    appsv1alpha1 "your/module/api/v1alpha1"
)

func TestCRQueryWithUnstructuredClient(t *testing.T) {
    // 1. 生成测试CR列表
    cv1 := &appsv1alpha1.ClusterVersion{ObjectMeta: metav1.ObjectMeta{Name: "cv-1", Namespace: "default"}}
    cv2 := &appsv1alpha1.ClusterVersion{ObjectMeta: metav1.ObjectMeta{Name: "cv-2", Namespace: "default"}}
    cvlists := getFakeClusterVersionList(cv1, cv2)

    // 2. 配置FakeDiscoveryClient
    fakeDiscovery := fake.NewSimpleDiscoveryClient()
    fakeDiscovery.Fake.Resources = []*metav1.APIResourceList{
        {
            GroupVersion: appsv1alpha1.SchemeGroupVersion.String(),
            APIResources: []metav1.APIResource{
                {
                    Name:       "clusterversions",
                    Namespaced: true,
                    Kind:       "ClusterVersion",
                    Verbs:      []string{"get", "list"},
                },
            },
        },
    }

    // 3. 初始化TestFactory
    tf := cmdtesting.NewTestFactory()
    defer tf.Cleanup()

    // 关联FakeDiscoveryClient到ClientConfig
    tf.ClientConfigVal = &rest.Config{
        Host:            "http://fake-k8s-server",
        DiscoveryClient: fakeDiscovery,
    }

    // 4. 配置UnstructuredClient
    codec := resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer
    tf.UnstructuredClient = &fake.RESTClient{
        NegotiatedSerializer: codec,
        Resp: &http.Response{
            StatusCode: http.StatusOK,
            Header:     cmdtesting.DefaultHeader(),
            Body:       cmdtesting.ObjBody(codec, cvlists),
        },
    }

    // 5. 执行查询并验证结果(逻辑同方案一)
    // ...
}

// 你的CR列表生成函数
func getFakeClusterVersionList(clusterVersions ...*appsv1alpha1.ClusterVersion) *appsv1alpha1.ClusterVersionList {
    res := &appsv1alpha1.ClusterVersionList{
        ListMeta: metav1.ListMeta{ResourceVersion: "1"},
        Items:    make([]appsv1alpha1.ClusterVersion, 0),
    }
    for _, cv := range clusterVersions {
        res.Items = append(res.Items, *cv.DeepCopy())
    }
    return res
}

关键注意事项

  • 必须注册CR API到Scheme:确保Kubectl的序列化/反序列化机制能识别CR的结构
  • 配置Fake发现机制:NewBuilder()会先通过发现客户端验证资源类型,必须伪造CR的API资源信息
  • 优先选择FakeDynamicClient:动态客户端的Fake实现更贴近真实场景,无需手动构造HTTP响应,维护成本更低

内容的提问来源于stack exchange,提问作者al al

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 11:37:06