Kubernetes API Server原理解析

总体来看,Kubernetes API Server的核心功能是提供Kubernetes各类资源对象(如Pod、RC、Service等)的增、删、改、查及Watch等HTTP Rest接口,成为集群内各个功能模块之间数据交互和通信的中心枢纽,是整个系统的数据总线和数据中心。除此之外,它还有以下一些功能特性。

(1)是集群管理的API入口。
(2)是资源配额控制的入口。
(3)提供了完备的集群安全机制。

Kubernetes API Server概述

Kubernetes API Server通过一个名为kube-apiserver的进程提供服务,该进程运行在Master上。在默认情况下,kube-apiserver进程在本机的8080端口(对应参数--insecure-port)提供REST服务。我们可以同时启动HTTPS安全端口(--secure-port=6443)来启动安全机制,加强REST API访问的安全性。

我们通常可以通过命令行工具kubectl来与Kubernetes API Server交互,它们之间的接口是RESTful API。为了测试和学习Kubernetes API Server所提供的接口,我们也可以使用curl命令行工具进行快速验证。

比如,得到以JSON方式返回的Kubernetes API的版本信息(这里以1.23.0版本默认6443加密端口方式提取):

[root@k8s-master ~]# curl https://192.168.1.10:6443/api --cacert /etc/kubernetes/pki/ca.crt --cert /etc/kubernetes/pki/apiserver-kubelet-client.crt --key /etc/kubernetes/pki/apiserver-kubelet-client.key
{
  "kind": "APIVersions",
  "versions": [
    "v1"
  ],
  "serverAddressByClientCIDRs": [
    {
      "clientCIDR": "0.0.0.0/0",
      "serverAddress": "192.168.1.10:6443"
    }
  ]
}

可以运行下面的命令查看Kubernetes API Server目前支持的资源对象的种类:

curl https://192.168.1.10:6443/api/v1 --cacert /etc/kubernetes/pki/ca.crt --cert /etc/kubernetes/pki/apiserver-kubelet-client.crt --key /etc/kubernetes/pki/apiserver-kubelet-client.key |  jq
Kubernetes API Server原理解析

可以运行下面的命令查看Kubernetes API Server目前支持的资源对象的版本:

kubectl api-resources
Kubernetes API Server原理解析

根据以上命令的输出,我们可以运行下面的curl命令,分别返回集群中的Pod列表、Service列表、RC列表等:

curl https://192.168.1.10:6443/api/v1/pods --cacert /etc/kubernetes/pki/ca.crt --cert /etc/kubernetes/pki/apiserver-kubelet-client.crt --key /etc/kubernetes/pki/apiserver-kubelet-client.key |  jq

curl https://192.168.1.10:6443/api/v1/services --cacert /etc/kubernetes/pki/ca.crt --cert /etc/kubernetes/pki/apiserver-kubelet-client.crt --key /etc/kubernetes/pki/apiserver-kubelet-client.key |  jq

curl https://192.168.1.10:6443/api/v1/replicationcontrollers --cacert /etc/kubernetes/pki/ca.crt --cert /etc/kubernetes/pki/apiserver-kubelet-client.crt --key /etc/kubernetes/pki/apiserver-kubelet-client.key |  jq

如果只想对外暴露部分REST服务,则可以在Master或其他节点上运行kubectl proxy进程启动一个内部代理来实现。

运行下面的命令,在8001端口启动代理,并且拒绝客户端访问RC的API:

Kubernetes API Server原理解析
Kubernetes API Server原理解析

kubectl proxy具有很多特性,最实用的一个特性是提供简单有效的安全机制,比如在采用白名单限制非法客户端访问时,只需增加下面这个参数即可:

--accept-hosts="^localhost$,^192\\.168\\.1\\.10$,^\\[::1\\]$"

第1种使用场景:运行在Pod里的用户进程调用Kubernetes API,通常用来实现分布式集群搭建的目标。比如下面这段来自谷歌官方的Elasticsearch集群例子中的代码,Pod在启动的过程中通过访问Endpoints的API,找到属于elasticsearch-logging这个Service的所有Pod副本的IP地址,用来构建集群,如图

Kubernetes API Server原理解析

