3、CRD版本迁移测试基础:测试目标、测试范围、测试环境搭建、测试数据准备

CRD版本迁移是Operator演进中最容易引发生产故障的环节之一。测试的目标不是“验证迁移工具能用”,而是证明迁移前后CR实例的语义等价性、API兼容性以及Operator的平稳过渡能力。本节从测试目标、范围、环境搭建和数据准备四个维度建立可复用的测试基线。

3.1 测试目标

CRD版本迁移测试的核心目标可归纳为以下四个层次:

层次 目标描述 验证手段
L1 数据完整性 迁移后CR实例的所有字段值、嵌套结构、默认值、枚举值与原版本完全一致 字段级diff、JSON Patch比对
L2 语义等价性 同一CR实例在旧版和新版API下,Operator的Reconcile结果(状态、子资源、事件)一致 Reconcile输出比对、状态快照对比
L3 存储版本切换 etcd中存储的版本从旧版切换到新版后,读写路径无错误,无数据丢失 etcd数据导出校验、storage版本标记检查
L4 回滚安全 迁移后若发现问题,能通过CRD回滚恢复旧版本,且旧版Operator能正常读取新版存储的数据 回滚后Reconcile正常、无panic
测试优先级建议: L1和L2是必须通过的硬性条件,L3和L4可根据生产环境风险等级选择性执行。对于有状态CRD(如数据库集群、存储卷),L4回滚测试不可跳过。

3.2 测试范围

CRD版本迁移测试的范围应覆盖以下五个维度,避免只测“happy path”:

  • API版本组合: 覆盖v1→v2、v1→v1beta1→v2、v1→v2→v1回滚等典型迁移路径。
  • 字段变更类型: 新增字段、删除字段、重命名字段、字段类型变更(string→int)、嵌套结构变更、枚举值增减。
  • CR实例状态: 正常实例、处于异常/错误状态的实例、包含自定义状态子资源的实例、包含Finalizer的实例。
  • 并发场景: 迁移过程中有新的CR创建/更新/删除操作,验证并发安全。
  • 集群规模: 单实例、多实例(100+)、跨namespace分布、大规模CRD(字段数超过50)。
常见遗漏范围: 很多团队只测试了“空集群迁移”和“简单CR迁移”,忽略了包含Finalizer、OwnerReference、自定义状态子资源的CR实例,这些场景最容易在迁移后出现Reconcile死循环或数据不一致。

3.3 测试环境搭建

测试环境需要模拟真实集群的版本演进路径,同时支持快速重建和状态快照。推荐使用Kind或K3s搭建轻量级集群,配合etcdctl直接操作底层存储。

3.3.1 环境组件清单

组件 版本要求 用途
Kubernetes集群 v1.22+(支持CRD v1) 运行CRD和Operator
etcdctl v3.5+ 直接查看etcd中CRD存储版本
kubectl v1.24+ 管理CRD和CR实例
yq / jq 最新稳定版 YAML/JSON字段级比对
Operator SDK v1.25+ 构建和部署多版本Operator

3.3.2 环境搭建步骤

  1. 创建Kind集群并开启CRD v1beta1兼容:
    kind: Cluster
    apiVersion: kind.x-k8s.io/v1alpha4
    nodes:
      - role: control-plane
        extraMounts:
          - hostPath: /tmp/etcd-data
            containerPath: /var/lib/etcd
    kubeadmConfigPatches:
      - |
        apiVersion: kubeadm.k8s.io/v1beta3
        kind: ClusterConfiguration
        metadata:
          name: config
        etcd:
          local:
            dataDir: /var/lib/etcd
  2. 部署旧版CRD和Operator:
    kubectl apply -f config/crd/bases/example.io_myresources_v1.yaml
    kubectl apply -f config/manager/operator-v1.yaml
  3. 创建测试CR实例并等待Reconcile完成:
    kubectl apply -f testdata/cr-sample-v1.yaml
    kubectl wait --for=condition=Ready myresources/sample --timeout=60s
  4. 保存迁移前状态快照:
    kubectl get myresources sample -o yaml > snapshot/pre-migration.yaml
    etcdctl get /registry/example.io/myresources/default/sample --print-value-only | jq . > snapshot/pre-migration-etcd.json
环境搭建避坑: 如果使用Kind,务必在集群创建时挂载etcd数据目录到宿主机,否则集群删除后无法保留etcd数据用于回滚测试。另外,Kind默认的etcd版本可能不支持某些存储版本标记,建议使用 kindest/node:v1.27.3 及以上镜像。

