被拒过一次,才懂那份契约
一句话总结
CRD 的价值在于借用 API 服务器的验证、默认值、RBAC 与审计能力。在什么都接受的集群中,这些能力一项也无法验证。
为什么必须经历拒绝
上一模块使用 CRD 构建了平台 API。但实验环境不具备 API 服务器能力,因此无论填入什么内容都会接受。
CRD 的作用是“把 API 服务器已有的能力借给自定义类型”,包括:
검증 스키마에 안 맞으면 거절한다
기본값 빠진 값을 저장 시점에 채운다
RBAC 다른 자원과 똑같이 권한을 건다
감사 누가 언제 무엇을 바꿨는지 남는다
watch 컨트롤러가 변화를 구독한다
문서 kubectl explain 이 스키마를 읽어 준다
自行构建 API 时,必须重新实现全部六项能力。但在只会接受请求的集群中,我们无法验证其中任何一项。
被悄然裁剪的字段
结构化模式默认会裁剪字段。不在 properties 中的字段会自动删除,并不是因为额外开启了阻止设置。把 replicas 误写为 replica 时,该值会消失,却没人提醒。
这里也常有人误用 additionalProperties: false。它不能与 properties 同时使用(Forbidden: mutual exclusive),因为没有使用必要,所以 API 直接禁止这种组合。
反过来,添加 x-kubernetes-preserve-unknown-fields: true 会同时关闭裁剪与验证。为了方便而加入它的一刻,所有边界都会消失。
由谁填充默认值会产生不同结果
스키마의 default 저장 시점에 채워져 kubectl get -o yaml 에 바로 보인다
컨트롤러가 채움 한참 뒤에 나타난다. 그 사이에는 비어 있다
开发者检查对象时就应看到值,才能知道会发生什么。黄金路径正是在这里形成。
修改模式时会破坏什么
CRD 部署后,集群中已经存在保存的对象。修改模式等于重新解释已保存数据,因此必须遵守规则。
| 变更 | 是否安全 | 原因 |
|---|---|---|
| 添加可选字段 | ✅ | 旧对象只是该字段为空 |
| 添加必填字段 | ❌ | 旧对象会验证失败,甚至无法修改 |
| 删除字段 | ⚠️ | 值会被裁剪,无法恢复 |
| 更改类型(string→int) | ❌ | 无法读取已保存值 |
| 增加 enum 值 | ✅ | |
| 删除 enum 值 | ❌ | 使用该值的对象会失效 |
如果必须增加必填字段,应创建新版本(v1alpha1 → v1beta1)。只能有一个版本设置 storage: true,其他版本通过转换(conversion)提供。没有转换 Webhook 时采用 None 策略,字段会原样通过;如果结构不同,就必须部署 Webhook。
模式能验证到什么程度
OpenAPI 模式能做和不能做的事情界限明确。
properties:
replicas:
type: integer
minimum: 1
maximum: 100
default: 3
tier:
type: string
enum: [bronze, silver, gold]
name:
type: string
pattern: '^[a-z][a-z0-9-]{2,30}$'
这些可以由模式完成,但字段之间的关系不行,例如“tier 为 gold 时 replicas 至少为 5”。过去需要 Webhook,现在可以使用 CEL 验证规则在 CRD 内表达。
x-kubernetes-validations:
- rule: "self.tier != 'gold' || self.replicas >= 5"
message: "gold 등급은 복제본이 5개 이상이어야 합니다"
- rule: "self.name == oldSelf.name" # 불변 필드
message: "name 은 만든 뒤에 바꿀 수 없습니다"
它明显优于 Webhook:无需独立部署,不会因 Webhook 宕机阻塞 API,还能让错误消息与模式放在一起。只有 CEL 无法表达时才使用 Webhook。
状态放在哪里
status 应与 spec 分离,放入子资源。
subresources:
status: {}
scale:
specReplicasPath: .spec.replicas
statusReplicasPath: .status.replicas
这样用户无法修改 status,控制器也无法修改 spec,权限自然分离。加入 scale 子资源后,kubectl scale 与 HPA 可以直接工作,这正是为自定义资源接入 HPA 的方法。
实践中真正重要的事项
**拼写错误不会返回错误,而会悄然消失。**结构化模式默认裁剪字段,把 replicas 写成 replica 时,值会静默删除。发布平台 API 时,最好在说明中要求用户通过 kubectl get -o yaml 重新读取保存结果。
**x-kubernetes-preserve-unknown-fields: true 是最后手段。**一旦为了方便加入它,裁剪和验证都会关闭,为该类型建立的全部边界随之消失。
**默认值应放在模式中,而不是控制器中。**模式的 default 会在保存时填充,开发者能立即看到;控制器填充则会留下一段空白时间。黄金路径就建立在这种差异之上。
下一项实验会在真实 API 服务器上,通过亲自遭到拒绝来验证这些内容。