dbaplus社群

颠覆认知!用了Kubernetes这么多年才最终悟透CRD

ArgoCD、KEDA、Crossplane 这些工具并非什么黑魔法,它们的核心都是CRD。一旦你理解了这一点,看待Kubernetes的视角将彻底被改变。

Image

一、你已经用了很多年,却从未真正了解

大多数Kubernetes工程师每天都在使用CRD,但真正理解其原理与设计的人寥寥无几。

打开你的终端,运行以下命令:

kubectl 获取 crds

现在暂停一下。

向下滚动。

观察显示了多少行内容。

数百行?

没错,这里有一个扎心的事实:

你用Kubernetes很多年了,却根本不了解它是如何实现自身拓展的。

这些:

  • Argo CD的Application

  • KEDA的ScaledObject

  • Crossplane资源

  • cert-manager的Certificate

它们本质上都是同一个东西:自定义资源定义(CRD)

二、让你真正 “悟透” K8s的那个瞬间

大多数人以为Kubernetes只是:

  • Pod

  • 部署

  • 服务

然而,这只是表面现象。

底层的真相是:

Kubernetes不是一个平台,而是一种语言。

那CRD是什么?

CRD让你给这门语言添加新词汇。

好好想想:

你不是在“扩展”Kubernetes,你是在教它新的名词。例如:

  • DatabaseCluster

  • MLModel

  • TenantConfig

一旦定义完成,它们就变成:

  • 一级API对象

  • 可通过kubectl查询

  • 存储在etcd中

  • 采用RBAC进行安全保护

  • 像原生资源一样被监控

你的构想,就此成为Kubernetes本身的一部分。

为什么这一点能颠覆一切?

大多数教程都忽略了这一点:

CRD不只是一项功能,它是整个云原生生态的基石。

你欣赏的每一款成熟的Kubernetes工具,都构建在它之上。

不是插件,不是黑科技。

仅仅是......一些新的名词而已。

三、从零开始创建一个CRD

我们来创建一个真实的例子:

DatabaseCluster,用于管理PostgreSQL或云数据库的抽象概念。

只需要关注以下3个核心点:

  • group

  • name

  • version

apiVersion:  apiextensions.k8s.io/v1 kind:  CustomResourceDefinition metadata:   name:  databaseclusters.infra.example.com   # 必须为:<复数>.<组> spec:   group:  infra.example.com         # 您的 API 组(例如 apps、batch、networking.k8s.io)  scope:  Namespaced                # 或者:集群  名称:    复数:  databaseclusters       # kubectl get databaseclusters    单数:  databasecluster      # kubectl get databasecluster my-db     kind:  DatabaseCluster          # 用于 YAML 清单    shortNames:       -  dbc                        # kubectl get dbc   versions:     -  name:  v1alpha1       serving:  true                 # 此版本处于活动状态并接受请求      storage:  true                # 此版本用于 etcd 中的持久化      schema:         openAPIV3Schema:           type:  object           properties:             spec:               type:  object               properties: {}       # 我们将在下一步中填写            status:               type:  object               properties: {}

分析一下其中的关键决策:

这一行代码:

名称: databaseclusters.infra.example.com

这不仅仅是一个名字。

这是一个API端点。

Kubernetes刚刚创建了:

/apis/infra.example.com/v1alpha1/databaseclusters

无需重启,无需安装插件,无需重建服务器。

你刚刚扩展了Kubernetes API。

范围:命名空间级(Namespaced) VS 集群级 (Cluster)

  • 命名空间:资源位于命名空间内,绝大多数CRD属于此类。

  • 集群:全局资源,用于集群级概念,例如 cert-manager 的 ClusterIssuer或基于角色的访问控制 (RBAC)中的ClusterRole。

服务(served)VS存储 (storage)

  • served: true:该版本对外提供API服务。

  • storage: true:该版本数据持久化存储到etcd,有且仅有一个版本可设为storage: true。

这种分离是实现安全多版本CRD的关键:可以同时提供v1 和 v1alpha1版本的API服务,但只存一份v1到etcd中。

注意:不做版本控制直接改schema会破坏现有资源。

四、CRD本身不执行任何操作

很多人在这里搞混。

必须明确:CRD只定义意图,不执行任何动作。

它不会:

  • 创建Pod

  • 供应数据库

  • 扩缩工作负载

一句话总结:

  • CRD = 词汇

  • Operator = 动作

如果没有控制器,执行以下命令:

kubectl get dbc

只会查到一堆静静躺在那儿的对象。

就这么一直等着,什么也不会发生。

五、构建生产级 DatabaseCluster CRD

让我们使其达到生产级别。

定义:

  • 必填字段

  • 校验规则

  • 枚举

  • 默认值