在上述使用场景中,Pod中的进程如何知道API Server的访问地址呢?答案很简单:Kubernetes API Server本身也是一个Service,它的名称就是kubernetes,并且它的Cluster IP地址是Cluster IP地址池里的第1个地址!另外,它所服务的端口是HTTPS端口443,通过kubectl get service命令可以确认这一点:

Kubernetes API Server原理解析

第2种使用场景:开发基于Kubernetes的管理平台。比如调用Kubernetes API来完成Pod、Service、RC等资源对象的图形化创建和管理界面,此时可以使用Kubernetes及各开源社区为开发人员提供的各种语言版本的Client Library。后面会介绍通过编程方式访问API Server的一些细节技术。

由于API Server是Kubernetes集群数据的唯一访问入口,因此安全性与高性能就成为API Server设计和实现的两大核心目标。通过采用HTTPS安全传输通道与CA签名数字证书强制双向认证的方式,API Server的安全性得以保障。此外,为了更细粒度地控制用户或应用对Kubernetes资源对象的访问权限,Kubernetes启用了RBAC访问控制策略,之后会深入讲解这一安全策略。

API Server的性能是决定Kubernetes集群整体性能的关键因素,因此Kubernetes的设计者综合运用以下方式来最大程度地保证API Server的性能。

(1)API Server拥有大量高性能的底层代码。在API Server源码中使用协程(Coroutine)+队列(Queue)这种轻量级的高性能并发代码,使得单进程的API Server具备了超强的多核处理能力,从而以很快的速度并发处理大量的请求。

2)普通List接口结合异步Watch接口,不但完美解决了Kubernetes中各种资源对象的高性能同步问题,也极大提升了Kubernetes集群实时响应各种事件的灵敏度。

(3)采用了高性能的etcd数据库而非传统的关系数据库,不仅解决了数据的可靠性问题,也极大提升了API Server数据访问层的性能。在常见的公有云环境中,一个3节点的etcd集群在轻负载环境中处理一个请求的时间可以低于1ms,在重负载环境中可以每秒处理超过30000个请求。

正是由于采用了上述提升性能的方法,API Server可以支撑很大规模的Kubernetes集群。截至1.19版本时,Kubernetes已经可以支持最大规模如下:

  • 最多5000节点规模的集群
  • 最多支持150000个pod
  • 每个node最多支持100个pod
  • 最多支持300000个容器

API Server架构解析

API Server的架构从上到下可以分为以下几层

Kubernetes API Server原理解析

(1)API层:主要以REST方式提供各种API接口,除了有Kubernetes资源对象的CRUD和Watch等主要API,还有健康检查、UI、日志、性能指标等运维监控相关的API。Kubernetes从1.11版本开始废弃Heapster监控组件,转而使用Metrics Server提供Metrics API接口,进一步完善了自身的监控能力。

(2)访问控制层:当客户端访问API接口时,访问控制层负责对用户身份鉴权,验明用户身份,核准用户对Kubernetes资源对象的访问权限,然后根据配置的各种资源访问许可逻辑(Admission Control),判断是否允许访问。

(3)注册表层:Kubernetes把所有资源对象都保存在注册表(Registry)中,针对注册表中的各种资源对象都定义了:资源对象的类型、如何创建资源对象、如何转换资源的不同版本,以及如何将资源编码和解码为JSON或ProtoBuf格式进行存储。

(4)etcd数据库:用于持久化存储Kubernetes资源对象的KV数据库。etcd的watch API接口对于API Server来说至关重要,因为通过这个接口,API Server创新性地设计了List-Watch这种高性能的资源对象实时同步机制,使Kubernetes可以管理超大规模的集群,及时响应和快速处理集群中的各种事件。

从本质上看,API Server与常见的MIS或ERP系统中的DAO模块类似,可以将主要处理逻辑视作对数据库表的CRUD操作。这里解读API Server中资源对象的List-Watch机制。下图以一个完整的Pod调度过程为例,对API Server的List-Watch机制进行说明。

Kubernetes API Server原理解析

list-watch介绍

