千家信息网

深度剖析Kubernetes API Server三部曲 - part 3

发表于:2026-11-02 作者:千家信息网编辑
千家信息网最后更新 2026年11月02日,在本系列的前两部分中我们介绍了 API Server 的总体流程,以及 API 对象如何存储到 etcd 中。在本文中我们将探讨如何扩展 API 资源。在一开始的时候,扩展 API 资源的唯一方法是扩
千家信息网最后更新 2026年11月02日深度剖析Kubernetes API Server三部曲 - part 3

在本系列的前两部分中我们介绍了 API Server 的总体流程,以及 API 对象如何存储到 etcd 中。在本文中我们将探讨如何扩展 API 资源。

在一开始的时候,扩展 API 资源的唯一方法是扩展相关 API 源代码,集成为你所需的资源。或者,推动一个全新的类型为新的核心对象 API 合入社区代码。但是,这样就会导致核心 API 资源类型的不断增加,直至 API 过载。为了避免这种 API 资源的无限制扩展,在 Kubernetes 中提供两种扩展核心 API 的方法:

1. 使用自定义资源定义( CRDs ),最开始的时候被称为第三方资源( TPRs )。通过 CRD 你能够简单而灵活的方式定义自己的资源对象类型,并让 API server 处理整个生命周期。

2. 使用与主 API Servers 并行运行的用户 API Servers ( UAS )。这种方式,可能更多的设计代码开发,可能需要你投入较多的时间及精力。当然,这种方式也能够让你对 API 资源有更细致,全面的了解。

在本文中,我们主要对 CRD 相关定义以及使用进行探讨。

CRDs 的声明及创建

在本系列文章第一部分所提到过的,每个 API 资源根据 Group 群组分类,每个对象都有一个对应的版本号与 HTTP 路径相关联。现在如果想要实现一个 CRD ,首先需要的是就是命名一个新的 API Group 群组,这个 API 群组不能与已经存在的群组重复。在你自己新建的 API 群组中,你可以拥有任意数量的资源,并且它们可以与其他群组中的资源具有相同的名称。下面我们来列举一个实际的例子:

在之前我们有介绍过,每个版本的由 API 群组管理的 Kubernetes 资源是跟 HTTP 路径相关的。 CRD 类似于面向对象编程中一个类的定义,而实际使用的 CR 可以看做为它的一组实例。首先我们对例子中的一些字段作说明,第一行中的 CRD apiVersion 在 kube-apiserver 1.7 之后都是这样定义的。从第 5 行之后我们定义了 spec 的相关字段。在第 6 行 spec.group 是定义了你创建的 CRD 的 API 群组(在本例子中定义为了 example.com )。第 7 行定义了 CRD 对象的版本。每个资源只有一个固定版本,但在 API 群组中还是能有多个不同版本的资源。第 8 行的 spec.names 有两个必填项: kind ,按照惯例第一个字母大写, plural ,按照惯例全为小写,这个字段与最终生成的 HTTP 路径相关,比如在本例子中,最终的 HTTP 路径为 https:// 。还有一个可选的 singular 字段,默认为小写类型值,可以在 kubectl 的上下文中使用。此外,在 spec.names 中还有许多可选字段,这些字段将会由 API Server 自动生成并填充。

上面的 kind 主要是用来描述对象的类型,而 resource 资源是与 HTTP 路径相关的。大多数情况下这两个是匹配的;但是在某些特定情况下在相同的 API HTTP 路径下可能返回不通的 kind (比如 Status 错误对象会返回另一种 kind )。

值得注意的是 resource 资源(在本例中是 databases )和 group 群组(本例中是 example.com )必须与 metadata.name 字段匹配(本例为第四行 databases. example.com )。

现在我们根据上面的 YAML 文件来创建一个 CRD :

$ kubectl create -f databases-crd.yaml

customresourcedefinition "databases.example.com" created

由于这个创建过程是异步进行的,所以你必须检查一下你创建的 CRD 的状态,确认你创建的 CRD 没有与其它资源冲突,并且 API Server 已经调用相关处理函数完成创建。你可以在脚本或代码中通过轮询完成这个过程。最后我们能得到以下状态:

$ kubectl get crd databases.example.com -o yaml

