Kubernetes 的核心能力建立在资源对象模型之上。用户通过 YAML 或 JSON 描述期望状态,API Server 将其持久化到 etcd,控制器持续调谐使实际状态向期望状态靠拢。内置对象资源(如 Pod、Service、Deployment)由 Kubernetes 核心组件直接识别和处理;自定义资源(Custom Resource,CR)则允许用户扩展 API。两者本质区别不在于是否存储在 etcd,而在于是否有系统级控制器自动响应变更。没有控制器监听的自定义资源,只是一份静态配置数据。我们先看几个核心概念。

资源对象的存储与 API 注册机制
内置资源对象在 Kubernetes 源码编译阶段就已经注册到 API Server 中。像 Pod、Node、Service 这些核心类型,它们的结构体定义、默认值逻辑、校验规则以及版本转换函数都直接写死在 kube-apiserver 的代码里。API Server 启动时会自动加载这些内置类型,并在 /api/v1、/apis/apps/v1 等路径下暴露对应的 RESTful 接口。内置资源的生命周期完全由 Kubernetes 核心团队维护,升级集群版本时,这些对象的 schema 变化会经过严格的兼容性评估。
自定义资源则完全不同。用户不需要修改任何 Kubernetes 源码,只需要向集群提交一个 CustomResourceDefinition(CRD)对象。API Server 监听到这个 CRD 之后,会动态注册一个全新的 API 组和版本,并在 etcd 中为新资源建立独立的存储映射。CRD 中定义的 OpenAPI schema 用于校验提交进来的自定义资源实例,但 API Server 不会干预这些资源实例的具体业务逻辑。换句话说,CRD 只是让集群“认识”了一种新的数据类型,它自己并不会“理解”这些数据应该怎么处理。
下面是一个典型的 CRD 定义示例,它向集群注册了一个 ipipp.com 组下的 App 资源:
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: apps.ipipp.com
spec:
group: ipipp.com
versions:
- name: v1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
replicas:
type: integer
image:
type: string
scope: Namespaced
names:
plural: apps
singular: app
kind: App
shortNames:
- ap
这条 CRD 提交后,API Server 会立即创建 /apis/ipipp.com/v1/namespaces/{namespace}/apps 这样的访问路径。你可以用 kubectl get apps 查看资源列表,但前提是必须有对应的控制器去创建实际的资源实例,否则这个列表永远是空的。这也说明了存储机制与业务逻辑分离的关键特征。
控制器协同与调谐循环的差异
内置对象之所以能够自动完成扩缩容、自愈、服务发现等功能,是因为 kube-controller-manager 中运行着一批系统级控制器。每个控制器都针对特定的内置对象类型编写,例如 DeploymentController 监听 Deployment 对象的变更,然后创建或更新 ReplicaSet;ReplicaSetController 再进一步保证 Pod 副本数量达标。这套调谐循环是 Kubernetes 声明式 API 的核心引擎,用户不需要关心控制器如何工作,只需要声明期望状态即可。
自定义资源本身不会触发任何系统级控制器行为。如果你提交了一个 kind: App 的实例,并设置了 replicas: 3,API Server 只会把这个对象存储起来,不会去创建 3 个 Pod,也不会做任何健康检查或滚动更新。要让自定义资源真正“活”起来,必须自己编写一个自定义控制器。这个控制器通常使用 client-go 的 informer 机制监听自定义资源的增删改事件,然后在自己的调谐循环里执行业务逻辑,比如创建 Deployment、调用外部 API 或者更新状态字段。
以下是一个简化的 Go 控制器调谐逻辑片段,演示如何监听并处理自定义资源:
package main
import (
"context"
"fmt"
"k8s.io/client-go/kubernetes"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func reconcileApp(ctx context.Context, client kubernetes.Interface, namespace string) error {
// 获取命名空间下所有自定义资源实例
apps, err := client.AppsV1().Apps(namespace).List(ctx, metav1.ListOptions{})
if err != nil {
return err
}
for _, app := range apps.Items {
if app.Spec.Replicas > 0 {
fmt.Printf("需要为 %s 创建 %d 个副本\n", app.Name, app.Spec.Replicas)
// 这里可以调用 client.AppsV1().Deployments(...) 创建 Deployment
}
}
return nil
}
这种设计与内置控制器形成了明显对比:内置对象的控制器复杂度对用户完全透明,而自定义控制器可以完全按照业务需求定制,但同时也意味着用户要自己处理并发、重试、状态更新、leader election 等问题。好在 Kubernetes 生态提供了 Kubebuilder、Operator SDK 等工具,可以大幅降低编写自定义控制器的门槛。
实际场景中的选择权衡
判断一个需求应该使用内置对象还是自定义资源,核心看两点:是否需要系统级控制器自动处理,以及是否需要独立的 API 生命周期管理。如果需求完全可以通过 Deployment、StatefulSet、Service 等原生对象组合实现,那就没有必要引入 CRD。例如运行一个无状态 Web 应用,Deployment 就能完成副本管理、滚动更新和回滚,强行编写 CRD 反而增加了维护负担。
但是当你的应用需要描述 Kubernetes 原生对象无法表达的状态时,自定义资源就变得非常合适。比如在数据库 Operator 中,你需要一个资源来描述数据库集群的拓扑、备份策略、故障切换行为,这些信息无法直接塞进 ConfigMap 或 Annotation 里。通过 CRD 定义一个 MySQLCluster 资源,可以让用户以声明式方式管理复杂的数据库生命周期,同时利用 Kubernetes 的 RBAC、审计和命名空间隔离能力。
还有一个常见误区是滥用 ConfigMap 或 Annotation 来存储结构化配置。ConfigMap 没有强类型校验,字段写错只能等到应用运行时才暴露问题;Annotation 虽然灵活但缺少版本管理和 API 文档支持。自定义资源通过 OpenAPI schema 提供字段级校验,通过版本转换支持平滑升级,这是普通配置存储无法替代的。因此,当你发现团队内部开始用大量 Annotation 来传递复杂业务语义时,通常就是考虑引入 CRD 的信号。
总的来说,内置对象资源解决的是 Kubernetes 平台级的通用问题,而自定义资源解决的是你的业务领域特有的扩展问题。两者在 Kubernetes 生态中各司其职,理解它们之间的边界,可以帮助你设计出更清晰、更可维护的云原生架构。
Kubernetes对象资源自定义资源CRD修改时间:2026-10-05 06:13:07