List-watch 是 K8S 统一的异步消息处理机制,保证了消息的实时性,可靠性,顺序性,性能等等,为声明式风格的 API 奠定了良好的基础,它是优雅的通信方式,是 K8S 架构的精髓。

1、Kubernetes 是通过 List-Watch 的机制进行每个组件的协作,保持数据同步的,每个组件之间的设计实现了解耦。
2、用户是通过 kubectl 根据配置文件,向 APIServer 发送命令,在 Node 节点上面建立 Pod 和 Container。APIServer 经过 API 调用,权限控制,调用资源和存储资源的过程,实际上还没有真正开始部署应用。这里需要 Controller Manager、Scheduler 和 kubelet 的协助才能完成整个部署过程。
3、在 Kubernetes 中,所有部署的信息都会写到 etcd 中保存。实际上 etcd 在存储部署信息的时候,会发送 Create事件给 APIServer,而 APIServer 会通过监听(Watch)etcd发过来的事件。其他组件也会监听(Watch)APIServer 发出来的事件。

上图有三个 List-Watch,分别是 Controller Manager(运行在 Master),Scheduler(运行在 Master),kubelet(运行在 Node)。他们在进程已启动就会监听(Watch)APIServer 发出来的事件。

list-watch工作流程

首先,借助etcd提供的Watch API接口,API Server可以监听(Watch)在etcd上发生的数据操作事件,比如Pod创建事件、更新事件、删除事件等,在这些事件发生后,etcd会及时通知API Server。图中API Server与etcd之间的交互箭头表明了这个过程:当一个ReplicaSet对象被创建并被保存到etcd中后(图中的2.Create RepliatSet箭头),etcd会立即发送一个对应的Create事件给API Server(图中的3. Send RepliatSet Create Event箭头),与其类似的6、7、10、11箭头都是针对Pod的创建、更新事件的。

然后,为了让Kubernetes中的其他组件在不访问底层etcd数据库的情况下,也能及时获取资源对象的变化事件,API Server模仿etcd的Watch API接口提供了自己的Watch接口,这样一来,这些组件就能近乎实时地获取它们感兴趣的任意资源对象的相关事件通知了。图中controller-manager、scheduler、kublet等组件与API Server之间的3个标记有List-Watch的虚框表明了这个过程。同时,在监听自己感兴趣的资源的时候,客户端可以增加过滤条件,以List-Watch 3为例,node1节点上的kubelet进程只对自己节点上的Pod事件感兴趣。

星状图

最后,Kubernetes List-Watch用于实现数据同步的代码逻辑。客户端首先调用API Server的List接口获取相关资源对象的全量数据并将其缓存到内存中,然后启动对应资源对象的Watch协程,在接收到Watch事件后,再根据事件的类型(比如新增、修改或删除)对内存中的全量资源对象列表做出相应的同步修改,从实现上来看,这是一种全量结合增量的、高性能的、近乎实 时的数据同步方式。

接下来说说API Server中的另一处精彩设计。我们知道,对于不断迭代更新的系统,对象的属性一定是在不断变化的,API接口的版本也在不断升级,此时就会面临版本问题,即同一个对象不同版本之间的数据转换问题及API接口版本的兼容问题。后面这个问题解决起来比较容易,即定义不同的API版本号(比如v1alpha1、v1beta1)来加以区分,但前面的问题就有点麻烦了,比如数据对象经历v1alpha1、v1beta1、v1beta1、v1beta2等变化后最终变成v1版本,此时该数据对象就存在5个版本,如果这5个版本之间的数据两两直接转换,就存在很多种逻辑组合,变成一种典型的网状网络,如下图所示,为此我们不得不增加很多重复的转换代码。

Kubernetes API Server原理解析
对象版本转换的拓扑图

上述直接转换的设计模式还存在另一个不可控的变数,即每增加一个新的对象版本,之前每个版本的对象就都需要增加一个到新版本对象的转换逻辑。如此一来,对直接转换的实现就更难了。于是,API Server针对每种资源对象都引入了一个相对不变的internal版本,每个版本只要支持转换为internal版本,就能够与其他版本进行间接转换。于是对象版本转换的拓扑图就简化成了如图5.5所示的星状图。

Kubernetes API Server原理解析
图5.5 星状图