apiVersion: apiextensions.k8s.io/v1beta1

kind: CustomResourceDefinition

metadata:

creationTimestamp: 2017-08-09T09:21:43Z

name: databases.example.com

resourceVersion: "792"

selfLink: /apis/apiextensions.k8s.io/v1beta1/customresourcedefinitions/databases.example.com

uid: 28c94a05-7ce4-11e7-888c-42010a9a0fd5

spec:

group: example.com

names:

kind: Database

listKind: DatabaseList

plural: databases

singular: database

scope: Namespaced

version: v1

status:

acceptedNames:

kind: Database

listKind: DatabaseList

plural: databases

singular: database

conditions:

- lastTransitionTime: null

message: no conflicts found

reason: NoConflicts

status: "True"

type: NamesAccepted

- lastTransitionTime: 2017-08-09T09:21:43Z

message: the initial names have been accepted

reason: InitialNamesAccepted

status: "True"

type: Established

以上,我们可以看到通过 kubectl 可以看到我们之前创建的 CRD ,并且显示出了 CRD 的一些状态信息。

CRDs 的使用

在 通过 kubectl proxy 将 Kubernetes API 开启本地代理后,查看我们刚才创建的 CRD:

$ http 127.0.0.1:8001/apis/example.com

HTTP/1.1 200 OK

Content-Length: 223

Content-Type: application/json

Date: Wed, 09 Aug 2017 09:25:44 GMT

{

"apiVersion": "v1",

"kind": "APIGroup",

"name": "example.com",

"preferredVersion": {

"groupVersion": "example.com/v1",

"version": "v1"

},

"serverAddressByClientCIDRs": null,

"versions": [

{

"groupVersion": "example.com/v1",

"version": "v1"

}

]

}

请注意,在默认情况下十分钟内, kubectl 是查看存储在 ~/.kube/cache/discovery 目录的缓存。所以,可能会需要 10 分钟后你才能看到你新创建的 CRD 资源。但是,当没有缓存时, kubectl 发现不了所需的资源时,那么会重新缓存它。

接下来,我们来看一个 CRD 实例:

$ cat wordpress-database.yaml

apiVersion: example.com/v1

kind: Database

metadata:

name: wordpress

spec:

user: wp

password: secret

encoding: unicode

$ kubectl create -f wordpress-databases.yaml

database "wordpress" created

$ kubectl get databases.example.com

NAME KIND

wordpress Database.v1.example.com

想要通过 API 来监控资源的创建与 更新,你可以通过对某个 resourceVersion (我们通过 curl 来实例对指定版本的 database 做监控 )之后的修改做监控 watch 。

$ http 127.0.0.1:8001/apis/example.com/v1/namespaces/default/databases

HTTP/1.1 200 OK

Content-Length: 593

Content-Type: application/json

Date: Wed, 09 Aug 2017 09:38:49 GMT

{

"apiVersion": "example.com/v1",

"items": [

{

"apiVersion": "example.com/v1",

"kind": "Database",

"metadata": {

"clusterName": "",

"creationTimestamp": "2017-08-09T09:38:30Z",

"deletionGracePeriodSeconds": null,

"deletionTimestamp": null,

"name": "wordpress",

"namespace": "default",

"resourceVersion": "2154",

"selfLink": "/apis/example.com/v1/namespaces/default/databases/wordpress",

"uid": "8101a7af-7ce6-11e7-888c-42010a9a0fd5"

},

"spec": {

"encoding": "unicode",

"password": "secret",

"user": "wp"

}

}

],

"kind": "DatabaseList",

"metadata": {

"resourceVersion": "2179",

"selfLink": "/apis/example.com/v1/namespaces/default/databases"

}

}

我们可以对 /apis/example.com/v1/namespaces/default/databases/wordpress CRD 的 HTTP 路径 通过 curl 命令对的 "resourceVersion": "2154" 进行监控 watch:

$ curl -f 127.0.0.1:8001/apis/example.com/v1/namespaces/default/databases?watch=true&resourceVersion=2154

现在我们新开一个 shell 对话窗口,删除 wordpress CRD 资源,我们可以查看刚才的监控 watch 窗口是否接收到了这个消息:

$ kubectl delete databases.example.com/wordpress