# database-cluster-crd.yaml apiVersion:  apiextensions.k8s.io/v1 kind:  CustomResourceDefinition metadata:   name:  databaseclusters.infra.example.com   annotations:     controller-gen.kubebuilder.io/version:  v0.14.0 spec:   group:  infra.example.com   scope:  Namespaced   names:     plural:  databaseclusters     singular:  databasecluster     kind:  DatabaseCluster     shortNames:       -  dbc   versions:     -  name:  v1alpha1       serve:  true       storage:  true       # 启用状态子资源(对运维人员至关重要)      subresources:         status: {}       # kubectl get dbc 输出中显示的自定义列      additionalPrinterColumns:         -  name:  Replicas           type:  integer           jsonPath:  .spec.replicas         -  name:  Region           type:  string           jsonPath:  .spec.region         -  name:  Phase           type:  string           jsonPath:  .status.phase         -  name:  Age           type:  date           jsonPath:  .metadata.creationTimestamp       schema:         openAPIV3Schema:           type:  object           properties:             spec:               type:  object               required: [ "engine" , "replicas" , "region" ]               properties:                 engine:                   type:  string                   enum: [ "postgres" , "mysql" , "mariadb" ]                   description:  "要使用的数据库引擎"                 replicas:                   type:  integer                   minimum:  1                   maximum:  9                   description:  "数据库副本数"                 region:                   type:  string                   description:  "AWS 区域或数据中心位置"                 storageGB:                   type:  integer                   minimum:  10                   default:  20                   description:  "存储大小(GB)"                 version:                   type:  string                   description:  "数据库引擎版本(例如,PostgreSQL 的 '16.2')"             status:               type:  object               properties:                 phase:                   type: 字符串                  描述: “当前生命周期阶段:待处理、配置中、就绪、失败”                端点:                  类型: 字符串                  描述: “集群就绪后的连接端点”                条件:                  类型: 数组                  项:                    类型: 对象                    属性:                      类型:                        字符串 状态                      :                        类型: 字符串                      lastTransitionTime:                        类型: 字符串                        格式: 日期时间                      原因:                        类型: 字符串                      消息:                        类型: 字符串

六、为什么Schema至关重要

  • 没有schema:Kubernetes几乎可以接受任何格式。

  • 有schema:Kubernetes变成第一道防线。

无效的配置根本触及不了Operator。

将其应用到集群:

kubectl apply -f database-cluster-crd.yaml 
# 验证是否已注册kubectl get crd databaseclusters.infra.example.com 
# 创建于# databaseclusters.infra.example.com 2026-03-31T16:00:00Z

应用此配置之后,Kubernetes会立即注册新API端点,无需重启。

七、创建第一个自定义资源

现在CRD已经存在,你可以创建它的实例,就像创建任何其他Kubernetes对象一样:

# my-postgres.yamlapiVersion: infra.example.com/v1alpha1kind: DatabaseClustermetadata:  name: production-postgres  namespace: databasesspec:  engine: postgres  replicas: 3  region: ap-south-1  version: "16.2"

应用与操作:

kubectl apply -f my -postgres.yaml 
# 像操作任何原生资源一样操作它kubectl get dbc -n databases 
# NAME REPLICAS REGION PHASE AGE # production-postgres 3 ap-south-1 <none> 10s
 kubectl describe dbc production-postgres -n databases kubectl delete dbc production-postgres -n databases

你不再只是简单使用Kubernetes,而是在塑造它的API体系。

此时Phase列为 <none>,因为没有控制器更新状态。

注意:仅有CRD而没有控制器,是没有任何作用的。

八、CRD如何融入Kubernetes架构

Image

此图展示了CRD在Kubernetes架构中的完整生命周期:

  • 应用DatabaseCluster YAML

  • API server 按 OpenAPI schema 校验

  • 存入etcd

  • 一个controller监视它

  • controller让实际状态匹配你的期望状态

Kubernetes 并非在执行 YAML,它是在调和你的意图。

一旦你理解了这一点……

其它一切:operators、controllers、自动化等等,都变得显而易见了。

九、为什么 status 子资源如此重要

这一点能区分新手与平台工程师。

添加:

subresources:  status: {}

现在,神奇的事情发生了。

  • 如果没有它:任何人都能覆盖系统状态

  • 有了它:只有Operator能修改实际状态

想想看:

  • spec:用户定义期望状态

  • status:Operator上报真实状态

并且Kubernetes在API层强制执行这种分离。

这不是惯例,而是架构层面的强制性规定。

在你的Python operator中,将像这样更新status :

只有操作员才能写入 /status — 用户无法覆盖此设置。patch.status[ 'phase' ] = 'Ready' patch.status[ 'endpoint' ] = f"postgres. {name} .svc.cluster.local:5432"

status包含两种类型的信息:

1)简单字段(实际是什么?)

这些字段回答:“当前状态是什么?”

2)条件数组(健康状况如何?)

这些条件回答:“一切正常吗?当前情况如何?”

一个condition就像指示每个方面的交通信号灯。

十、通过Python与CRD交互

先来了解如何使用官方kubernetes Python client与自定义资源进行交互。这一方法适用于脚本编写、CI流水线以及数据迁移任务。

# pip install kubernetes from kubernetes import client, config 
 # 加载 kubeconfig(如果运行在 pod 中,则加载集群内配置) config.load_kube_config() 
 # 使用动态客户端访问自定义资源dyn_client = client.ApiClient() custom_api = client.CustomObjectsApi(dyn_client) 