最后简单说说Kubernetes中的CRD在API Server中的设计和实现机制。根据Kubernetes的设计,每种官方内建的资源对象如Node、Pod、Service等的实现都包含以下主要功能。

(1)资源对象的元数据(Schema)的定义:可以将其理解为数据库Table的定义,定义了对应资源对象的数据结构,官方内建资源对象的元数据定义是固化在源码中的。

(2)资源对象的校验逻辑:确保用户提交的资源对象的属性的合法性。

(3)资源对象的CRUD操作代码:可以将其理解为数据库表的CRUD代码,但比后者更难,因为API Server对资源对象的CRUD操作都会保存到etcd数据库中,对处理性能的要求也更高,还要考虑版本兼容性和版本转换等复杂问题。

(4)资源对象相关的“自动控制器”(如RC、Deployment等资源对象背后的控制器):这是很重要的一个功能。因为Kubernetes是一个以自动化为核心目标的平台,用户给出期望的资源对象声明,运行过程中则由资源背后的“自动控制器”负责,确保对应资源对象的数量、状态、行为都始终符合用户的预期。

类似地,每个自定义CRD的开发人员都需要实现上面这些功能。为了减小编程的难度与工作量,API Server的设计者们做出了大量的努力,使得上面前3个功能无须编程实现,直接编写YAML定义文件即可实现。对于唯一需要编程的第4个功能来说,由于API Server提供了大量的基础API库,特别是易用的List-Watch的编程框架,也使得CRD自动控制器的编程难度大大减小。

独特的Kubernetes Proxy API接口

前面讲到,Kubernetes API Server最主要的REST接口是资源对象的增、删、改、查接口,除此之外,它还提供了一类很特殊的REST接口——Kubernetes Proxy API接口,这类接口的作用是代理REST请求,即Kubernetes API Server把收到的REST请求转发到某个Node上的kubelet守护进程的REST端口,由该kubelet进程负责响应。

Node 相关接口

首先来说说Kubernetes Proxy API里关于Node的相关接口。该接口的REST路径为/api/v1/nodes/{name}/proxy,其中{name}为节点的名称或IP地址

具体包括以下几个具体接口:

  • /api/v1/nodes/{name}/proxy/pods # 列出指定节点内所有 pod 的信息
  • /api/v1/nodes/{name}/proxy/stats # 列出指定节点内物理资源的统计信息
  • /api/v1/nodes/{name}/proxy/spec # 列出指定节点的概要信息
  • /api/v1/nodes/{name}/proxy/logs # 列出指定节点的各类日志信息,例如tallylog、lastlog、wtmp、ppp/、rhsm/、audit/、tuned/和anaconda/等
  • /api/v1/nodes/{name}/proxy/metrics # 列出指定节点的 Metrics 信息

例如,当前Node的名称为k8s-node1,用下面的命令即可获取该节点上所有运行中的Pod:

前提:本地终端开启一个proxy

Kubernetes API Server原理解析
curl http://127.0.0.1:8001/api/v1/nodes/k8s-node2/proxy/pods
Kubernetes API Server原理解析

需要说明的是:这里获取的Pod的信息数据来自Node而非etcd数据库,所以两者可能在某些时间点有所偏差。此外,如果kubelet进程在启动时包含--enable-debugging-handlers =true参数,那么Kubernetes Proxy API还会增加下面的接口:

/api/v1/nodes/{name}/proxy/run             # 在节点上运行某个容器
/api/v1/nodes/{name}/proxy/exec            # 在节点上的某个容器中运行某条命令
/api/v1/nodes/{name}/proxy/attach          # 在节点上attach某个容器
/api/v1/nodes/{name}/proxy/portForward     # 实现节点上的Pod端口转发
/api/v1/nodes/{name}/proxy/runningpods     # 列出节点内运行中的Pod信息
/api/v1//nodes/{name}/proxy/debug/pprof    # 列出节点内当前Web服务的状态,包括CPU占用情况和内存使用情况等

Pod 相关接口

接下来说说Kubernetes Proxy API里关于Pod的相关接口,通过这些接口,我们可以访问Pod里某个容器提供的服务(如Tomcat在8080端口的服务):

