K8s 的一些设计理念
从分层架构、API 设计到控制机制,理解 Kubernetes 的设计哲学
目录
分析和理解 Kubernetes 的设计理念,可以使我们更深入地了解整个系统,更好地利用它管理分布式部署的云原生应用;另一方面,也能让我们借鉴其在分布式系统设计方面的经验。
分层架构
Kubernetes 采用分层架构设计,自下而上构成完整的技术栈:
- 核心层:Kubernetes 最核心的功能,对外提供 API 构建高层的应用,对内提供插件式的应用执行环境。
- 应用层:部署(无状态应用、有状态应用、批处理任务、集群应用等)和路由(服务发现、DNS 解析等)。
- 管理层:系统度量(如基础设施、容器和网络的度量)、自动化(如自动扩展、动态 Provision 等)以及策略管理(RBAC、ResourceQuota、Pod Security Admission、NetworkPolicy 等)。
- 接口层:
kubectl命令行工具、客户端 SDK 以及集群联邦(Federation)。 - 生态系统:在接口层之上的庞大容器集群管理调度生态系统,可以划分为两个范畴:
- Kubernetes 外部:日志、监控、配置管理、CI、CD、Workflow、FaaS、OTS 应用、ChatOps 等。
- Kubernetes 内部:CRI、CNI、CSI、镜像仓库、Cloud Provider、集群自身的配置和管理等。
API 设计原则
对于云计算系统而言,系统 API 实际上处于设计的统领地位。Kubernetes 每支持一项新功能、引入一项新技术,几乎都会引入对应的 API 对象。理解了 API,就抓住了 Kubernetes 的"牛鼻子"。Kubernetes 的 API 设计遵循以下几条原则:
- 所有 API 应该是声明式的。声明式操作相对于命令式操作,在重复执行时效果是稳定的,这对于容易出现数据丢失或重复执行的分布式环境来说至关重要。同时,声明式操作更易于用户使用,可以让系统向用户隐藏实现细节,也就保留了系统未来持续优化的空间。此外,声明式 API 隐含了所有 API 对象都是"名词"性质的——例如 Service、Volume,这些名词描述了用户期望得到的目标分布式对象。
- API 对象是彼此互补而且可组合的。这实际上是鼓励 API 对象尽量满足面向对象设计中的"高内聚、松耦合",对业务相关的概念做合适的分解,以提高对象的可重用性。Kubernetes 这种分布式系统管理平台本身也是一种业务系统,只不过它的业务就是调度和管理容器服务。
- 高层 API 以操作意图为基础设计。如何设计好 API,与如何用面向对象方法设计应用系统相通——高层设计一定是从业务出发,而不是过早地从技术实现出发。因此 Kubernetes 的高层 API 一定以其业务为基础,即以"系统调度管理容器的操作意图"为基础进行设计。
- 低层 API 根据高层 API 的控制需要设计。设计低层 API 的目的是被高层 API 使用,为减少冗余、提高重用性,低层 API 也要以需求为基础,尽量抵抗受技术实现影响的诱惑。
- 尽量避免简单封装,不要有外部 API 无法显式感知的内部隐藏机制。简单封装并未提供新功能,反而增加了对所封装 API 的依赖。内部隐藏机制也非常不利于系统维护。例如 StatefulSet 和 ReplicaSet 本质上是两种不同的 Pod 集合,Kubernetes 就用不同的 API 对象来定义它们,而不是共用同一个 ReplicaSet、再通过内部算法区分有状态与无状态。
- API 操作复杂度与对象数量成正比。这条主要从系统性能角度考虑:要保证系统在规模扩大时性能不会迅速退化到不可用,最低限度是 API 的操作复杂度不能超过 O(N)(N 为对象数量),否则系统就不具备水平伸缩性。
- API 对象状态不能依赖于网络连接状态。在分布式环境下,网络连接断开是常态,因此要保证 API 对象状态能应对网络的不稳定,其状态就不能依赖于网络连接状态。
- 尽量避免让操作机制依赖于全局状态。因为在分布式系统中,保证全局状态的同步是非常困难的。
控制机制设计原则
- 控制逻辑应该只依赖于当前状态。这是为了保证分布式系统的稳定可靠。对于经常出现局部错误的分布式系统,如果控制逻辑只依赖当前状态,那么将一个暂时出现故障的系统恢复到正常状态就很容易——只要将系统重置到某个稳定状态,就可以确信所有控制逻辑会按正常方式运行。
- 假设任何错误都可能发生,并做容错处理。在分布式系统中出现局部或临时错误是大概率事件,错误可能来自物理系统故障、外部系统故障,也可能来自系统自身的代码错误。指望"自己写的代码不会出错"来保证系统稳定是难以实现的,因此要为一切可能的错误设计容错处理。
- 尽量避免复杂状态机,控制逻辑不要依赖无法监控的内部状态。分布式系统的各子系统无法严格通过程序内部保持同步,如果两个子系统的控制逻辑相互影响,那么子系统之间一定要能互相访问到影响控制逻辑的状态,否则就等同于系统中存在不确定的控制逻辑。
- 假设任何操作都可能被任何操作对象拒绝,甚至被错误解析。由于分布式系统的复杂性和各子系统的相对独立性,不同子系统往往来自不同团队,因此不能奢望任何操作都被另一个子系统以正确方式处理。要保证错误发生时,操作级别的错误不会波及系统整体稳定性。
- 每个模块都可以在出错后自动恢复。分布式系统无法保证各模块始终连接,因此每个模块都要有自我修复能力,保证不会因连接不到其他模块而自我崩溃。
- 每个模块都可以在必要时优雅降级。所谓优雅降级,是对系统鲁棒性的要求:在设计实现模块时要划分清楚基本功能和高级功能,保证基本功能不依赖高级功能,也就避免了因高级功能故障而导致整个模块崩溃。按此理念实现的系统,也更容易快速增加新的高级功能,因为不必担心引入高级功能会影响原有基本功能。