GROUP = "infra.example.com" VERSION = "v1alpha1" PLURAL = "databaseclusters" NAMESPACE = "databases" 
 # 列出所有 DatabaseCluster 资源clusters = custom_api.list_namespaced_custom_object(     group=GROUP,     version=VERSION,     namespace=NAMESPACE,     plural=PLURAL ) 
for cluster in clusters[ "items" ]:     name = cluster[ "metadata" ][ "name" ]     phase = cluster.get( "status" , {}).get( "phase" , "Unknown" )     replicas = cluster[ "spec" ][ "replicas" ]     print(f "{name}: phase={phase}, replicas={replicas}" ) # 以编程方式创建一个新的数据库集群new_cluster = {     "apiVersion" : f "{GROUP}/{VERSION}" ,     "kind" : "DatabaseCluster" ,     "metadata" : { "name" : "staging-mysql" , "namespace" : NAMESPACE},     "spec" : {         "engine" : "mysql" ,         "replicas" : 1,         "region" : "ap-south-1" ,         "storageGB" : 20,         "version" : "8.0"     } } 
custom_api.create_namespaced_custom_object(     group=GROUP,     version=VERSION,     namespace=NAMESPACE,     plural=PLURAL,     body=new_cluster ) print( "已创建 staging-mysql 数据库集群" ) 
#修补状态(通常仅由操作员执行) status_patch = { "status" : { "phase" : "Provisioning" }} custom_api.patch_namespaced_custom_object_status(     group=GROUP,     version=VERSION,    namespace=NAMESPACE,     plural=PLURAL,     name= "staging-mysql" ,     body=status_patch )

这是原始的API交互层。

十一、隐藏的仪表盘技巧

添加:

additionalPrinterColumns:

现在运行:

kubectl get dbc

输出示例:

名称 副本数 区域 阶段 时间production-postgres 3 ap-south-1 就绪 2天staging-mysql 1 ap-south-1 配置中 5个月

你刚刚把kubectl变成了一个仪表盘。

无需UI和额外工具,考的仅仅只是巧妙的API设计。

十二、导致系统崩溃的常见错误

在构建自己的CRD之前,请避免以下情况:

  • 没有 schema → 会接收无效或错误的输入数据

  • 没有 status 子资源 → 用户可覆盖系统状态

  • 破坏性修schema 变更 → 破坏现有资源

  • 忘记写 Operator → 不会执行任何实际操作

大多数CRD问题不是Kubernetes本身的问题,而是API设计问题。

十三、你真正学到了什么

让我们把视角拉远一些。

这篇文章讲的不仅仅是YAML,而是了解Kubernetes的演进方式。

你学到了:

  • 工具的实际构建方式

  • API的扩展方式

  • 平台的设计方式

更重要的是:

你不再只是Kubernetes用户,而是开始像平台工程师一样思考。

十四、接下来会讲什么

目前,你的CRD仍然存在一个缺陷。

任何人都可以这样操作:

副本数:0

而Kubernetes会照单全收。

在API服务器内部运行的验证规则:

不需要webhooks,不需要额外的服务。

仅仅只是靠API层面的强制约束。

十五、最后,还有一个问题

假设你要创建一个系统,让用户可以通过声明式的方式定义数据库备份。他们不用写脚本,而是编写YAML文件即可。

用户想要输入的内容如下:

apiVersion:  backup.example.com/v1  kind:  DatabaseBackup  metadata:  name:  my-postgres-daily  spec:   database:  "postgres-prod"   schedule:  "0 2 * * *"  # 每天凌晨 2 点 执行 retention:  7  # 保留 7 个备份

请思考:

1、这里的期望状态(DESIRED state)是什么?(用户表达了哪些需求?)
  • 我希望备份按指定的计划执行

  • 我希望保留指定数量的备份

还可以补充什么?

2、实际状态(ACTUAL state)是什么?(Operator会在集群中检查哪些信息?)
  • 当前实际存在多少个备份?

  • 上一次备份是什么时候?

  • 上一次备份是成功还是失败?

  • 还有哪些信息值得跟踪?

3、还可以增加哪些字段?(用户可能还想指定什么?)
  • 备份存储位置(S3、GCS、本地存储)?

  • 备份类型(全量、增量)?

  • 成功或者失败通知?

  • 其它内容?

作者丨Ramesh      编译丨dbaplus社群
来源丨网址:https://medium.com/@rameshavutu/kubernetes-crds-08bb705ed406
dbaplus社群欢迎广大技术人员投稿,投稿邮箱:[email protected]
--

更多运维领域不容错过的热点探讨:

  • OpenClaw浪潮下的智能体应用可观测体系构建

  • 从AIOps到Agentic AIOps的战略转型

  • 多Agent协作及统一管理实践

  • 金融级全栈信创化与云原生实践路径

  • ……

都能在XCOPS智能运维管理人年会-广州站上看到生产级实战案例、找到可参考可落地的方式方法。扫描下方二维码可了解大会详情及报名↓

Image
Image