/api/v1/namespaces/{namespace}/pods/{name}/proxy  # 访问 Pod
/api/v1/namespaces/{namespace}/pods/{name}/proxy/{path:*}  # 访问 Pod 服务的 URL 路径

比如我们部署Tomcat Pod,在/home/目录下新建tomcat.yaml文件并向其中写入如下内容:

apiVersion: v1
kind: ReplicationController
metadata:
  name: myweb # rc 的名称为 myweb
spec:
  replicas: 1 # 只需要创建一个 pod 副本
  selector:
    app: myweb # 选择那些具有 app=myweb 标签的 pod
  template:
    metadata:
      labels:
        app: myweb # 创建的 pod 副本拥有的标签为 app=myweb
    spec:
      containers:
        - name: myweb # 容器名为 myweb
          image: kubeguide/tomcat-app:v1
          ports:
            - containerPort: 8080 # 容器暴露出来的端口为 8080 端口

执行创建,然后获取对应的 Pod:

[root@k8s-master ~]# kubectl get pod
NAME                     READY   STATUS    RESTARTS   AGE
myweb-8dmjd              1/1     Running   0          71s

可以在浏览器中访问上面的地址,我们开启代理后在浏览器中输入http://127.0.0.1:8001/api/v1/namespaces/default/pods/myweb-8dmjd/proxy/,就能够访问Tomcat首页了

http://127.0.0.1:8001/api/v1/namespaces/default/pods/myweb-8dmjd/proxy/
Kubernetes API Server原理解析

看到这里,你可能明白Pod的Proxy接口的作用和意义了:在Kubernetes集群之外访问某个Pod容器的服务(HTTP服务)时,可以用Proxy API实现,这种场景多用于管理目的,比如逐一排查Service的Pod副本,检查哪些Pod的服务存在异常。

Service 相关接口

与 Service 相关接口的 REST 路径为/api/v1/namespaces/{namespace}/services/{name}/proxy

比如,若我们想访问myweb-8dmjd这个Service,则可以在浏览器里输入http://127.0.0.1:8001/api/v1/namespaces/default/services/myweb-8dmjd/proxy/

Kubernetes API Server原理解析

集群功能模块之间的通信

从下图5.6中可以看出,Kubernetes API Server作为集群的核心,负责集群各功能模块之间的通信。集群内的各个功能模块通过API Server将信息存入etcd,当需要获取和操作这些数据时,则通过API Server提供的REST接口(用GET、LIST或WATCH方法)来实现,从而实现各模块之间的信息交互。

Kubernetes API Server原理解析
图5.6 Kubernetes结构图

常见的一个交互场景是kubelet进程与API Server的交互。每个Node上的kubelet每隔一个时间周期,就会调用一次API Server的REST接口报告自身状态,API Server在接收到这些信息后,会将节点状态信息更新到etcd中。此外,kubelet也通过API Server的Watch接口监听Pod信息,如果监听到新的Pod副本被调度绑定到本节点,则执行Pod对应的容器创建和启动逻辑;如果监听到Pod对象被删除,则删除本节点上相应的Pod容器;如果监听到修改Pod的信息,kubelet就会相应地修改本节点的Pod容器。

另一个交互场景是kube-controller-manager进程与API Server的交互。kube-controller-manager中的Node Controller模块通过API Server提供的Watch接口实时监控Node的信息,并做相应处理。

还有一个比较重要的交互场景是kube-scheduler与API Server的交互。Scheduler通过API Server的Watch接口监听到新建Pod副本的信息后,会检索所有符合该Pod要求的Node列表,开始执行Pod调度逻辑,在调度成功后将Pod绑定到目标节点上。

为了缓解集群各模块对API Server的访问压力,各功能模块都采用缓存机制来缓存数据。各功能模块定时从API Server获取指定的资源对象信息(通过List-Watch方法),然后将这些信息保存到本地缓存中,功能模块在某些情况下不直接访问API Server,而是通过访问缓存数据来间接访问API Server。

发布者:LJH,转发请注明出处:https://www.ljh.cool/40448.html

(0)
上一篇 2024年1月23日 下午7:29
下一篇 2024年1月31日 上午12:36

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注