[TOC]
KUBERNETES API 接口模型
1 介绍
Kubernetes 中最核心的就是 kube-apiserver 组件,其他组件都是和 kube-apiserver 进行通信的,本节主要就来研究下 Kubernetes API 接口的模型。
2 API 对象
在 Kubernetes 集群中,Kubernetes 对象是我们持久化的实体,就是最终存入 etcd 中的数据,集群中通过这些实体来表示整个集群的状态。平时我们都是直接编写 YAML 资源清单文件,然后通过 kubectl 来提交创建对应的资源对象,那么它究竟是如何将我们的 YAML 文件转换成集群中的一个 API 对象的呢?
如下:
# 这里我直接通过 kubectl 来获取 kube-system 下的所有 pod 资源列表
root@master:~# kubectl get pod -n kube-system
NAME READY STATUS RESTARTS AGE
calico-kube-controllers-968cf86cc-cd7sn 1/1 Running 5 (10h ago) 225d
calico-kube-controllers-968cf86cc-tt898 1/1 Running 4 (3d11h ago) 225d
calico-node-4w44f 1/1 Running 0 225d
calico-node-g5zn8 1/1 Running 1 (10h ago) 225d
calico-node-xlmrr 1/1 Running 1 (7h50m ago) 225d
coredns-7f6cbbb7b8-gkjlh 1/1 Running 1 (7h50m ago) 225d
coredns-7f6cbbb7b8-wwwmr 1/1 Running 1 (7h50m ago) 225d
etcd-master 1/1 Running 2 (7h50m ago) 225d
kube-apiserver-master 1/1 Running 2 (7h50m ago) 225d
kube-controller-manager-master 1/1 Running 2 (7h50m ago) 225d
kube-proxy-bpf5q 1/1 Running 1 (7h50m ago) 225d
kube-proxy-j8g9j 1/1 Running 1 (10h ago) 225d
kube-proxy-sqwn5 1/1 Running 0 225d
kube-scheduler-master 1/1 Running 2 (7h50m ago) 225d
# 当然也可以通过下面命令来指定一个清单文件或者直接通过 create 来创建一个资源清单
root@master:~# kubectl apply -f xxx.yaml
root@master:~# kubectl create xxx
那么我们是如何将这些 yaml 文件转换为 API 对象的呢?
这些定义在 yaml 文件中的资源最终肯定是需要提交到 apiserver 中的,而当 apiserver 接收到这些数据之后就会将提交数据做成对应的资源类型而成为一个实体,并最后持久化到 ETCD 中。创建完成以后就能通过 kubectl get 来获取对应的资源类型
2.1 版本
这个就需要去了解下声明式 API
的设计,为了可扩展性,Kubernetes 在不同的 API 路径(比如/api/v1
或者 /apis/batch
)下面支持了多个 API 版本,不同的 API 版本意味着不同级别的稳定性和支持:
- Alpha 级别,例如
v1alpha1
默认情况下是被禁用的,可以随时删除对功能的支持,所以要慎用 - Beta 级别,例如
v2beta1
默认情况下是启用的,表示代码已经经过了很好的测试,但是对象的语义可能会在随后的版本中以不兼容的方式更改 - 稳定级别,比如
v1
表示已经是稳定版本了,也会出现在后续的很多版本中。
2.2 API 路径
在 K8S 集群中,一个 API 对象在 ETCD 里的完整资源路径,是由:group(API 组)
、version(API 版本)
和resource(API 资源类型)
三个部分组成的。通过这样的结构,整个 K8S 里的所有 API 对象,实际上就可以如下的树形结构表示出来:
# 比如我们的 deployment 在 K8S 中完整的 URL 路径应该如下:
apps -> v1
# Restful API 的 URL 路径:
/apis/apps/v1/<namespaces>/<namespaces>/deployments
# Pod、svc、node 由于是我们的核心组,所以路径应该如下:
/api/v1/pods
/api/v1/services
/api/v1/nodes
从上图中我们也可以看出 Kubernetes 的 API 对象的组织方式,在顶层,我们可以看到有一个核心组(由于历史原因,是 /api/v1
下的所有内容而不是在 /apis/core/v1
下面)和命名组(路径 /apis/$NAME/$VERSION
)和系统范围内的实体,比如 /metrics
。我们也可以用下面的命令来查看集群中的 API 组织形式:
$ kubectl get --raw /
{
"paths": [
"/api",
"/api/v1",
"/apis",
"/apis/",
......
"/version"
]
}
比如我们来查看批处理这个操作,在我们当前这个版本中存在两个版本的操作:/apis/batch/v1
和 /apis/batch/v1beta1
,分别暴露了可以查询和操作的不同实体集合,同样我们还是可以通过 kubectl 来查询对应对象下的数据:
root@master:~# kubectl get --raw /apis/batch/v1 | python3 -m json.tool
{
"kind": "APIResourceList",
"apiVersion": "v1",
"groupVersion": "batch/v1",
"resources": [
{
"name": "cronjobs",
"singularName": "",
"namespaced": true,
"kind": "CronJob",
"verbs": [
"create",
"delete",
"deletecollection",
"get",
"list",
"patch",
"update",
"watch"
],
"shortNames": [
"cj"
],
"categories": [
"all"
],
"storageVersionHash": "sd5LIXh4Fjs="
},
{
"name": "cronjobs/status",
"singularName": "",
"namespaced": true,
"kind": "CronJob",
"verbs": [
"get",
"patch",
"update"
]
},
{
"name": "jobs",
"singularName": "",
"namespaced": true,
"kind": "Job",
"verbs": [
"create",
"delete",
"deletecollection",
"get",
"list",
"patch",
"update",
"watch"
],
"categories": [
"all"
],
"storageVersionHash": "mudhfqk/qZY="
},
{
"name": "jobs/status",
"singularName": "",
"namespaced": true,
"kind": "Job",
"verbs": [
"get",
"patch",
"update"
]
}
]
}
但是这个操作和我们平时操作 HTTP 服务的方式不太一样,这里我们可以通过 kubectl proxy
命令来开启对 apiserver 的访问:
# 这里我开启 proxy
root@master:~# kubectl proxy
Starting to serve on 127.0.0.1:8001
然后重新开启一个新的终端,我们可以通过如下方式来访问批处理的 API 服务:
root@master:~# curl http://127.0.0.1:8001/apis/batch/v1
{
"kind": "APIResourceList",
"apiVersion": "v1",
"groupVersion": "batch/v1",
"resources": [
{
"name": "cronjobs",
"singularName": "",
"namespaced": true,
"kind": "CronJob",
"verbs": [
"create",
"delete",
"deletecollection",
"get",
"list",
"patch",
"update",
"watch"
],
"shortNames": [
"cj"
],
"categories": [
"all"
],
"storageVersionHash": "sd5LIXh4Fjs="
},
{
"name": "cronjobs/status",
"singularName": "",
"namespaced": true,
"kind": "CronJob",
"verbs": [
"get",
"patch",
"update"
]
},
{
"name": "jobs",
"singularName": "",
"namespaced": true,
"kind": "Job",
"verbs": [
"create",
"delete",
"deletecollection",
"get",
"list",
"patch",
"update",
"watch"
],
"categories": [
"all"
],
"storageVersionHash": "mudhfqk/qZY="
},
{
"name": "jobs/status",
"singularName": "",
"namespaced": true,
"kind": "Job",
"verbs": [
"get",
"patch",
"update"
]
}
]
}
同样也可以去访问另外一个版本的对象数据:
$ curl http://127.0.0.1:8001/apis/batch/v1beta1
......
通常,kubernetes API 支持通过标准 HTTP POST
、PUT
、DELETE
和 GET
在指定 PATH 路径上创建、更新、删除和检索操作,并使用 JSON 作为默认的数据交互格式
比如这里我们需要访问 deployment 的数据,那么只需要向该 deployment 发起一个 GET
请求即可:
http://xxxx:8001/apis/apps/v1/deployment --> GET
# 如果要新建一个 deployment 只需发起一个 POST 请求
http://xxxx:8001/apis/apps/v1/deployment --> POST
# 如果说中途我们修改了该 deployment 那么就需要将新的数据进行上传那么就是 PUT 操作
http://xxxx:8001/apis/apps/v1/deployment --> PUT
# 需要删除该 deployment 就是一个 DELETE 操作
http://xxxx:8001/apis/apps/v1/deployment --> DELETE
当然上面的一系列操作都是对不同资源的 URL 发起请求,而这个 URL 的后面当然是由我们的 APIServer 一直在监听,所以后面肯定有一个 handler 来专门处理这个请求,从而实现不同的 HTTP 操作
也就是说用户的请求动作其实都是向 APIServer 发起网络请求,然后 APIServer 会根据请求的这么一个路径来匹配对应的 URL ,并将该请求的转发给后端对应的 handler 来进行处理
比如现在我们要创建一个 Job 对象,那么我们的 YAML 文件的声明就需要如下写法:
apiVersion: batch/v1 # 这里是将创建的 job 发送到对应的 batch/v1 这个 apiGroup 中
kind: Job # 创建一个 job 的资源对象
metadata: # 下面该 job 的一些元数据信息
name: demo
namespace: default
.......
其中 Job
就是这个 API 对象的资源类型 Kind,而资源 Resource 通常为 Kind 的小写复数词,比如这里就是 jobs
,batch
就是它的组(Group),v1 就是它的版本(Version),API Group、Version 和资源就唯一定义了一个 HTTP 路径,然后在 kube-apiserver 端对这个 URL 进行了监听,然后把对应的请求传递给了对应的控制器进行处理而已,当我们收到请求过后 apiserver 就会去读取到里面的这个 apiVersion。
# 请求完整 url 如下
apiserver --> /apis/apps/v1/namespaces/<namespace>/deployments --> handler -->
group 版本 命名空间 resource
Resource 和 Kind 的区别是什么?需要注意的 Resource 指的是 HTTP Restful API 请求路径中的资源(理解 Restful API 的资源),而 Kind 对应的是系统中真正的实体,这两个是有本质区别的。
Restful API 的 URL 路径:(在 Restful API 中有一个叫做资源的概念,而在 K8S 中这个资源实际上就对应这个对象)
比如一个 Pod 在 ETCD 中存的是一个实体(对象),那么在 Restful API 中对应的资源就是一个 Pods 的 resource,所以 Restful API 指的就是 URL 中的资源,而 Kind 是系统中真正的实例一个真正的对象
每个 Kind 都存在于一个 Group 和 Version 中 , 并通过 GroupVersionKind (GVK) 来标识,GVR 和 GVK 是相关联的,GVK 通过 GVR 标识的 HTTP 路径来提供服务,将 GVK 映射到 GVR 的过程就叫做 REST mapping。
2.3 API 请求处理
上图是 Kubernetes API 处理请求的整个流程:
比如我们要创建一个 Pod 的资源对象的话:
- 创建的时候首先会将整个数据提交给我们的 APIServer
- 当 APIServer 收到这个请求的时候就会根据我们在 yaml 中定义的各个字段,来生成对应的 K8S URL 路径(如 pod 就是
/api/v1/pods
的路径) - 然后再路由到对应的请求 handler(各个资源、pod、job、svc 等路由处理器) 中进行处理,处理完成之后就需要做认证和权限的校验
- 校验成功就会进入到准入控制器
- 当准入控制器校验成功之后才会将真正的数据持久化到 ETCD 中
流程总结:
- HTTP 请求先由
DefaultBuildHandlerChain()
注册的一系列过滤器处理 , 这个函数位于k8s.io/apiserver/pkg/server/config.go 文件中,它对请求进行一系列过滤操作,经过验证的就返回相应 HTTP 返回码 - 接下来根据请求的路径,通过 handler 路由到各种程序中 k8s.io/apiserver/pkg/server/handler.go
- 每个 API Group 都注册了一个 handler , 详情参见k8s.io/apiserver/pkg/endpoints/groupversion.go和 k8s.io/apiserver/pkg/endpoints/installer.go 它接受 HTTP 请求和上下文,并从 etcd 中检索和传递请求的对象进行数据处理。
3 本章总结
在本章节中需要注意以下几个概念:
- api group、api version、kind、resource
- 我们需要理解的是如何把这个 yaml 文件转换为对应的 URL
- 其实就是 kubectl apply -f xxx.yaml ,然后将该 yaml 提交给 APIServer 中
- 这时候 APIServer 就会获取到 yaml 中定义的数据,并通过对应的 apigroup、apiversion、kind、namespace、 来确定需要创建资源的 URL
- 确定了 URL 之后在提交给对应的 handler 进行处理,从而实现资源的创建并持久化至 ETCD 中
- 后面需要查询该 Pod 也是需要通过 ETCD 进行 query