Skip to main content

6. GVK 与 GVR

一个 YAML 的旅行​

你写了一个 Deployment YAML:

apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx

kubectl 收到这个 YAML,干的第一件事是:搞清楚这是个什么东西,该往哪个 API 路径发。

apiVersion: apps/v1 + kind: Deployment 就是这个 YAML 的"身份证"。K8S 用这套身份证来区分成百上千种资源——这个区分机制叫 GVK。

GVK:Group/Version/Kind​

apiVersion: apps/v1
│ │
│ └── Version
└────── Group

kind: Deployment
└──── Kind
字段含义比方
GroupAPI 组,资源的"部门"公司里的部门
VersionAPI 版本部门里的文件版本 v1/v2
Kind资源类型部门里的具体岗位

三个拼一起就是 GVK:apps/v1/Deployment。

Group 的分类​

Group 有两种:

核心组(core group):没有 group 名,API 路径直接是 /api/v1。Pod、Service、Node、ConfigMap 这些元老级资源都在核心组。

apiVersion: v1 # 没有 group,只有 version
kind: Pod

命名组(named group):有 group 名,API 路径是 /apis/<group>/<version>。后来加的资源都在命名组里。

apiVersion: apps/v1 # group=apps
apiVersion: batch/v1 # group=batch
apiVersion: networking.k8s.io/v1 # group=networking.k8s.io

为什么分 group?跟公司分部门一个道理——资源太多,全堆一个 namespace 里乱。apps 组管工作负载,batch 组管任务,rbac 组管权限,各管各的。

Version 的含义​

Version 不是软件版本,是 API 的成熟度:

版本前缀含义能上生产吗
alpha实验性,随时可能改不能
beta基本稳定,可能有微调谨慎
v1(无前缀)稳定,保证向后兼容能

一个资源可以有多个 version 同时存在——比如 v1beta1 和 v1。K8S 内部做版本转换,对外暴露哪个 version 都行,存到 etcd 时统一转成内部版本。

打个比方:API 像合同模板。alpha 是草稿,beta 是送审版,v1 是正式签章版。同一份合同可能同时有草稿和正式版,但法务(API Server)对外只发正式版,内部归档用统一的版本号。

GVR:Group/Version/Resource​

GVK 是 YAML 里的写法,到了 HTTP 路径里就变成 GVR。

GVK: apps/v1/Deployment (YAML 里这么写)
GVR: apps/v1/deployments (HTTP 路径里这么拼)

区别就是 Kind vs Resource:

KindResource
是什么资源类型的名字(驼峰)REST 路径里的名字(小写复数)
例子Deployment、Pod、Servicedeployments、pods、services
出现在哪YAML 的 kind: 字段URL 路径

简单记:Kind 是单数,Resource 是复数。Deployment 是一个,deployments 是一类。

API 路径怎么拼​

核心组 Pod:
GET /api/v1/namespaces/default/pods/nginx

命名组 Deployment:
GET /apis/apps/v1/namespaces/default/deployments/nginx

CRD(自定义资源)MySQL:
GET /apis/database.example.com/v1/namespaces/default/mysqls/replica

规律:

  • 核心组:/api/<version>/...
  • 命名组:/apis/<group>/<version>/...
  • 都带 namespaces/<ns>/(除非是集群级资源如 Node)

kubectl api-resources​

这个命令列出集群里所有 GVR:

$ kubectl api-resources
NAME SHORTNAMES APIVERSION NAMESPACED KIND
pods po v1 true Pod
services svc v1 true Service
deployments deploy apps/v1 true Deployment
daemonsets ds apps/v1 true DaemonSet
jobs batch/v1 true Job
cronjobs cj batch/v1 true CronJob
mysqls database.example.com/v1 true MySQL

每一行就是一个 GVR。看 APIVERSION 列——拼上 KIND 就是 GVK。

用 kubectl explain 查 schema​

知道 GVK 后,想看这个资源的字段结构:

$ kubectl explain deployment.spec
KIND: Deployment
VERSION: apps/v1

FIELD: spec <DeploymentSpec>

DESCRIPTION:
Specification of the desired behavior of the Deployment.

FIELDS:
replicas <integer>
Number of desired pods.
selector <LabelSelector> -required-
Label selector for pods.
template <PodTemplateSpec> -required-
Template describes the pods that will be created.

这个结构信息哪来的?来自资源的 OpenAPI Schema——描述资源长什么样的 schema 定义。后面写 CRD 时我们会自己定义这个 schema。

