LabHub
学习 学习路径 课程

CRD 与 Operator

定义 WebService 类型并注册到 API

在 LabHub 中继续学习

目标

apps.labhub.io/v1 组中定义名为 WebService 的新资源类型并注册到 API 服务器,完成一个具备 schema、自定义列、子资源和多版本支持的实用 CRD。

为什么重要

编写 CRD 并不只是“列出字段”,而是在决定由谁负责验证。在 schema 中写入 minimum: 1 后,API 服务器会在 apply 时强制执行该规则,并把错误消息直接返回给用户。若把规则放在控制器代码中,错误对象已经保存后,才会在日志中被发现。子资源也不是便利功能。把 status 分离到独立路径后,写入 status 不会增加 metadata.generation,控制器便能区分“用户修改了 spec”和“我刚刚写入了 status”。若无法区分,控制器可能因自己的 status 写入再次触发调谐,陷入无限循环。最后,最好从一开始就练习两个版本。亲自掌握存储版本必须恰好只有一个的规则,以及 status.storedVersions 的作用,可避免日后删除旧版本后导致数据无法读取。

步骤

  1. 创建 /root/crd/crd.yaml。必须设置 apiVersion: apiextensions.k8s.io/v1kind: CustomResourceDefinitionmetadata.name: webservices.apps.labhub.iospec.group: apps.labhub.io
  2. 在同一文件的 spec.names 中加入 plural: webservicessingular: webservicekind: WebServicelistKind: WebServiceListshortNames: [ws]categories: [labhub],并设置 spec.scope: Namespaced
  3. spec.versions 中添加 name: v1 并编写 schema.openAPIV3Schema。顶层为 type: objectproperties.spec.type: objectproperties.spec.required: [image];在 properties.spec.properties 下定义三个字段:image(type string)、replicas(type integer,minimum: 1maximum: 10default: 1)、tier(type string,enum: [dev, stage, prod]default: dev)。
  4. 使用 kubectl apply -f /root/crd/crd.yaml 应用,确认 Established 条件为 True,然后把 kubectl api-resources --api-group=apps.labhub.io 的输出保存到 /root/crd/out/api-resources.txt
  5. 为 v1 版本添加 additionalPrinterColumns,共四列:Image(type string,jsonPath .spec.image)、Replicas(type integer,jsonPath .spec.replicas)、Tier(type string,jsonPath .spec.tier)、Age(type date,jsonPath .metadata.creationTimestamp)。
  6. 在 v1 版本中启用 subresources.status: {}subresources.scale。scale 设置为 specReplicasPath: .spec.replicasstatusReplicasPath: .status.replicaslabelSelectorPath: .status.selector。同时在 schema 中把 properties.status 定义为 type: object,并在其下加入 replicas(integer)、selector(string)、observedGeneration(integer)、conditions(type array;items 为 type object,其中 typestatusreasonmessage 为 string,lastTransitionTime 为 string)。schema 中不存在的 status 字段会被剪除,无法保存。
  7. spec.versions 中添加 name: v1alpha1。v1alpha1 设置 served: truestorage: false,v1 设置 served: truestorage: true。v1alpha1 也必须有 schema,因此原样复制 v1 的 schema。重新应用后,确认 kubectl get crd webservices.apps.labhub.io -o jsonpath='{.status.storedVersions}' 中包含 v1
  8. 使用 kubectl create ns crd-lab 创建命名空间,应用 /opt/lab/fixtures/crd/sample-cr.yaml 创建 samplespec.image 必须包含标签)。随后把 kubectl get webservice -n crd-lab 的输出保存到 /root/crd/out/get-ws.txt,把 kubectl get --raw /apis/apps.labhub.io/v1/namespaces/crd-lab/webservices/sample/status 的输出保存到 /root/crd/out/status.json

参考

匹配 CRD 骨架与命名规则

创建 /root/crd/crd.yaml。必须设置 apiVersion: apiextensions.k8s.io/v1kind: CustomResourceDefinitionmetadata.name: webservices.apps.labhub.iospec.group: apps.labhub.io

