随着使用的深入,你会发现 Kubernetes 中内置的对象定义,比如 Deployment、StatefulSet、Configmap,可能已经不能满足你的需求了。你很希望在 Kubernetes 定义一些自己的对象,一来可以通过 kube-apiserver 提供统一的访问入口,二来可以像其他内置对象一样,通过 kubectl 命令管理这些自定义的对象。
Kubernetes 中提供了两种自定义对象的方式,一种是聚合 API,另一种是 CRD。
我们今天就来看看。
聚合 API
聚合 API(Aggregation API,AA)是自 Kubernetes v1.7 版本就引入的功能,主要目的是方便用户将自己定义的 API 注册到 kube-apiserver 中,并且可以像使用其他内置的 API 一样,通过 APIServer 的 URL 就可以访问和操作。
在使用 API 聚合之前,我们还需要通过一些参数在 kube-apiserver 中启用这个功能:
--requestheader-client-ca-file=<path to aggregator CA cert>
--requestheader-allowed-names=front-proxy-client
--requestheader-extra-headers-prefix=X-Remote-Extra-
--requestheader-group-headers=X-Remote-Group
--requestheader-username-headers=X-Remote-User
--proxy-client-cert-file=<path to aggregator proxy cert>
--proxy-client-key-file=<path to aggregator proxy key>
Kubernetes 在 kube-apiserver 中引入了一个 API 聚合层(API Aggregation Layer),可以将访问请求转发到后端用户自己的扩展 APIServer 中。你可以参考这个文档学习如何安装一个扩展的 APIServer。当然,扩展的 APIServer 需要你自己修改并实现相关的代码逻辑。
- Kubernetes apiserver 会判断 --requestheader-client-ca-file 指定的 CA 证书中的 CN 是否是 --requestheader-allowed-names 提供的列表名称之一。如果不是,则该请求被拒绝。如果名称允许,则请求会被转发。
- 接着 Kubernetes apiserver 将使用由 --proxy-client-*-file 指定的文件来访问用户的扩展 APIServer。
下图就是用户发起请求后一个完整的身份认证流程,你可以阅读官方文档来详细了解步骤 。
图 1:一个完整的身份认证流程
配置好了上述参数后, 为了让 kube-apiserver 知道我们自定义的 API,我们需要创建一个 APIService 的对象,比如:
apiVersion apiregistration.k8s.io/v1
kind APIService
metadata
name v1alpha1.wardle.example.com # 对象名称
spec
insecureSkipTLSVerifytrue
group wardle.example.com # 扩展 Apiserver 的 API group 名称
groupPriorityMinimum 1000 # APIService 对对应 group 的优先级
versionPriority 15 # 优先考虑 version 在 group 中的排序
service
name myapi # 扩展 Apiserver 服务的 name
namespace wardle # 扩展 Apiserver 服务的 namespace
version v1alpha1 # 扩展 Apiserver 的 API version
通过上述 YAML 文件,我们就在 kube-apiserver 中创建一个 API 对象组,其对应的组(Group)为 wardle.example.com,版本号为 v1alpha1。创建成功后,我们就可以通过 /apis/wardle.example.com/v1alpha1 这个路径访问了。至于这个对象组里提供了哪些对象,就需要我们在扩展的 APIServer 中声明出来了。
那 kube-apiserver 又是怎么知道将 /apis/wardle.example.com/v1alpha1 这个路径的所有请求转发到后端的扩展 APIServer 中的呢?我们注意到上面的 APIService 定义中,还有一个 spec.service 字段,就是在这里,我们指定了扩展 APIServer 的服务名,也就是说 kube-apiserver 会将 /apis/wardle.example.com/v1alpha1 这个路径的所有访问通过 API 聚合层转发到后端的服务 myapi.wardle.svc 上。
扩展 APIServer 的具体代码设计及逻辑,你可以参考sample-apiserver或者使用apiserver-builder。
这种聚合 API 的方式对代码要求很高,但支持对 API 行为进行完全的掌控,比如你可以自己定义数据如何存储、API 各个版本的切换、各个对象的逻辑控制。
如果你只想简简单单地在 Kubernetes 中定义个对象,可以直接通过下面要介绍的 CRD 定义。
CRD
CRD(CustomResourceDefinitions)在 v1.7 刚引入进来的时候,其实是 ThirdPartyResources(TPR)的升级版本,而 TPR 在 v1.8 的版本被剔除了。CRD 目前使用非常广泛,各个周边项目都在使用它,比如 Ingress、Rancher。
我们来看一下官方的一个例子,通过如下的 YAML 文件,我们可以创建一个 API:
apiVersion apiextensions.k8s.io/v1
kind CustomResourceDefinition
metadata
# 名字必须与下面的 spec 字段匹配,并且格式为 '<名称的复数形式>.<组名>'
name crontabs.stable.example.com
spec
# 组名称,用于 REST API: /apis/<组>/<版本>
group stable.example.com
# 列举此 CustomResourceDefinition 所支持的版本
versions
name v1
# 每个版本都可以通过 served 标志来独立启用或禁止
servedtrue
# 其中一个且只有一个版本必需被标记为存储版本
storagetrue
schema
openAPIV3Schema
type object
properties
spec
type object
properties
cronSpec
type string
image
type string
replicas
type integer
# 可以是 Namespaced 或 Cluster
scope Namespaced
names
# 名称的复数形式,用于 URL:/apis/<组>/<版本>/<名称的复数形式>
plural crontabs
# 名称的单数形式,作为命令行使用时和显示时的别名
singular crontab
# kind 通常是单数形式的驼峰编码(CamelCased)形式。你的资源清单会使用这一形式。
kind CronTab
# shortNames 允许你在命令行使用较短的字符串来匹配资源
shortNames
ct
我们可以像创建其他对象一样,通过 kubectl create 命令创建。创建好了以后,一个类型为 CronTab 的对象就在 kube-apiserver 中注册好了,你可以通过如下的 REST 接口访问,比如 LIST 命名空间 ns1 下的 CronTab 对象,可以通过这个 URL“/apis/stable.example.com/v1/namespaces/ns1/crontabs/”访问。这种接口跟 Kubernetes 内置的其他对象的接口风格是一模一样的。
声明好了 CronTab,下面我们就来看看如何创建一个 CronTab 类型的对象。我们来看看官方给的一个例子:
apiVersion"stable.example.com/v1"
kind CronTab
metadata
name my-new-cron-object
spec
cronSpec"* * * * */5"
image my-awesome-cron-image
通过 kubectl create 创建 my-new-cron-object 后,就可以通过 kubectl get 查看,并使用 kubectl 管理你的 CronTab 对象了。例如:
kubectl get crontab
NAME AGE
my-new-cron-object 6s
这里的资源名是大小写不敏感的,我们在这里可以使用缩写 kubectl get ct,也可以使用 kubectl get crontabs。
同时原生内置的 API 对象一样,这些 CRD 不仅可以通过 kubectl 来创建、查看、修改,删除等操作,还可以给其配置 RBAC 规则。
我们还能开发自定义的控制器,来感知和操作这些自定义的 API。
我们在创建 CRD 的时候,还可以一起定义 /status 和 /scale 子资源,如下所示:
apiVersion apiextensions.k8s.io/v1
kind CustomResourceDefinition
metadata
name crontabs.stable.example.com
spec
group stable.example.com
versions
name v1
servedtrue
storagetrue
schema
openAPIV3Schema
type object
properties
spec
type object
properties
cronSpec
type string
image
type string
replicas
type integer
status
type object
properties
replicas
type integer
labelSelector
type string
# subresources 描述定制资源的子资源
subresources
# status 启用 status 子资源
status
# scale 启用 scale 子资源
scale
# specReplicasPath 定义定制资源中对应 scale.spec.replicas 的 JSON 路径
specReplicasPath .spec.replicas
# statusReplicasPath 定义定制资源中对应 scale.status.replicas 的 JSON 路径
statusReplicasPath .status.replicas
# labelSelectorPath 定义定制资源中对应 scale.status.selector 的 JSON 路径
labelSelectorPath .status.labelSelector
scope Namespaced
names
plural crontabs
singular crontab
kind CronTab
shortNames
ct
这些子资源的定义,可以帮助我们在更新对象的时候,只更改期望的部分,比如只更新 status 部分,避免误更新 spec 部分。
同时 CRD 定义的时候还支持合法性检查、设置默认值等操作,你可以参照文档来实践。
小结
当然,你也可以选择在其他地方定义新的 API,你可以参考这份文档确定是否需要在 Kubernetes 中定义 API,还是让你的 API 独立运行。
你可以通过这个网址中的内容来比较 CRD 和 聚合 API 的功能差异。简单来说,CRD 更为易用,而聚合 API 更为灵活。
Kubernetes 提供这两种选项以满足不同用户的需求,这样就既不会牺牲易用性也不会牺牲灵活性。