Kubernetes API 是 Kubernetes 系统的重要组成部分,组件之间的所有操作和通信以及外部对 Kuber-netes 的调用都是由 API Server 处理的 REST API 调用。API 的设计对于产品内部通信和外部协作。
1. API 结构与版本
Kubernetes API 是通过 HTTP 提供的编程接口,以 REST 风格组织并管理资源,支持通过 POST ,PUT ,DELETE , GET 等标准的 HTTP 方法对资源进行增删改查等操作。
1.1 资源
Kubernetes 中所有内容都被抽象为资源。所有资源都可以使用清单文件(manifest file)进行描述,使用Etcd 数据库进行存储并由 API Server 统一管理。
资源分为集群和命名空间两级作用域,命名空间级资源会在其命名空间删除时被删除。上图资源类别并不代表其作用域
所有资源在其资源对象模式(清单文件)中都有一个具体的表示形式,称为 Kind。同一资源的多个对象(实例)可以组成集合
可以通过 kubectl api-resources 命令查看当前 Kubernetes 环境支持的所有资源的名称,缩写,api 组,作用域及其对应的 Kind
1.2 API
Kubernetes API 大多数情况下遵循标准的 HTTP REST 规范,JSON 和 Protobuf 是其主要序列化结构,资源通过 API 接口传入 API Server 最终持久化到 Etcd 数据库。API 是由 API Server 组件提供服务,API Server 是 Kubernetes 的管理中心,是唯一能够与 Etcd 数据库交互的组件
1.2.1 API 群组
Kubernetes API 除了提供组织和管理各种资源的接口外,还包括一些系统层面的接口。目前 API 主要分为三种形式:
除了系统级 API 外,Kubernetes 基本上是以 API Group(API 群组)的方式组织各种 API 的,核心组 API并未使用/apis/core/v1 路径是历史原因(事实上核心组也成为遗留组)。API 群组是一组相关的 API 对象的集合,使用群组概念能够更方便的管理和扩展 API。
结构示意如下:
1.2.2 API 版本
为了在兼容旧版本的同时不断升级新的 API,Kubernetes 支持多种 API 版本,不同的 API 版本代表其处于不同的稳定性阶段,低稳定性的 API 版本在后续的产品升级中可能成为高稳定性的版本。
API 版本规则是通过基于 API level 选择版本,而不是基于资源和域级别选择,是为了确保 API 能够描述一个清晰的连续的系统资源和行为的视图,能够控制访问的整个过程和控制实验性 API 的访问。
API 通过这种三级渐进式版本共存与演化策略,在不断吸纳新的功能特性并给予其足够的孵化空间的同时,保证了整体 API 的可用性和稳定性。
资源定位三元组
API Group,API Version 和 Resource(GVR 三元组)就可以唯一确定一个资源的 API 路径。如 /apis/r-bac.authorization.k8s.io/v1beta1/clusterroles 。
对于命名空间级资源则需要额外包含具体命名空间(否则将请求所有命名空间下相应资源),如/apis/apps/v1/namespaces/kube-system/deployments 。
对应到资源对象模式(清单文件)三元组则为 API Group,API Version 和 Kind(GVK),相应字段为apiVersion 和 kind ,如{"apiVersion": "app/v1","kind": "Deployment"} 。
Kubernetes 组件默认启用加密通信,并需要请求者提供凭证,为了更方便地请求 API,可以开启代理访问。
kubectl proxy --port=8888 # 开启代理访问
curl http://localhost:8888/api/pods/ # kubectl 代理会自动使用默认凭证路径
(/etc/kubernetes/ssl/)下的凭证文件(kube-proxy.xx)
可以通过 kubectl api-versions 命令查看当前 Kubernetes 环境启用的所有 API 群组及其版本。
1.3 数据持久化与无损转换
用户向 Kubernetes 发起资源构建请求时只提供了一个资源清单文件(如 deployment.yaml),但事实上Kubernetes 基于可用性和稳定性的考虑,却能够支持同时使用不同稳定性的 API 版本访问同一资源,返回不同版本的资源数据。这一灵活的特性有赖于 API Server 的资源数据无损耗转换机制。
1.3.1 数据持久化
资源数据是持久化到 Etcd 数据库中的,而从资源清单文件到持久化到 Etcd 数据库的资源数据的大致流程如下:
1. 客户端(kubectl,curl,sdk 等)得到资源清单文件(YAML 或 JSON 格式)
2. 部分支持格式转换的客户端(如 kubectl,sdk 等)会先将 YAML 格式的资源清单文件转换为 JSON 格式化,然后根据清单字段或相应参数获取 API Server 请求路径,发送到 API Server
3. API Server 对收到的资源清单文件进行准入校验和字段预处理,生成资源数据,对同资源的多个版本进行无损转换
4. API Server 将资源数据转换为指定的存储版本
5. API Server 将存储版本的资源数据按照指定编码(PROTOBUF 或 JSON)进行序列化,以 key-value的 方式存储到 Etcd 中
API Server 启动时可以通过--storage-versions 参数指定资源数据的存储版本(默认是最新稳定版,如v1); 通过--storage-media-type 参数指定序列化编码(默认是 application/vnd.kubernetes.protobuf)。
Etcd 数据库中的资源数据是作为 value 存储的,而对应的 key 则是按照/registry/#{k8s 对象}/#{命名空间}/#{具体实例名}的规范格式生成的。
1.3.2 无损转换
Etcd 数据库中只存储了资源的一个指定版本,但客户端传入的资源清单文件中指定的资源版本和客户端向 API Server 请求的资源版本可能并不是 Etcd 数据库中存储的版本,API Server 如何在各个版本之间进行无损转换呢?
如果一个资源存在众多版本,那么编写各种不同版本之间的转换规则无疑是非常麻烦的,因此 API Server 中维护着一个 internal 版本,需要作版本转换时,任意原版本都先转换为 internal 版本,再由 internal 版本转换到指定的目的版本,如此只要每个版本都可转换为 internal 版本,则可以支持任意版本之间的转换。
而保证版本转换过程中不出现数据丢失(即无损转换)则是依靠 annotations(注解)实现。例如从版本 A 转换到版本 B,对不同字段的处理如下:
版本 A 和 B 中均存在的字段可直接转换
版本 A 中存在而版本 B 中不存在的字段将写入注解中
版本 A 中不存在而版本 B 中存在的字段,如果存在于版本 A 的注解中则从注解中读取字段值,否则字段值置空
2. API 扩展
Kubernetes 因其平台级基础设施的特殊性,与服务器,网络,存储,虚拟化,身份认证等等绝大多数计算机软硬件技术领域存在广泛交集,这需要大量的适配与对接,此外作为底层容器编排引擎,也需要满足高度的可扩展性以面对大量的功能特性扩展需求。
常规的解决方案是修改 Kubernetes 相关 API 和控制器的源代码或者定义新的资源类型并作为新的核心资源 API 合并到 Kubernetes 官方社区代码中。但这些方无疑会迅速使得 Kubernetes 核心 API 资源变得臃肿庞杂难以维护,最终导致 API 过载,这会为项目本身维护和产品生产环境运行的稳定性带来巨大挑战。
Kubernetes 提供了两种 API 扩展机制保证核心 API 足够精简的同时满足庞杂的适配对接和特性扩展需求:
1. 自定义资源类型(CRD): 即 CustomResourceDefinitions。允许用户通过资源清单的方式定义任意全新的资源对象类型,并由 API Server 管理自定义资源的整个生命周期,用户还可以通过定义相应的控制器对自定义资源及其他相关资源进行监视,协调和管理。通常将自定义资源和自定义控制器配合工作的方式统称为 CRD 方式。
2. API Server 聚合(AA): 即 API Server Aggregattion。其前身是用户 API Server(UAS),UAS 允许用户设计一套自定义的 API Server 与 Kubernetes 主 API Server 并行生效,可以在不影响原 API Server 的前提下实现更加复杂和定制化的逻辑和功能,但这种方式对代码开发的要求会比较高。自定义 API Server 可以选择与主 API Server 进行聚合也可以独立存在,但独立存在的方式无法与 Kubernetes 很好的集成,因此自定义 API Server 普遍采用 API Server 聚合的方式。
2.1 自定义资源类型
Kubernetes 原生支持自定义资源的创建和生命周期维护,自定义资源类型一经创建便与 Pod,Job,Secret 等内建资源拥有同等地位,可以像内建资源一样创建并运行自定义资源类型的实例对象。自定义资源配合定制的控制器就可以完成如自动化网络管理,自动化存储管理,自动化证书管理,自动化应用 集群管理等广泛的特性需求。
2.1.1 自定义资源
自定义资源类型的创建
每个 API 资源都有相应的 Group 群组和资源类型,声明自定义资源就必须命名一个与已有群组不重复的新的 Group 群组,新的群组中可以有任意数量的自定义资源类型,并且这些资源类型可以与其他群组中的资源类型重名。
自定义资源类型的声明方式与 Kubernetes 的内建资源的创建方式相同,都是通过资源清单文件进行声明并应用,因为 CustomResourceDefinition 本身就是一种内建资源。一个最简单的自定义资源类型的声明清单示例如下:
# apps-crd.yaml
apiVersion: apiextensions.k8s.io/v1beta1 kind:
CustomResourceDefinition
metadata:
name: apps.foo.bar spec:
group: foo.bar version:
v1 names:
kind: App plural:
apps
scope: Namespaced
各字段解释如下:
apiVersion: CustomResourceDefinition 这一内建资源所在的群组及当前使用的 api 版本。目前为固定字段
kind: 固定字段,表示是在声明自定义资源类型
metadata.name: 自定义资源类型的全名,它由 spec.group 和 spec.names.plural 字段组合而成
spec.group: 自定义资源类型所在群组
spec.version: 自定义资源类型的群组版本
spec.names.kind: 自定义资源的类型,惯例首字母大写
spec.names.plural: 其值通常为 kind 的全小写复数,关系到自定义资源在 REST API 中的 HTTP路径
spec.names.scope: 表示自定义资源的作用范围,Kubernetes 中大部分资源都是命名空间级 (Name-spaced)
自定义资源本身是不支持多版本的,但自定义资源的群组支持多版本。也就是说每一个群组的特定版本里的所有自定义资源都不需要考虑资源版本之间的兼容问题,保证群组内各资源的整体一致性。
spec.names 中还有许多其他字段,不指定则会由 API Server 在创建自定义资源类型时自动填充。
自定义资源类型声明完成后就可以通过 kubectl create -f apps-crd.yaml 或 kubectl apply -f appscrd.yaml 命令进行创建了,创建完成后可通过 kubectl get crd apps.foo.bar -o yaml 命令进行查看。
自定义资源类型创建完成后,其 REST API 的 HTTP 访问路径为/apis/foo.bar/v1/namespaces/de-fault/apps (以 default 命名空间为例)。
自定义资源的创建
自定义资源类型创建完成后就可以创建相应的自定义资源。一个简单的自定义资源的创建清单如下:
# app.yaml apiVersion:
foo.bar/v1 kind: App
metadata: name:
demo
spec:
port: 3333
path: /app
自定义资源声明完成后就可以通过 kubectl create -f app.yaml 或 kubectl apply -f app.yaml 命令进行创建了,创建完成后可通过 kubectl get apps.foo.bar -o yaml 命令进行查看。
自定义资源创建完成后,其 REST API 的 HTTP 访问路径为/apis/foo.bar/v1/namespaces/default/apps/demo (以 default 命名空间为例)。
自定义资源 spec 下的字段只有在被特定控制器或应用按照约定的规范读取解析和处理后才具有实际意义。
终止器
自定义资源和内建资源一样都可以支持终止器(finalizer),终止器允许控制器实现异步的预删除钩子。
对于具有终止器的资源对象第一个删除请求仅仅是为 metadata.deletionTimestamp 字段设置一个值, 而不是删除它,然后触发相应控制器执行自定义处理并删除该资源对象的终止器,最后再一次发出删除请求。
每个资源对象都可以有多个终止器,删除资源对象时,只有当其所有终止器都删除后才会真正被删除。
2.1.2 自定义控制器
在 Kubernetes 中,工作负载(workload)类的资源(如 ReplicaSet,Deployment,StatefulSet,CronJob等运行容器的内建资源)是通过控制器(controller)进行管理的,这些控制器相当于一个状态机,用于控制对应 Pod 的具体状态和行为。
对于自定义资源,同样可以为其编写相应的控制器,进行资源数据的分析处理和 Pod 的状态行为控制。
自定义资源和自定义资制器的配合使用才能创建,配置和管理复杂的有状态应用,真正提供声明式 API 服务,实现新特性的添加和 Kubernetes API 的扩展。
Kubernetes 中控制器的主要工作模式如下:
控制器代码主要包括两部分:
客户端 SDK: SDK 是 Kubernetes 官方提供的开发工具包(sdk 是 golang 编写,又称为 client-go),提供诸如 Reflector,Delta FIFO queue,Thread safe Local store,Informer,Indexer, Workqueue 等与API Server 进行交互的通用组件
控制器特定内容: 根据特定控制器提供的特定功能而编写的相应回调函数和处理逻辑。这部分是编写自定义控制器的主要内容
控制器的完整工作流如下:
1. Reflector 反射器通过 List&Watch 机制从 API Server 获取资源(包括内建资源和自定义资源)变化
2. Reflector 将获取到的资源添加到 Delta FIFO 队列中
3. Informer 通知器从 Delta FIFO 队列中弹出资源对象
4. Informer 将得到的资源对象传递到 Indexer(索引器)
5. Indexer 为资源对象构建索引,以线程安全的方式将资源数据存储到线程安全的 Key-Value 本地存储中
6. Informer 通过 Dispatch Event Handler 事件分发处理函数将资源对象的 key 发送到自定义控制器
7. 自定义控制器通过 Resource Event Handler 资源事件处理函数将资源对象 key 发送到 Workqueue 工作队列
8. Process Item 任务处理函数从工作队列获取资源对象 key 并将其传递给 Object Handler 资源对象处理函数
9. 资源对象处理函数通过 Indexer 的引用从 Key-Value 本地存储中获取资源对象本身并进行处理
2.1.3 Operator 和 Kubebuilder
声明自定义资源并编写自定义控制器进行 Kubenetes API 扩展的方式对于代码开发有一定的要求。主要的工作内容如下:
初始化项目结构
定义自定义资源
编写自定义资源相关代码
初始化自定义控制器
编写自定义控制器相关代码(即业务逻辑)
这其中除了定义自定义资源和编写业务逻辑是需要针对具体需求单独开发外,其他内容都是通用的,可以自动化完成,因此社会和官方提供了一些 CRD 开发脚手架以帮助开发者无需了解复杂的 Kubernetes API 特性的情况下迅速构建 Kubernetes 扩展应用。目前比较流行的有两个:
Operator Framework: CoreOS 公司(目前属 redhat 旗下)开发和维护 CRD 快速开发框架。它包括 Operator SDK 和 Operator Lifecycle Manager 两部分,前者是 Operator 核心开发工具包,后者对 Operator 提供从安装,更新到运维的全生命周期管理
Kubebuilder: Kubenetes 社区兴趣小组开发和维护的 CRD 快速开发框架。提供与 Operator 类似的功能,但不支持 Operator 生命周期管理
Operator 和 Kubebuilder 的实现原理和主要功能类似,二者均使用控制器工具和控制器运行时,封装结构类似,使用难易度相当,其主要区别如下:
Operator SDK 原生支持 Ansible 和 Helm Operator; Kubebuilder 不支持
Operator SDK 集成 Operator Lifecycle Manager(OLM),提供 Operator 全生命周期管理;
Kubebuilder 不支持
Kubebuilder 使用 Makefile 帮助用户完成操作员的任务(构建,测试,运行,代码生成等); Operator SDK 当前使用内置子命令。Operator SDK 团队将来可能会迁移到基于 Makefile 的方法
Kubebuilder 使用 Kustomize 来构建部署清单; Operator SDK 使用带有占位符的静态文件
Kubebuilder 改善了对 admission 准入和 CRD 转换 webhooks 的支持; Operator SDK 尚未支持
Kubebuilder 提供了更为完善的官方文档