3.4 测试数据准备

测试数据是版本迁移测试的“弹药”。数据准备需要覆盖CRD版本演进中可能出现的所有字段变化模式。建议按以下分类构造测试数据集:

3.4.1 数据分类与构造策略

数据类别 构造策略 预期覆盖的迁移场景
基础数据 每个字段都使用合法值,嵌套结构完整 正常迁移路径,验证字段映射正确性
边界数据 字段使用最小值、最大值、空字符串、null、空数组 验证默认值填充、空值处理、类型转换边界
异常数据 包含旧版已废弃字段、非法枚举值、类型不匹配的值 验证迁移转换器的错误处理与降级逻辑
状态数据 CR实例包含自定义status字段、conditions、observedGeneration 验证状态子资源的版本兼容性
关联数据 CR实例包含Finalizer、OwnerReference、跨namespace引用 验证垃圾回收和级联删除在迁移后是否正常

3.4.2 数据准备脚本示例

以下脚本使用yq批量生成不同类别的测试CR实例:

#!/bin/bash
# generate-test-data.sh
# 生成5类测试CR实例,每类3个,共15个实例

BASE_DIR="testdata/cr-samples"
mkdir -p $BASE_DIR/{basic,boundary,abnormal,status,reference}

# 1. 基础数据
for i in 1 2 3; do
  yq eval "
    .metadata.name = \"basic-$i\" |
    .spec.replicas = $((i * 2)) |
    .spec.image = \"nginx:1.2$i\" |
    .spec.config.timeout = $((30 + i * 10))
  " templates/cr-template.yaml > $BASE_DIR/basic/cr-basic-$i.yaml
done

# 2. 边界数据
yq eval '.spec.replicas = 0 | .spec.image = "" | .spec.config = {}' \
  templates/cr-template.yaml > $BASE_DIR/boundary/cr-boundary-empty.yaml
yq eval '.spec.replicas = 9999 | .spec.config.timeout = 0' \
  templates/cr-template.yaml > $BASE_DIR/boundary/cr-boundary-max.yaml

# 3. 异常数据
yq eval '.spec.image = "invalid:tag:extra" | .spec.config.timeout = "not-a-number"' \
  templates/cr-template.yaml > $BASE_DIR/abnormal/cr-abnormal-type.yaml

# 4. 状态数据
yq eval '.status.phase = "Running" | .status.conditions[0].type = "Ready" | .status.conditions[0].status = "True"' \
  templates/cr-template.yaml > $BASE_DIR/status/cr-status-ready.yaml

# 5. 关联数据
yq eval '.metadata.finalizers = ["example.io/cleanup"] | .metadata.ownerReferences[0].apiVersion = "v1" | .metadata.ownerReferences[0].kind = "Namespace" | .metadata.ownerReferences[0].name = "default" | .metadata.ownerReferences[0].uid = "12345678-1234-1234-1234-123456789abc"' \
  templates/cr-template.yaml > $BASE_DIR/reference/cr-with-finalizer.yaml

echo "测试数据生成完成,共15个CR实例"

3.4.3 数据验证清单

在迁移测试执行前,需要对测试数据做一次预验证,确保数据本身是有效的:

  • 每个CR实例能被旧版CRD正常创建(kubectl apply 无报错)
  • 每个CR实例能被旧版Operator正常Reconcile(状态字段被填充)
  • 每个CR实例的etcd存储版本标记与CRD的storage版本一致
  • 异常数据类CR实例在创建时可能被Webhook拒绝,需确认Webhook是否开启
数据准备自动化建议: 将测试数据生成脚本集成到CI流水线中,每次CRD版本变更时自动生成新的测试数据集。同时保留旧版数据集,用于回归测试。推荐使用 gotest + testdata 目录结构管理测试数据。
CRD版本迁移测试数据准备流程 基础数据 边界数据 异常数据 状态数据 关联数据 合法值+完整嵌套 字段映射验证 最小值/最大值/null 默认值/空值处理 废弃字段/非法值 错误处理降级 自定义status/conditions 状态子资源兼容 Finalizer/引用 GC/级联删除 批量生成CR实例(yq脚本 + 模板) 预验证:kubectl apply + Reconcile + etcd存储版本检查 测试数据集就绪(15+ CR实例)