Scheme:GVK 和 Go 类型的映射​

Controller 是 Go 写的。YAML 里写 kind: Deployment,Go 里有个 appsv1.Deployment 结构体——这俩怎么对应?

Scheme 就是这个映射表:

scheme := runtime.NewScheme()

// 注册:GVK → Go 类型
scheme.AddKnownTypes(appsv1.SchemeGroupVersion, &appsv1.Deployment{})
scheme.AddKnownTypes(corev1.SchemeGroupVersion, &corev1.Pod{})

// 反序列化时用
obj, err := scheme.New(kind) // kind=Deployment → 返回 &appsv1.Deployment{}

打个比方:Scheme 像 ORM 里的表映射。数据库有 deployments 表,Go 有 Deployment 结构体,Scheme 就是中间那张映射表,告诉框架"这俩是一回事"。

写 Controller 时第一件事就是建 Scheme,把你要管的资源类型注册进去:

var scheme = runtime.NewScheme()

func init() {
clientgoscheme.AddToScheme(scheme) // 注册 K8S 内置资源
mygroupv1.AddToScheme(scheme) // 注册自定义资源
}

没注册的资源,Controller 不认识——读到 kind: MySQL 的 YAML,Scheme 里没有,反序列化直接报错。

RESTMapper:GVK 到 GVR 的桥梁​

你知道 GVK(YAML 里的),想发 HTTP 请求要 GVR(URL 里的)。这俩怎么转?

RESTMapper 干这个活:

gvr, err := mapper.RESTMapping(schema.GroupKind{Group: "apps", Kind: "Deployment"}, "v1")
// 返回 gvr = {Group: "apps", Version: "v1", Resource: "deployments"}

为什么需要这一步?因为 Kind → Resource 的转换规则不是死的——大部分是加 s,但也有特殊情况:

KindResource规律
Podpods加 s
Serviceservices加 s
Ingressingresses加 es
Policypoliciesy → ies
EndpointSliceendpointslices加 s

RESTMapper 就是处理这些边角情况的查表器。

完整链条​

一个 YAML 从提交到落库的完整路径:

你写的 YAML
│
▼ apiVersion + kind
GVK: apps/v1/Deployment
│
▼ RESTMapper 转换
GVR: apps/v1/deployments
│
▼ 拼 HTTP 路径
POST /apis/apps/v1/namespaces/default/deployments
│
▼ API Server 收到
├─ Scheme 反序列化 → &appsv1.Deployment{}
├─ Admission 准入校验
├─ Validation schema 校验
└─ 写入 etcd

写 CRD 时要定义什么​

预告下一章——写 CRD 时,你本质在做这几件事:

  1. 定义 GVK:group: database.example.com, version: v1, kind: MySQL
  2. 定义 schema:spec 里有哪些字段,什么类型
  3. 注册到 Scheme:让 Controller 能反序列化这个资源

理解了 GVK/GVR,下一章写 CRD 就是水到渠成的事。

前置阅读​

  • K8S架构 — Starter 第 2 章,API Server 的路由层就是基于 GVK/GVR 设计的
  • 标准接口:CNI/CSI/CRI — Starter 第 13 章,三大接口也通过 API 暴露,理解 GVK 后回头看这章会更清楚

思考题​

问题 1:apiVersion: v1 和 apiVersion: apps/v1 的区别是什么?为什么 Pod 不用写 group?

问题 2:你写了一个 CRD,kind: MySQL。它的 GVR 是什么?API 路径是什么?

问题 3:Scheme 不注册某个资源类型,会发生什么?


解答​

问题 1:v1 是核心组(core group),没有 group 名,API 路径是 /api/v1。apps/v1 是命名组,group 是 apps,API 路径是 /apis/apps/v1。Pod 是 K8S 最早期的资源,那时候还没有 group 的概念,所以放在核心组里。后来加的资源都进了命名组。

问题 2:GVR 是 mysqls(MySQL 的复数,加 s)。假设 group 是 database.example.com,version 是 v1,那 API 路径是 /apis/database.example.com/v1/namespaces/<ns>/mysqls/<name>。

问题 3:Controller 读到这个资源的 YAML 或 JSON,调用 scheme.New(kind) 时返回 nil 或报错——因为它不知道这个 GVK 对应哪个 Go 类型。反序列化直接失败。这就是为什么写 Controller 必须先把要管的资源注册到 Scheme 里。

下一章​

理解了 GVK/GVR,下一章我们编写 CRD——定义自己的资源类型,教 K8S 认识"MySQL"这样的新词。