定义 WebService 类型并注册到 API
目标
在 apps.labhub.io/v1 组中定义名为 WebService 的新资源类型并注册到 API 服务器,完成一个具备 schema、自定义列、子资源和多版本支持的实用 CRD。
为什么重要
编写 CRD 并不只是“列出字段”,而是在决定由谁负责验证。在 schema 中写入 minimum: 1 后,API 服务器会在 apply 时强制执行该规则,并把错误消息直接返回给用户。若把规则放在控制器代码中,错误对象已经保存后,才会在日志中被发现。子资源也不是便利功能。把 status 分离到独立路径后,写入 status 不会增加 metadata.generation,控制器便能区分“用户修改了 spec”和“我刚刚写入了 status”。若无法区分,控制器可能因自己的 status 写入再次触发调谐,陷入无限循环。最后,最好从一开始就练习两个版本。亲自掌握存储版本必须恰好只有一个的规则,以及 status.storedVersions 的作用,可避免日后删除旧版本后导致数据无法读取。
步骤
- 创建
/root/crd/crd.yaml。必须设置apiVersion: apiextensions.k8s.io/v1、kind: CustomResourceDefinition、metadata.name: webservices.apps.labhub.io、spec.group: apps.labhub.io。 - 在同一文件的
spec.names中加入plural: webservices、singular: webservice、kind: WebService、listKind: WebServiceList、shortNames: [ws]、categories: [labhub],并设置spec.scope: Namespaced。 - 在
spec.versions中添加name: v1并编写schema.openAPIV3Schema。顶层为type: object,properties.spec.type: object,properties.spec.required: [image];在properties.spec.properties下定义三个字段:image(type string)、replicas(type integer,minimum: 1、maximum: 10、default: 1)、tier(type string,enum: [dev, stage, prod]、default: dev)。 - 使用
kubectl apply -f /root/crd/crd.yaml应用,确认Established条件为True,然后把kubectl api-resources --api-group=apps.labhub.io的输出保存到/root/crd/out/api-resources.txt。 - 为 v1 版本添加
additionalPrinterColumns,共四列:Image(type string,jsonPath.spec.image)、Replicas(type integer,jsonPath.spec.replicas)、Tier(type string,jsonPath.spec.tier)、Age(typedate,jsonPath.metadata.creationTimestamp)。 - 在 v1 版本中启用
subresources.status: {}和subresources.scale。scale 设置为specReplicasPath: .spec.replicas、statusReplicasPath: .status.replicas、labelSelectorPath: .status.selector。同时在 schema 中把properties.status定义为type: object,并在其下加入replicas(integer)、selector(string)、observedGeneration(integer)、conditions(type array;items 为 type object,其中type、status、reason、message为 string,lastTransitionTime为 string)。schema 中不存在的 status 字段会被剪除,无法保存。 - 在
spec.versions中添加name: v1alpha1。v1alpha1 设置served: true、storage: false,v1 设置served: true、storage: true。v1alpha1 也必须有 schema,因此原样复制 v1 的 schema。重新应用后,确认kubectl get crd webservices.apps.labhub.io -o jsonpath='{.status.storedVersions}'中包含v1。 - 使用
kubectl create ns crd-lab创建命名空间,应用/opt/lab/fixtures/crd/sample-cr.yaml创建sample(spec.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。
参考
- 可直接应用
/opt/lab/fixtures/crd/broken-crd.yaml,查看 API 服务器针对命名规则和必填字段给出的拒绝消息。该文件不是修复目标,只用于观察错误。 - 使用
kubectl explain webservice.spec,检查刚注册的 schema 是否像文档一样显示。 - 常见错误 1:把
metadata.name只写成webservices。CRD 名称必须是<복수형>.<그룹>。 - 常见错误 2:第 6 步只启用子资源,却遗漏 schema 的
properties.status。即使 patch status,所有内容也会因 pruning 被剪除。 - 常见错误 3:第 7 步把两个版本都设为
storage: true。存储版本必须恰好只有一个,否则 CRD 本身就会被拒绝应用。
匹配 CRD 骨架与命名规则
创建 /root/crd/crd.yaml。必须设置 apiVersion: apiextensions.k8s.io/v1、kind: CustomResourceDefinition、metadata.name: webservices.apps.labhub.io、spec.group: apps.labhub.io。
CRD 是 apiextensions.k8s.io/v1 组中的对象。metadata.name 不能任意命名,必须采用复数名与组名用点连接的形式。应用 fixture 中损坏的 CRD,可以看到服务器使用什么消息拒绝它。
确定名称、作用域和短名称
在同一文件的 spec.names 中加入 plural: webservices、singular: webservice、kind: WebService、listKind: WebServiceList、shortNames: [ws]、categories: [labhub],并设置 spec.scope: Namespaced。
spec.names 中需要分别填写复数名、单数名、kind 和 listKind。kind 使用 PascalCase,listKind 则在 kind 后加 List。shortNames 和 categories 都是数组。
使用 OpenAPI v3 schema 固定字段契约
在 spec.versions 中添加 name: v1 并编写 schema.openAPIV3Schema。顶层为 type: object,properties.spec.type: object,properties.spec.required: [image];在 properties.spec.properties 下定义三个字段:image(type string)、replicas(type integer,minimum: 1、maximum: 10、default: 1)、tier(type string,enum: [dev, stage, prod]、default: dev)。
required 以数组形式放在 spec 对象中。数值字段可以使用 minimum、maximum、default,字符串字段可以使用 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.replicas、statusReplicasPath: .status.replicas、labelSelectorPath: .status.selector。同时在 schema 中把 properties.status 定义为 type: object,并在其下加入 replicas(integer)、selector(string)、observedGeneration(integer)、conditions(type array;items 为 type object,其中 type、status、reason、message 为 string,lastTransitionTime 为 string)。schema 中不存在的 status 字段会被剪除,无法保存。
仅启用子资源还不够。如果 schema 中未定义 status 字段,即使写入也会因 pruning 而无法保存。scale 需要三个路径,其中一个供 HPA 统计 Pod。
提供两个版本并只保留一个存储版本
在 spec.versions 中添加 name: v1alpha1。v1alpha1 设置 served: true、storage: false,v1 设置 served: true、storage: 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 创建 sample(spec.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 路径。