4. API 定义与 CRD 生成:定义自定义资源(CR)结构体、使用 markers 标签、生成 CRD YAML

在 Operator 开发中,自定义资源(CR)是核心数据模型,它定义了用户期望的集群状态。本章将深入讲解如何使用 Go 结构体定义 CR、通过 markers 标签控制生成行为,并最终生成 CRD(CustomResourceDefinition)YAML 文件。

4.1 自定义资源结构体定义

Operator SDK 使用 Kubernetes 风格的 API 结构体来定义 CR。每个 CR 通常包含三个核心部分:TypeMeta、ObjectMeta 和 Spec/Status。

核心结构体组成:
  • TypeMeta:API 版本和资源类型(由 SDK 自动填充)
  • ObjectMeta:名称、命名空间、标签等元数据(Kubernetes 标准)
  • Spec:用户期望的“期望状态”(Desired State)
  • Status:集群当前的“实际状态”(Actual State),由 Operator 更新

以下是一个典型的 CR 结构体定义示例(位于 api/v1/ 目录下):

package v1

import (
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

// MyAppSpec 定义用户期望的应用配置
type MyAppSpec struct {
    // +kubebuilder:validation:Required
    // +kubebuilder:validation:Minimum=1
    // +kubebuilder:validation:Maximum=10
    Replicas int32 `json:"replicas"`

    // +kubebuilder:validation:Required
    Image string `json:"image"`

    // +kubebuilder:validation:Optional
    // +kubebuilder:default="default-config"
    ConfigName string `json:"configName,omitempty"`
}

// MyAppStatus 定义应用当前状态
type MyAppStatus struct {
    // +kubebuilder:validation:Optional
    AvailableReplicas int32 `json:"availableReplicas,omitempty"`

    // +kubebuilder:validation:Optional
    Conditions []metav1.Condition `json:"conditions,omitempty"`
}

// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:resource:path=myapps,scope=Namespaced
// +kubebuilder:printcolumn:name="Replicas",type="integer",JSONPath=".spec.replicas"
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp"

// MyApp 是 CR 的主结构体
type MyApp struct {
    metav1.TypeMeta   `json:",inline"`
    metav1.ObjectMeta `json:"metadata,omitempty"`

    Spec   MyAppSpec   `json:"spec,omitempty"`
    Status MyAppStatus `json:"status,omitempty"`
}

// +kubebuilder:object:root=true

// MyAppList 包含多个 MyApp 实例
type MyAppList struct {
    metav1.TypeMeta `json:",inline"`
    metav1.ListMeta `json:"metadata,omitempty"`
    Items           []MyApp `json:"items"`
}

4.2 Markers 标签详解

Markers 是 Operator SDK 中用于控制代码生成、CRD 验证、打印列等行为的注释标签。它们以 +kubebuilder: 或 +k8s: 开头,直接写在结构体或字段上方。

Marker 类别 常用标签 作用说明
对象标记 +kubebuilder:object:root=true 标记该结构体是 CR 的根对象,用于生成 DeepCopy 和客户端代码
子资源标记 +kubebuilder:subresource:status 启用 Status 子资源,允许通过 /status 端点更新状态
资源元数据 +kubebuilder:resource:path=myapps,scope=Namespaced 定义 CR 的 REST 路径和作用域(Namespaced/Cluster)
打印列 +kubebuilder:printcolumn:name="Replicas",type="integer",JSONPath=".spec.replicas" 在 kubectl get 输出中显示额外列
字段验证 +kubebuilder:validation:Required 标记字段为必填项
字段约束 +kubebuilder:validation:Minimum=1 设置数值字段的最小值/最大值/正则等约束
默认值 +kubebuilder:default="default-config" 为字段设置默认值(用户未指定时生效)
DeepCopy 生成 +k8s:deepcopy-gen:interfaces=... 控制 DeepCopy 方法的生成范围
最佳实践:建议为每个 Spec 字段都添加 +kubebuilder:validation:Required 或 +kubebuilder:validation:Optional 标记,避免生成 CRD 时出现歧义。

4.3 CRD YAML 生成流程

定义好结构体和 markers 后,通过以下命令生成 CRD YAML:

# 生成所有 CRD YAML 文件(默认输出到 config/crd/bases/)
make generate

# 仅生成 CRD 清单(不包含其他资源)
make manifests

# 查看生成的 CRD 文件
ls config/crd/bases/
# 输出示例:example.com_myapps.yaml

生成的 CRD YAML 文件结构如下:

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  annotations:
    controller-gen.kubebuilder.io/version: v0.14.0
  name: myapps.example.com
spec:
  group: example.com
  names:
    kind: MyApp
    listKind: MyAppList
    plural: myapps
    singular: myapp
  scope: Namespaced
  versions:
  - name: v1
    schema:
      openAPIV3Schema:
        description: MyApp 是 CR 的主结构体
        properties:
          spec:
            properties:
              replicas:
                format: int32
                minimum: 1
                maximum: 10
                type: integer
              image:
                type: string
              configName:
                default: default-config
                type: string
            required:
            - replicas
            - image
            type: object
          status:
            properties:
              availableReplicas:
                format: int32
                type: integer
              conditions:
                items:
                  type: object
                  x-kubernetes-preserve-unknown-fields: true
                type: array
            type: object
        type: object
    subresources:
      status: {}
    additionalPrinterColumns:
    - jsonPath: .spec.replicas
      name: Replicas
      type: integer
    - jsonPath: .metadata.creationTimestamp
      name: Age
      type: date
注意:如果修改了结构体或 markers,必须重新运行 make generate 和 make manifests,否则 CRD 不会更新。建议将这两个命令加入 CI/CD 流程。

4.4 CRD 生成流程示意图

下图展示了从 Go 结构体到 CRD YAML 的完整生成链路:

CRD 生成流程 CRD 生成流程:从 Go 结构体到 CRD YAML Go 结构体定义 api/v1/myapp_types.go Markers 标签 +kubebuilder:validation controller-gen make generate + manifests CRD YAML config/crd/bases/ kubectl apply 部署到集群 CR 实例 kubectl apply -f myapp.yaml 关键命令 make generate make manifests make install

4.5 验证 CRD 与测试 CR 实例

生成 CRD 后,可以通过以下步骤验证:

  1. 安装 CRD 到集群:make install 或 kubectl apply -f config/crd/bases/
  2. 查看已安装的 CRD:kubectl get crd | grep myapps
  3. 创建 CR 实例:编写 YAML 文件并 kubectl apply -f config/samples/
  4. 验证 CR 状态:kubectl get myapps -o yaml

示例 CR 实例 YAML(config/samples/example_v1_myapp.yaml):

apiVersion: example.com/v1
kind: MyApp
metadata:
  name: myapp-sample
spec:
  replicas: 3
  image: nginx:1.25
  configName: prod-config
调试技巧:如果 CRD 安装失败,使用 kubectl explain myapp 查看 CRD 的字段描述,确保与结构体定义一致。也可以使用 kubectl get crd myapps.example.com -o yaml 查看完整的 CRD 定义。

4.6 常见问题与避坑指南

问题 原因 解决方案
CRD 生成后字段缺失 结构体字段缺少 json 标签或 markers 检查每个字段是否都有 json:"fieldName" 标签
验证规则不生效 markers 拼写错误或位置不对 确认 markers 写在字段上方,且使用正确的 +kubebuilder:validation: 前缀
打印列不显示 printcolumn 标记未正确配置 检查 JSONPath 是否正确,确保字段在 Spec 或 Status 中存在
Status 子资源无法更新 缺少 +kubebuilder:subresource:status 标记 在 CR 主结构体上方添加该标记
DeepCopy 方法缺失 未运行 make generate 或标记错误 运行 make generate,检查 zz_generated.deepcopy.go 文件
重要:CRD 一旦被创建并用于生产环境,修改字段类型或删除字段可能导致现有 CR 实例无法解析。建议在开发阶段充分测试,或使用 CRD 版本升级机制(Conversion Webhook)进行平滑迁移。