请注意:我们能够使用 kubectl delete database wordpress 删除 CRD 资源,是因为之前在 Kubernetes 没有定义有 database 资源。此外, database 是我们 CRD 中的 spec.name.singular 字段 ,从英语语法派生而来。

我们可以看到之前监控 watch CRD databases 从 API Server 处返回的更新状态:

{"type":"DELETED","object":{"apiVersion":"example.com/v1","kind":"Database","metadata":{"clusterName":"","creationTimestamp":"2017-0[0/515]

:38:30Z","deletionGracePeriodSeconds":null,"deletionTimestamp":null,"name":"wordpress","namespace":"default","resourceVersion":"2154","selfLink":"/apis/example.com/v1/namespaces/

default/databases/wordpress","uid":"8101a7af-7ce6-11e7-888c-42010a9a0fd5"},"spec":{"encoding":"unicode","password":"secret","user":"wp"}}}

上述 shell 会话的运行及输出结果如下图所示:

最后,让我们看一下 CRD database 的各个数据是如何存储在 etcd 中的。下面是我们直接通过 HTTP API 进入 etcd 访问得到的数据:

$ curl -s localhost:2379/v2/keys/registry/example.com/databases/default | jq .

{

"action": "get",

"node": {

"key": "/registry/example.com/databases/default",

"dir": true,

"nodes": [

{

"key": "/registry/example.com/databases/default/wordpress",

"value": "{\"apiVersion\":\"example.com/v1\",\"kind\":\"Database\",\"metadata\":{\"clusterName\":\"\",\"creationTimestamp\":\"2017-08-09T14:53:40Z\",\"deletionGracePeriodSeconds\":null,\"deletionTimestamp\":null,\"name\":\"wordpress\",\"namespace\":\"default\",\"selfLink\":\"\",\"uid\":\"8837f788-7d12-11e7-9d28-080027390640\"},\"spec\":{\"encoding\":\"unicode\",\"password\":\"secret\",\"user\":\"wp\"}}\n",

"modifiedIndex": 670,

"createdIndex": 670

}

],

"modifiedIndex": 670,

"createdIndex": 670

}

}

从上面可以看到, CRD 数据在 etcd 中最终以一个未解析的的状态存在。现在将 CRD 删除,所有的 CRD 实例也会跟着删除,这是一个级联删除操作。

目前 CRDs 的使用现状,局限及将来的展望

CRDs 的发展现状如下所示:

1. 在 Kubernetes 1.7 版本中 CRDs 开始取代 ThirdPartyResources (TPRs) ,并且 TPRs 将会在 Kubernetes 1.8 被删除。

2. 将 TPRs 迁移到 CRDs 实例可以参考文档 migration 。

3. 支持一个 CRD 中只有单个 version 版本,当然,一个群组中可能有多个 version 版本。

4. CRDs 提供一个 API 方案,在用户角度看它与 Kubernetes 原生的 API 资源基本没有区别

5. CRDs 是多版本多分支稳定的基础。关于 CRD 资源的 JSON-Schema 的格式有效性校验可以参考文档 CRD validation proposal 。相关资源回收可以参考文档 Garbage collection 。

接下去我们来看一下一些 CRDs 的局限:

1. CRD 不提供版本转换功能,也就是说,每个 CRD 只能有一个版本(预计不会在近期或中期内看到支持 CRD 版本转换)。

2. 在 Kubernetes1.7 当中,目前并没有对于 CRD 的相关校验 validation 。

3. 没有快速,实时的准入( admission )机制(但是可以支持 webhooks 形式的初始化及准入)。

4. 在 Kubernetes1.7 中你不能定义子资源( sub-resources ),比如 scale 或者 status ,不过目前有在这方面 proposal 的讨论。

5. CRD 目前不支持默认值配置,即不支持为特定的字段配默认值(在 Kubernetes1.7 后续的版本中可能会支持)。

为了解决上述的问题,并且灵活的扩展 Kubernetes ,你可以运行一个与主 API Server 并行的用户 API Servers 。我们将在本博文的以后部分中详细介绍如何编写 UAS ,并编写一个 custom controller 完整使用 CRD 。https://www.huaweicloud.com/product/cce.html


0