1. K8s架构综述
Kubernetes 最初源于谷歌内部的 Borg,提供了面向应用的容器集群部署和管理系统。Kubernetes 的目标旨在消除编排物理/虚拟计算、网络和存储等基础设施资源的负担,并使应用程序运营商和开发人员完全将重点放在以容器为中心的原语上进行自助运营。Kubernetes 也提供稳定、兼容的基础(平台),用于构建定制化的 workflows 和更高级的自动化任务。 Kubernetes 具备完善的集群管理能力,包括多层次的安全防护和准入机制、多租户应用支撑能力、透明的服务注册和服务发现机制、内建负载均衡器、故障发现和自我修复能力、服务滚动升级和在线扩容、可扩展的资源自动调度机制、多粒度的资源配额管理能力。 Kubernetes 还提供完善的管理工具,涵盖开发、部署测试、运维监控等各个环节。
1.1 Borg 简介
Borg 是谷歌内部的大规模集群管理系统,负责对谷歌内部很多核心服务的调度和管理。Borg 的目的是让用户能够不必操心资源管理的问题,让他们专注于自己的核心业务,并且做到跨多个数据中心的资源利用率最大化。
Borg 主要由 BorgMaster、Borglet、borgcfg 和 Scheduler 组成,如下图所示:
- BorgMaster 是整个集群的大脑,负责维护整个集群的状态,并将数据持久化到 Paxos 存储中;
- Scheduer 负责任务的调度,根据应用的特点将其调度到具体的机器上去;
- Borglet 负责真正运行任务(在容器中);
- borgcfg 是 Borg 的命令行工具,用于跟 Borg 系统交互,一般通过一个配置文件来提交任务。
1.2 Kubernetes 架构介绍
Kubernetes 借鉴了 Borg 的设计理念,比如 Pod、Service、Labels 和单 Pod 单 IP 等。Kubernetes 的整体架构跟 Borg 非常像,在设计遵循了c/s架构设计理念,也就是下图中apiserver与其余组件的交互。K8s将集群中的机器划分为主节点(Master Node)和工作节点(Worker Node),在生产中通常会有多个Master节点组成高可用集群,以实现K8s系统管控服务的高可用。K8s业务集群至少有一个工作节点,节点上运行 K8s 所管理的容器化应用。
在Master通常上包括 kube-apiserver、etcd 存储、kube-controller-manager、cloud-controller-manager、kube-scheduler 和用于 K8s 服务的 DNS 服务器(插件)。这些对集群做出全局决策(比如调度),以及检测和响应集群事件的组件集合也称为控制平面。通过这些组件,可实现整个集群的资源管理、pod 调度、弹性伸缩、安全控制、系统监控和纠错等管理功能,并且都是全自动完成的。
其实K8s官方并没有Master这一说,只是大多数安装工具(kubeadm)或者脚本为了架构更明了会把控制平面中的组件安装到一台机器上即Master机器,并且不会在此机器上运行用户容器。但这不是强制性的,所以你也可以将控制平面组件实行分布式部署,不过这样的话高可用会是一个不小的挑战。
集群中的工作节点是真正运行应用程序的地方,各自又通过若干组件的组合来实现。在节点上,K8s管理的最小运行单元是pod。而在Worker Node上的管理组件包括 kubelet 、kube-porxy 以及服务于pod的容器运行时(runtime)。外部storage与registry用于为容器提供存储与镜像仓库服务。
1.3 Kubernetes设计理念
1.3.1 设计理念与分布式系统
分析和理解Kubernetes的设计理念可以使我们更深入地了解Kubernetes系统,更好地利用它管理分布式部署的云原生应用,另一方面也可以让我们借鉴其在分布式系统设计方面的经验。
1.3.2 API设计原则
对于云计算系统,系统API实际上处于系统设计的统领地位。Kubernetes集群系统每支持一项新功能,引入一项新技术,一定会新引入对应的API对象,以支持对该功能的管理操作。理解掌握了K8S的API,就好比抓住了K8s系统的牛鼻子。Kubernetes系统API的设计有以下几条原则:
- 所有API应该是声明式的。声明式的操作,相对于命令式操作,对于重复操作的效果是稳定的,这对于容易出现数据丢失或重复的分布式环境来说是很重要的。另外,声明式操作更容易被用户使用,可以使系统向用户隐藏实现的细节,同时也保留了系统未来持续优化的可能性。此外,声明式的API还隐含了所有的API对象都是名词性质的,例如Service、Volume这些API都是名词,这些名词描述了用户所期望得到的一个目标对象。
- API对象是彼此互补而且可组合的。这实际上鼓励API对象尽量实现面向对象设计时的要求,即"高内聚,松耦合",对业务相关的概念有一个合适的分解,提高分解出来的对象的可重用性。
- 高层API以操作意图为基础设计。如何能够设计好API,跟如何能用面向对象的方法设计好应用系统有相通的地方,高层设计一定是从业务出发,而不是过早的从技术实现出发。因此,针对Kubernetes的高层API设计,一定是以K8s的业务为基础出发,也就是以系统调度管理容器的操作意图为基础设计。
- 低层API根据高层API的控制需要设计。设计实现低层API的目的,是为了被高层API使用,考虑减少冗余、提高重用性的目的,低层API的设计也要以需求为基础,要尽量抵抗受技术实现影响的诱惑。
- 尽量避免简单封装,不要有在外部API无法显式知道的内部隐藏的机制。简单的封装,实际没有提供新的功能,反而增加了对所封装API的依赖性。内部隐藏的机制也是非常不利于系统维护的设计方式,例如StatefulSet和ReplicaSet,本来就是两种Pod集合,那么Kubernetes就用不同API对象来定义它们,而不会说只用同一个ReplicaSet,内部通过特殊的算法再来区分这个ReplicaSet是有状态的还是无状态。
- API操作复杂度与对象数量成正比。这一条主要是从系统性能角度考虑,要保证整个系统随着系统规模的扩大,性能不会迅速变慢到无法使用,那么最低的限定就是API的操作复杂度不能超过O(N),N是对象的数量,否则系统就不具备水平伸缩性了。
- API对象状态不能依赖于网络连接状态。由于众所周知,在分布式环境下,网络连接断开是经常发生的事情,因此要保证API对象状态能应对网络的不稳定,API对象的状态就不能依赖于网络连接状态。
- 尽量避免让操作机制依赖于全局状态,因为在分布式系统中要保证全局状态的同步是非常困难的。
1.3.3 控制机制设计原则
- 控制逻辑应该只依赖于当前状态。这是为了保证分布式系统的稳定可靠,对于经常出现局部错误的分布式系统,如果控制逻辑只依赖当前状态,那么就非常容易将一个暂时出现故障的系统恢复到正常状态,因为你只要将该系统重置到某个稳定状态,就可以自信的知道系统的所有控制逻辑会开始按照正常方式运行。
- 假设任何错误的可能,并做容错处理。在一个分布式系统中出现局部和临时错误是大概率事件。错误可能来自于物理系统故障,外部系统故障也可能来自于系统自身的代码错误,依靠自己实现的代码不会出错来保证系统稳定其实也是难以实现的,因此要设计对任何可能错误的容错处理。
- 尽量避免复杂状态机,控制逻辑不要依赖无法监控的内部状态。因为分布式系统各个子系统都是不能严格通过程序内部保持同步的,所以如果两个子系统的控制逻辑互相有影响,那么子系统就一定要能互相访问到影响控制逻辑的状态,否则,就等同于系统里存在不确定的控制逻辑。
- 假设任何操作都可能被任何操作对象拒绝,甚至被错误解析。由于分布式系统的复杂性以及各子系统的相对独立性,不同子系统经常来自不同的开发团队,所以不能奢望任何操作被另一个子系统以正确的方式处理,要保证出现错误的时候,操作级别的错误不会影响到系统稳定性。
- 每个模块都可以在出错后自动恢复。由于分布式系统中无法保证系统各个模块是始终连接的,因此每个模块要有自我修复的能力,保证不会因为连接不到其他模块而自我崩溃。
- 每个模块都可以在必要时优雅地降级服务。所谓优雅地降级服务,是对系统鲁棒性的要求,即要求在设计实现模块时划分清楚基本功能和高级功能,保证基本功能不会依赖高级功能,这样同时就保证了不会因为高级功能出现故障而导致整个模块崩溃。根据这种理念实现的系统,也更容易快速地增加新的高级功能,因为不必担心引入高级功能影响原有的基本功能。
1.3.4 架构设计原则
- 只有apiserver可以直接访问etcd存储,其他服务必须通过Kubernetes API来访问集群状态
- 单节点故障不应该影响集群的状态
- 在没有新请求的情况下,所有组件应该在故障恢复后继续执行上次最后收到的请求(比如网络分区或服务重启等)
- 所有组件都应该在内存中保持所需要的状态,apiserver将状态写入etcd存储,而其他组件则通过apiserver更新并监听所有的变化
- 优先使用事件监听而不是轮询
1.3.5 引导(Bootstrapping)原则
- Self-hosting 是目标
- 减少依赖,特别是稳态运行的依赖
- 通过分层的原则管理依赖
- 循环依赖问题的原则
- 同时还接受其他方式的数据输入(比如本地文件等),这样在其他服务不可用时还可以手动配置引导服务
- 状态应该是可恢复或可重新发现的
- 支持简单的启动临时实例来创建稳态运行所需要的状态;使用分布式锁或文件锁等来协调不同状态的切换(通常称为pivoting技术)
- 自动重启异常退出的服务,比如副本或者进程管理器等
2 Kubernetes 架构剖析
Kubernetes 主要由以下几个核心组件组成:
- etcd 保存了整个集群的状态;
- kube-apiserver 提供了资源操作的唯一入口,并提供认证、授权、访问控制、API 注册和发现等机制;
- kube-controller-manager 负责维护集群的状态,比如故障检测、自动扩展、滚动更新等;
- kube-scheduler 负责资源的调度,按照预定的调度策略将 Pod 调度到相应的机器上;
- kubelet 负责维持容器的生命周期,同时也负责 Volume(CVI)和网络(CNI)的管理;
- Container runtime 负责镜像管理以及 Pod 和容器的真正运行(CRI),默认的容器运行时为 Docker;
- kube-proxy 负责为 Service 提供 cluster 内部的服务发现和负载均衡;
除了核心组件,还有一些推荐的 Add-ons:
- kube-dns 负责为整个集群提供 DNS 服务
- Ingress Controller 为服务提供外网入口
- Heapster 提供资源监控
- Dashboard 提供 GUI
- Federation 提供跨可用区的集群
- Fluentd-elasticsearch 提供集群日志采集、存储与查询
2.1 分层架构
Kubernetes 设计理念和功能其实就是一个类似 Linux 的分层架构,如下图所示:
- 核心层:Kubernetes 最核心的功能,对外提供 API 构建高层的应用,对内提供插件式应用执行环境
- 应用层:部署(无状态应用、有状态应用、批处理任务、集群应用等)和路由(服务发现、DNS 解析等)
- 管理层:系统度量(如基础设施、容器和网络的度量),自动化(如自动扩展、动态 Provision 等)以及策略管理(RBAC、Quota、PSP、NetworkPolicy 等)
- 接口层:kubectl 命令行工具、客户端 SDK 以及集群联邦
- 生态系统:在接口层之上的庞大容器集群管理调度的生态系统,可以划分为两个范畴
- Kubernetes 外部:日志、监控、配置管理、CI、CD、Workflow、FaaS、OTS 应用、ChatOps 等
- Kubernetes 内部:CRI、CNI、CVI、镜像仓库、Cloud Provider、集群自身的配置和管理等
2.2 核心组件
2.3 核心 API
2.4 生态系统
关于分层架构,可以关注下 Kubernetes 社区正在推进的 Kubernetes architectural roadmap。
2.5 组件通信
Kubernetes 多组件之间的通信原理为:
- API Server 负责 etcd 存储的所有操作,且只有 API Server 才直接操作 etcd 集群
- API Server 对内(集群中的其他组件)和对外(用户)提供统一的 REST API,其他组件均通过 API Server 进行通信
- Controller Manager、Scheduler、Kube-proxy 和 Kubelet 等均通过 API Server watch API 监测资源变化情况,并对资源作相应的操作
- 所有需要更新资源状态的操作均通过 API Server 的 REST API 进行
- API Server 也会直接调用 Kubelet API(如 logs, exec, attach 等),默认不校验 Kubelet 证书,但可以通过
--kubelet-certificate-authority 开启(而 GKE 通过 SSH 隧道保护它们之间的通信)
比如典型的创建 Pod 的流程为:
- 用户通过 REST API 创建一个 Pod
- API Server 将其写入 etcd
- Scheduluer 检测到未绑定 Node 的 Pod,开始调度并更新 Pod 的 Node 绑定
- Kubelet 检测到有新的 Pod 调度过来,通过 Container Runtime 运行该 Pod
- Kubelet 通过 Container Runtime 取到 Pod 状态,并更新到 API Server 中
2.6 各组件端口号
Master node(s)
Worker node(s)