初识 Kubernetes API 的组织结构
Kubernetes CRD 系列:Api Server 和 GVK®
K8S API核心原理介绍:K8S学习系列5
Groups、Versions 和 Kinds 之间的关系
/apis/batch/v1/namespaces/default/job
这个就是表示 default 命名空间的 job 资源相同名称的Kind不仅可以同时存在于不同的Version中(这种很常见),而且可以同时存在于不同的API Group(这种情况相对少)。例如,Deployment作为一个kind,在 extensions group中以alpha类型开始,最终在apps.k8s.io这个API Group 中提升为Stable。我们常见的有:
Ingress, NetworkPolicy同时在这个两个API Group: extensions、 networking.k8s.io
Deployment, DaemonSet, ReplicaSet同时在这些API Group中:extensions、 apps
Event同时在这些API Group中: core group and events.k8s.io
Kubernetes API 通过 HTTP 协议以 RESTful 的形式提供,API 资源的序列化方式主要是以 JSON 格式进行,但为了内部通信也支持 Protocol Buffer 格式。为了方便扩展与演进,kubernetes API 支持分组与多版本,这体现在不同的 API 访问路径上。有了分组与多版本支持,即使要在新版本中去掉 API 资源的特定字段或者重构 API 资源的展现形式,也可以保证版本之间的兼容性。
将整个 kubernetes API 资源分成各个组,可以带来很多好处:
各组可以单独打开或者关闭[7]
各组可以有独立的版本,在不影响其他组的情况下单独向前衍化
同一个资源可以同时存在于多个不同组中,这样就可以同时支持某个特定资源稳定版本与实验版本
关于 kubernetes API 资源的分组信息可以在序列化的资源定义中有所体现,例如:
apiVersion: apps/v1kind: Deploymentmetadata: name: example-deployspec:...
其中 apiVersion 字段中 apps
即为 Deployment 资源的分组,实际上,Deployment 不止出现在 apps
分组里,也出现在 extensions
分组中,不同的分组可以实验不同的特性;另外,kubernetes 中的核心资源如 pod、namespace、configmap、node、service 等存在于 core
分组中,但是由于历史的原因,core
不出现在 apiVersion
字段中,例如以下定义一个 pod 资源的序列化对象:
apiVersion: v1kind: Podmetadata: name: example-pod labels: app: exp...
API 分组也体现在访问资源的 RESTful API 路径上,core 组中的资源访问路径一般为 /api/$VERSION
,其他命名组的资源访问路径则是 /apis/$GROUP_NAME/$VERSION
,此外还有一些系统级别的资源,如集群指标信息 /metrics
,以上这些就基本构成了 kubernetes API 的树结构:
为了支持独立的演进,kubernetes API 也支持不同的版本,不同的版本代表不同的成熟度。注意,这里说的是 API 而非资源支持多版本。因为多版本支持是针对 API 级别,而不是特定的资源或者资源的字段。一般来说,我们根据 API 分组、资源类型、namespace 以及 name 来区分不同的资源对象,对于同一个资源对象的不同版本,API-Server 负责不同版本之间的无损切换,这点对于客户端来说是完全透明的(无感知)。事实上,不同版本的同类型的资源在持久化层的数据可能是相同的。例如,对于同一种资源类型支持 v1
和 v1beta1
两个 API 版本,以 v1beta1
版本创建该资源的对象,后续可以以v1
或者 v1beta1
来更新或者删除该资源对象。
API 多版本支持一般通过将资源分组置于不同的版本中来实现,例如,batch
同时存在 v2alph1
与 v1
版本。一般来说,新的资源分组先出现 v1alpha1
版本,随着稳定性的提高被推进到 v1beta1
,最后从 v1
版本毕业。
随着新的用户场景出现,kubernetes API 需要不断变化,可能是新增一个字段,也可能是删除旧的字段,甚至是改变资源的展现形式。为了保证兼容性,kubernetes 制定了一系列的策略[8]。总的来说,对于已经 GA 的 API,API,kubernetes 严格维护其兼容性,终端用户可以放心食用,beta 版本的 API 则尽量维护,保证不打破版本跨版本之间的交互,而对于 alpha 版本的 API 则很难保证兼容性,不太推荐生产环境使用。
在 kubernetes API 宇宙中,我们经常使用属于 GVK 或者 GVR 来区分特定的 kubernetes 资源。其中 GVK 是 Group Version Kind 的简称,而 GVR 则是 Group Version Resource 的简称。
通过上面对于 kubernetes API 分组和多版本的介绍中我们已经了解了 Group 与 Version,那么 Kind 与 Resource 又分别是指什么呢?
Kind 是 API “顶级”资源对象的类型,每个资源对象都需要 Kind 来区分它自身代表的资源类型,例如,对于一个 pod 的例子:
apiVersion: v1kind: Podmetadata: name: example-pod labels: app: exp...
其中 kind 字段即代表该资源对象的类型。一般来说,在 kubernetes API 中有三种不同的 Kind:
单个资源对象的类型,最典型的就是刚才例子中提到的 Pod
资源对象的列表类型,例如 PodList 以及 NodeList 等
特殊类型以及非持久化操作的类型,很多这种类型的资源是 subresource, 例如用于绑定资源的 /binding
、更新资源状态的 /status
以及读写资源实例数量的 /scale
需要注意的是,同 Kind 不止可以出现在同一分组的不同版本中,如 apps/v1beta1
与 apps/v1
,它还可能出现在不同的分组中,例如 Deployment 开始以 alpha 的特性出现在 extensions
分组,GA 之后被推进到 apps
组,所以为了严格区分不同的 Kind,需要组合 API Group、API Version 与 Kind 成为 GVK。
Resource 则是通过 HTTP 协议以 JSON 格式发送或者读取的资源展现形式,可以以单个资源对象展现,例如 .../namespaces/default
,也可以以列表的形式展现,例如 .../jobs
。要正确的请求资源对象,API-Server 必须知道 apiVersion
与请求的资源,这样 API-Server 才能正确地解码请求信息,这些信息正是处于请求的资源路径中。一般来说,把 API Group、API Version 以及 Resource 组合成为 GVR 可以区分特定的资源请求路径,例如 /apis/batch/v1/jobs
就是请求所有的 jobs 信息。
GVR 常用于组合成 RESTful API 请求路径。例如,针对应用程序 v1 部署的 RESTful API 请求如下所示:
GET /apis/apps/v1/namespaces/{namespace}/deployments/{name}
通过获取资源的 JSON 或 YAML 格式的序列化对象,进而从资源的类型信息中可以获得该资源的 GVK;相反,通过 GVK 信息则可以获取要读取的资源对象的 GVR,进而构建 RESTful API 请求获取对应的资源。这种 GVK 与 GVR 的映射叫做 RESTMapper。Kubernetes 定义了 RESTMapper 接口[9]并带默认带有实现 DefaultRESTMapper[10]。
关于 kubernetes API 的详细规范请参考 API Conventions[11]
经过上一章节的研究,我们已经知道了 kubernetes API 的组织结构以及背后的设计原理,那么,Kubernetes API 的资源对象最终是怎么提供可靠存储的。之前也提到了 API-Server 是无状态的,它需要与分布式存储系统 etcd[12] 交互来实现资源对象的持久化操作。从概念上讲,etcd 支持的数据模型是键值(key-value)存储。在 etcd2 中,各个 key 是以层次结构存在,而在 etcd3 中这个就变成了平级模型,但为了保证兼容性也保持了层次结构的方式。
在 Kubernetes 中 etcd 是如何使用的呢?实际上,前面也提到了,etcd 被部署为独立的部分,甚至多个 etcd 可以组成集群,API-Server 负责与 etcd 交互来完成资源对象的持久化。从 1.5.x 之后,Kubernetes 开始全面使用 etcd3。可以在 API-Server 的相关启动项参数中配置使用 etcd 的方式:
$ kube-apiserver -h
...
Etcd flags:
--etcd-cafile string
SSL Certificate Authority file used to secure etcd communication.
--etcd-certfile string
SSL certification file used to secure etcd communication.
...
--etcd-keyfile string
SSL key file used to secure etcd communication.
--etcd-prefix string
The prefix to prepend to all resource paths in etcd. (default "/registry")
...
--storage-backend string
The storage backend for persistence. Options: 'etcd3' (default).
--storage-media-type string
The media type to use to store objects in storage. Some resources or storage backends may only support a specific media type and will ignore this setting. (default
"application/vnd.kubernetes.protobuf")
...
Kubernetes 资源对象是以 JSON 或 Protocol Buffers 格式存储在 etcd 中,这可以通过配置 kube-apiserver 的启动参数 --storage-media-type
来决定想要序列化数据存入 etcd 的格式,默认情况下为 application/vnd.kubernetes.protobuf
格式;另外也可以通过配置 --storage-versions
启动参数来配置每个 API 分组的资源对象的持久化存储的默认版本号。
下面通过一个简单的例子来看,创建一个 pod,然后使用 etcdctl 工具来查看存储在 etcd 中数据:
$ cat << EOF | kubectl create -f -
apiVersion: v1
kind: Pod
metadata:
name: webserver
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
EOF
pod/webserver created
$ etcdctl --endpoints=$ETCD_URL \
--cert /etc/kubernetes/pki/etcd/server.crt \
--key /etc/kubernetes/pki/etcd/server.key \
--cacert /etc/kubernetes/pki/etcd/ca.crt \
get /registry/pods/default/webserver --prefix -w simple
/registry/pods/default/webserver
...
10.244.0.5"
使用各种客户端工具创建资源对象到然后存储到 etcd 的流程大致如下图所示:
客户端工具(例如 kubectl)提供一个期望状态的资源对象的序列化表示,该例子使用 YAML 格式提供
kubectl 将 YAML 转换为 JSON 格式,并发送给 API-Server
对应同类型对象的不同版本,API-Server 执行无损转换。对于老版本中不存在的字段则存储在 annotations 中
API-Server 将接收到的对象转换为规范存储版本,这个版本由 API-Server 启动参数指定,一般是最新的稳定版本
最后将资源对象通过 JSON 或 protobuf 方式解析并通过一个特定的 key 存入 etcd 当中
上面提到的无损转换是如何进行的?下面使用 Kubernetes 资源对象对象 Horizontal Pod Autoscaling (HPA) 来举例说明:
$ kubectl proxy --port=8080 &
$ cat << EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: webserver
spec:
selector:
matchLabels:
app: webserver
template:
metadata:
labels:
app: webserver
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
EOF
$ kubectl autoscale deployment webserver --min=2 --max=5 --cpu-percent=80
$ curl http://127.0.0.1:8001/apis/autoscaling/v2beta1/namespaces/default/horizontalpodautoscalers/webserver > hpa-v2beta1.json
$ curl http://127.0.0.1:8001/apis/autoscaling/v2beta2/namespaces/default/horizontalpodautoscalers/webserver > hpa-v2beta2.json
$ diff hpa-v2beta1.json hpa-v2beta2.json
3c3
< "apiVersion": "autoscaling/v2beta1",
---
> "apiVersion": "autoscaling/v2beta2",
42c42,45
< "targetAverageUtilization": 80
---
> "target": {
> "type": "Utilization",
> "averageUtilization": 80
> }
通过上面命令的输出能够看出,即使 HorizontalPodAutoscale 的版本从 v2beta1 变为了 v2beta2,API-Server 也能够在不同的版本之前无损转换,不论在 etcd 中实际存的是哪个版本。实际上,API-Server 将所有已知的 Kubernetes 资源类型保存在名为 Scheme 的注册表(registry)中。在此注册表中,定义了每种 Kubernetes 资源的类型、分组、版本以及如何转换它们,如何创建新对象,以及如何将对象编码和解码为 JSON 或 protobuf 格式的序列化形式。