CRD 是 apiextensions.k8s.io/v1 组中的对象。metadata.name 不能任意命名,必须采用复数名与组名用点连接的形式。应用 fixture 中损坏的 CRD,可以看到服务器使用什么消息拒绝它。

确定名称、作用域和短名称

在同一文件的 spec.names 中加入 plural: webservicessingular: webservicekind: WebServicelistKind: WebServiceListshortNames: [ws]categories: [labhub],并设置 spec.scope: Namespaced

spec.names 中需要分别填写复数名、单数名、kind 和 listKind。kind 使用 PascalCase,listKind 则在 kind 后加 List。shortNamescategories 都是数组。

使用 OpenAPI v3 schema 固定字段契约

spec.versions 中添加 name: v1 并编写 schema.openAPIV3Schema。顶层为 type: objectproperties.spec.type: objectproperties.spec.required: [image];在 properties.spec.properties 下定义三个字段:image(type string)、replicas(type integer,minimum: 1maximum: 10default: 1)、tier(type string,enum: [dev, stage, prod]default: dev)。

required 以数组形式放在 spec 对象中。数值字段可以使用 minimummaximumdefault,字符串字段可以使用 enum。请注意,默认值本身也必须能够通过验证。

应用并确认已出现在 API 资源列表中

使用 kubectl apply -f /root/crd/crd.yaml 应用,确认 Established 条件为 True,然后把 kubectl api-resources --api-group=apps.labhub.io 的输出保存到 /root/crd/out/api-resources.txt

应用后并不一定立即可用。CRD 的某个 status 条件变为 True 后,API 服务器才会接收该类型。请通过 API 资源列表确认新类型确实完成注册。

添加 kubectl get 显示的列

为 v1 版本添加 additionalPrinterColumns,共四列:Image(type string,jsonPath .spec.image)、Replicas(type integer,jsonPath .spec.replicas)、Tier(type string,jsonPath .spec.tier)、Age(type date,jsonPath .metadata.creationTimestamp)。

每个版本分别配置 additionalPrinterColumns。每列都需要 name、type、jsonPath 三项;时间列的 type 必须是 date,才能显示为相对时间。

启用 status 和 scale 子资源

在 v1 版本中启用 subresources.status: {}subresources.scale。scale 设置为 specReplicasPath: .spec.replicasstatusReplicasPath: .status.replicaslabelSelectorPath: .status.selector。同时在 schema 中把 properties.status 定义为 type: object,并在其下加入 replicas(integer)、selector(string)、observedGeneration(integer)、conditions(type array;items 为 type object,其中 typestatusreasonmessage 为 string,lastTransitionTime 为 string)。schema 中不存在的 status 字段会被剪除,无法保存。

仅启用子资源还不够。如果 schema 中未定义 status 字段,即使写入也会因 pruning 而无法保存。scale 需要三个路径,其中一个供 HPA 统计 Pod。

提供两个版本并只保留一个存储版本

spec.versions 中添加 name: v1alpha1。v1alpha1 设置 served: truestorage: false,v1 设置 served: truestorage: true。v1alpha1 也必须有 schema,因此原样复制 v1 的 schema。重新应用后,确认 kubectl get crd webservices.apps.labhub.io -o jsonpath='{.status.storedVersions}' 中包含 v1

在 apiextensions/v1 中,每个版本都必须拥有自己的 schema。served 和 storage 含义不同,而且 storage 为 true 的版本必须恰好只有一个。应用后检查 CRD 的 status 是否记录了存储版本。

创建并查询第一个自定义资源

使用 kubectl create ns crd-lab 创建命名空间,应用 /opt/lab/fixtures/crd/sample-cr.yaml 创建 samplespec.image 必须包含标签)。随后把 kubectl get webservice -n crd-lab 的输出保存到 /root/crd/out/get-ws.txt,把 kubectl get --raw /apis/apps.labhub.io/v1/namespaces/crd-lab/webservices/sample/status 的输出保存到 /root/crd/out/status.json

必须先创建命名空间。保存普通查询的输出,以确认自定义列是否真正显示;status 则不要通过普通查询读取,而应单独访问子资源路径。可以直接请求 raw API 路径。