云原生学习路线导航页(持续更新中)
- 快捷链接
- Kubernetes常见问题解答
本文从 Google Borg系统的架构设计开始,深入讲解Kubernetes架构及组件的基本原理
1.什么是云计算
1.1.传统行业应用
- 假设有10台服务器,两个应用。
- 小规模管理下
- 每个应用分别部署在5台服务器上,就ok了
- 但是出现故障时,需要人工下掉节点,重新配置应用等,涉及大量人工手动操作
- 微服务架构盛行之后
- 一个中大型公司,可能会有数千上万个服务器,或数千上万的应用实例
- 此时人工进行应用的部署将变得不可想象。一定会出现大量的纰漏
- 为了满足需求,出现了云计算的概念
1.2.云计算到底是什么
- 云计算是对 网络资源、存储资源的 一种抽象。
- 假设有5000台机器,那么可以做以下操作:
- 1.把这5000台机器从网络上打通,形成一个集群;
- 2.维护一个控制平面,把所有机器的计算资源、存储资源等都抽象出来,比如有 1w个CPU、1wGB内存。
- 3.控制平面还维护了所有机器的状态,哪些机器能参与计算,哪些不能,哪些是up,哪些是down等
- 这样,就形成一个庞大的资源池
- 对于业务来说,不用管自己的业务跑到哪台机器,业务只需要告诉 控制平面,业务需要多少CPU、多少Memory,控制平面就会自动选择合适的节点把应用跑起来。
- 这个控制平面,就是云计算平台。
1.3.云计算平台的分类
- 云计算平台 主要分为两类:
- 以OpenStack为典型的虚拟化平台
- 以谷歌Borg系统为典型的基于进程的作业调度平台
1.3.1.以OpenStack为典型的虚拟化平台
- 虚拟化平台其实发展很平滑,最早大家都在做大型机、小型机,计算机的计算能力遵循摩尔定律,18个月会将芯片的性能提高一倍。此时应用架构都是单体的
- 后来发现摩尔定律逐渐失效了,发展到一定阶段就达到瓶颈了,因为计算能力到了一定上限就很难突破了。
- 另一方面,应用架构开始从单体变向微服务架构,就出现了一个问题:
- 一台物理机,资源可能很多,但是软件的需求很少。
- 因此一台机器就会跑很多个应用,来提高服务器的使用效率
- 可是这么做,应用的隔离性就变得很差
- 为解决这个问题,就出现了虚拟化技术,将一台物理机切割为不同的虚拟机,在不同虚拟机中去部署应用
- OpenStack是虚拟化云平台的代表,它是一个云平台管理的项目,它不是一个软件,而是由几个主要的组件组合起来,为公有云、私有云和混合云的建设与管理提供软件。
- 目前依旧有很多公司在用OpenStack
1.3.2.以谷歌Borg系统为典型的基于进程的作业调度平台
- 谷歌Borg系统完全没有走虚拟化的路,而是基于进程去做调度。
- Borg也是利用容器相关技术实现,比如利用cgroups实现资源限制,早期Borg使用了Chroot技术做隔离,当然现在也可以利用namespace技术了
2.什么是Borg
2.1.Borg简介
- Borg是谷歌最主要的集群管理系统,拥有数十万台机器,运行着数以万计的作业。
- Borg通过将准入控制、高效的任务打包、过度承诺和机器共享与流程级性能隔离结合起来,实现了高利用率。
- 它支持具有运行时特性的高可用性应用程序,这些运行时特性可以最小化故障恢复时间,并且调度策略可以降低相关故障的概率。
- Borg通过提供声明性作业规范语言、名称服务集成、实时作业监控以及分析和模拟系统行为的工具,简化了用户的使用。
- Kubernetes 直接继承自 Borg。 在 Google 的很多从事 Kubernetes 的开发人员以前都是 Borg 项目的开发人员。 他们在 Kubernetes 中结合了 Borg 的最佳创意,并试图解决用户多年来在 Borg 中发现的一些痛点。
2.2.Borg支持两类应用
- Borg支持两类应用:
- Production应用:LongRunningService,即长时间在线的应用,比如Gmail、GoogleDocs、WebSearch
- NonProduction应用:离线作业
2.2.1.Production应用
- Production应用,指长时间在线的应用,即我们日常接触的在线服务,如Gmail、GoogleDocs、WebSearch
- 对在线服务来说,高可用性是命根子,必须保证任何时候都是可访问、可使用的
- 因此在任何时候,都要满足Production应用的资源需求,保障可用性
2.2.2.NonProduction应用
- NonProduction应用:即离线作业,不需要长时间在线
- 比如:
- 电信业务,白天会有很多的支付账单,电信需要把订单数据和收费数据做对账。所以每天半夜就会在Linux上使用Cron job的方式开启离线作业做统计。
- 这种离线作业,可以按定时、按资源的需求、资源的利用率等情况来自行决定。有些离线作业,可以在有资源的时候跑一下,没有资源的时候先暂停。
- NonProduction应用的目标:
- 把整体的资源利用率提上去
- 因为既然上了云,从公司经营角度来说,就一定要给公司降本,因此混部才是唯一出路
2.3.Borg系统的特性
- 关于Google Borg,有一篇论文,论文中详细解释了Borg的特性。
- Large-scale cluster management at Google with Borg
- Borg为什么开源成为Kubernetes
- Google为什么会发那篇paper,是想把这份技术公开出来,希望自己内部也能去做技术迭代,把Borg系统不能解决的一些问题解决掉
- 因此Borg也是kubernetes的一个指导原则
2.3.1.Borg的特性
- 资源利用高
- 资源的额外开销少:类似容器技术,没有虚拟化,所有计算资源全部
- 有混部:在线作业一般都会有资源需求的波峰波谷,机器在请求波谷时资源其实是浪费的,所以Borg通过混部实现在波谷时跑一些短时离线作业,进而提高资源利用率。(空的时候跑,跑完再把资源还回来,或者在线业务激增时直接抢占non-prod应用的资源)
- 服务器共享,在进程级别做隔离
- Borg早期用的 chroot jail做隔离,现在基本上也是通过namespace
那还有就是
- Borg早期用的 chroot jail做隔离,现在基本上也是通过namespace
- 应用高可用,故障恢复时间短
- 后面会详细讲 borg和kubernetes 如何保证应用高可性
- 调度策略灵活
- 后面会详细讲 Borg和kubernetes有哪些调度策略
- 应用接入和使用方便
- 提供了完备的 job 描述语言(声明式API),服务发现,实时状态监控和诊断工具
2.3.2.Borg的优势
- 对外隐藏了资源管理、调度和故障处理这些细节,也是云平台的一个优势
- 声明式API特性,使得一切的业务需求都定义一个对象,这个对象发给控制平面,由控制平面去执行。
- 实现应用的高可靠和高可用
- 足够弹性,支持应用跑在成千上万的机器上
2.4.Borg基本概念
- Workload
- prod应用:在线任务
- non-prod应用:离线任务
- Cell,即集群
- 在Borg中,一个集群就叫一个Cell
- Job 和 Task
- 一个Job是一个基本的调度单位,相当于kubernetes的Pod
- 一个Task就是Job内部的一个作业,相当于kubernetes的Container
- Naming
- Borg系统的 服务注册、服务发现的机制。
- 任何的微服务平台肯定要负责提供服务之间的调用关系,就需要服务注册、服务发现的机制
- Borg系统 通过Borg Name Service 为 Borg 系统中的每个应用提供一个域名,那么 Borg 系统的所有应用都可以使用域名进行互相访问了
2.5.Borg系统架构
学习Borg的架构,会发现和kuebrnetes有很多的共通性。
假设有一天我希望做一套自己的云平台,那它的架构基本上也是八九不离十的
- 如上图,即为Borg系统的架构图
- Cell:就是这个集群,假设这个集群有5000台机器
- BorgMaster
- BorgMaster就是集群的管理节点,接收用户的指令,然后去做调度,把作业发到每个计算节点上面去
- 假设从5000台机器中选5台作为master,则剩下的4995台就是它的计算节点
- 在管理节点上面,需要安装额外的管理组件
- persistent store:基于Paxos协议的数据库。Paxos是分布式键值对存储的一种协议,用于确保分布式一致性
- UI组件:负责接收请求
- Scheduler 调度器:负责将作业调度到具体的计算node上运行
- 因此,BorgMaster整个运作流程即为:UI组件接收到请求,存储起来后,通过调度器下发到计算节点去运行。
- Mini Node
- 下面的每个蓝色立方体,就是一个Mini Node,它们参与计算。
- 集群初始化的时候,这些节点上面都是空的,没有任何的作业,但他们每个都安装了一个叫 Borglet 的组件。
- Borglet 是在每台机器上的一个代理,负责把BorgMaster下发到本节点的进程跑起来,并汇报应用状态
- 调度策略
- Worst Fit(最差适应):
- Worst Fit 即 永远找最空闲 的节点来运行任务。
- 即在所有可用节点中选择剩余资源最多的节点,以便能够容纳更多的任务。
- 这种策略可以减少资源碎片化,但可能导致资源利用率不高。
- Best Fit(最佳适应):
- Best Fit 策略是选择最佳匹配的节点来运行任务。
- 即在所有可用节点中选择剩余资源最接近任务需求的节点,以便能够最大程度地利用资源。
- 这种策略可以提高资源利用率,但可能导致资源碎片化。
- 简单来说,Best Fit就是把尽量多的应用往一个节点上叠,叠满了再去做下一个。这样的话,整个集群里面就永远是在理想状况下一半忙的一半闲的。公有云中,闲的那些可以通过收缩节点,踢出集群来降低费用,节省成本;私有云中,甚至可以把那些节点关机。
- Hybrid(混合):
- Hybrid 策略是一种综合利用 Worst Fit 和 Best Fit 的调度策略。
- 它根据任务的资源需求和集群中的资源情况,动态选择使用 Worst Fit 或 Best Fit 策略来分配任务。
- 这种策略可以在资源利用率和资源碎片化之间取得平衡。
- Worst Fit(最差适应):
- 调度优化的玩法特别多
- 比如 找局部最佳节点:
- 假设一个集群里面有1万个节点,调度时最好的方式就是遍历这1万台机器,找一个最佳的。
- 但有时候可能要求没有那么高,不要求那么完美。就可以其中选10台或100台,从这些机器中找一个最佳的,也可以提升调度效率
- 不过这种能力,目前kubernetes不支持,很多公司会自己做,比如阿里自己做了
- 再比如当有大量的作业要来调度,可以做一些批次调度,来提高效率
- 比如 找局部最佳节点:
注:从这里可以看到,一个系统出现它并不是偶然的,一定是很多以前的经验,把这些思想带到新的系统上来,然后再用新的思维,新的技术手段去实现,这一般是一个系统的变革或者迭代。而为什么kubernetes现在发展前景这么好?包括它API的定义、model的定义等,后面会详细说。
2.6.Borg如何保证高可用?
2.6.1.运行在Borg系统上的应用如何保证高可用?
- 保证鸡蛋不在同一个篮子里:多副本冗余部署、跨故障域部署、跨可用区部署、跨城市部署等等
- 保证幂等性:幂等性就是容忍重复提交的场景。
- 对于异步系统,我们经常会一个作业发出去,系统响应收到后就结束了,但是客户端有时候会重复提交。
- 所谓的密等性,就是不管你的这个请求提交多少次,对提供服务的服务器端,处理都是一样的
2.6.2.Borg控制平面如何保证自身高可用?
2.7.Borg如何提高资源利用率
2.7.1.Borg提高资源利用率
- 在kubernetes集群中,默认会给node设置pod上限110个,
- 可以在kubelet启动命令中增加–max-pods参数,重启kubelet 服务,修改node的pod上限。
- 可以参考:https://www.cnblogs.com/cheyunhua/p/18067849
- 查看kubernetes集群node的pod上限
kubectl describe node vm-226-235-tencentos | grep Capacity -A 10
- 可以在kubelet启动命令中增加–max-pods参数,重启kubelet 服务,修改node的pod上限。
-
那么为什么要设置上限呢,不设置行不行,只要有资源,就让pod运行?
- 跑在node上的应用,是需要定期巡检的,需要知道这些作业的状态,太多的pod会让巡检压力变大
- 110个的默认值,大概是基于一些经验得出的数据,综合考量了资源利用率、管理成本。
-
关于资源利用率,谷歌后来又发了一篇paper,叫 Autopilot
- Autopilot: workload autoscaling at Google
- Autopilot 是通过动态的调整作业的资源需求来完成更高的资源利用率,后面讲应用落地的时候会再讲,包括HPA和VPA等
2.7.2.Borg提高资源利用率的实现
- Borg提高资源利用率的方法
- 允许用户先设置资源limit,但是在实际运行中,Borg会动态监控应用实际的资源使用量,作出冗余分配,或者回收未用到的资源
- 为什么在用户设置limit后还要动态监控?
- 因为一个新开发的应用,它实际上会使用多少资源,研发和运维都是很难确认的
- 一般会通过线上的访问量,高峰时段的QPS等,拿着QPS去乘以一个Buffer(比如50或60),拿着这个数据去对应用做压测,看它的CPU和memory会占多少
- 然后以这个为数值作为参考去设置的资源limit
- Borg在资源申请时的动态计算
- 应用对资源申请的时候,Borg本身也会做一些动态计算
- 比如你可以设的很高,但是接下来Borg会去监控你的资源利用率。在启动的300秒以后,Borg会一直做评估,如果你的真实使用率只有上面绿色框这么点,而你申请了黄色框这么多,那么意味着中间的这一部分都是可以回收的
- 不过Borg会保守回收,他不会把绿色之外的都回收,而是会留一部分保留资源,让应用应对高峰期,剩下的其他资源就回收了。
- 目前在腾讯云这边,这些功能都已经做好了。所以使用公有云和自建云,最大的差异就是各种保障性功能公有云都做好了,自建云的话都需要自己搞
2.8.Borg如何实现隔离性
- 其实不止Borg系统,整个计算机领域中,都认为CPU是可压榨资源,Memory是不可压榨资源
- CPU是分时复用的,假设说CPU不够,应该分给应用A 80ms的,只分给了40ms,其他被别人占用了。从应用程序的角度来说,只是感觉应用慢一点,也不会有太大的问题,可能是用户多了1ms、2ms的,感受也不是很深。这种就是可压榨资源,因此对可压榨资源不需要关照的特别厉害,偶尔超一下没关系的。
- 但是对于Memory内存来说就不一样了,内存一旦超限,大部分情况下就会OOM,所以我们对内存要格外关照,这种资源就是不可压缩资源。
3.什么是Kubernetes(K8s)
3.1.认识Kubernetes
- 上图是Kubernetes worker计算节点的架构
- 在每一个计算节点上面,都会运行一个kubelet
- kubelet会通过Docker本身的interface去起一个个的容器
- 容器之间依然是通过namespace和cgroup做安全隔离和性能隔离
- Kubernetes的功能大部分沿袭了Borg
- 比如说基于容器的应用部署、维护,滚动升级,实现负载均衡,基于服务发现实现了跨集群、跨地域的调度,自动伸缩等
- Kubernetes可以承载有状态服务和无状态服务
- 关于云原生应用,其实更推荐把应用做成无状态的,这样才能更好的利用kubernetes平台的弹性伸缩、跨az部署升级等能力
- 有状态应用一般是千人千面的,不同的中间件,它的内在机理可谓千差万别,没有办法做成一个通用的组件,所以针对一个有状态的中间件,一般会为其单独开发一个Operator,而且一般也需要这个应用的专家才能维护
- 要使用有状态应用的话,建议先去社区搜一搜,可能就会有人已经做出来了,可以二次开发
3.2.Kubernetes代码结构
-
api包
- api包里面有个open-api-spec
- open-api-spec里面有个swagger.json
- swagger一般就是做API声明的,API声明一般会提供一个json,作为接口文档
-
build:所有构建相关的脚本
-
cmd:
- 所有应用程序的main方法入口,二进制的编译入口
- kubernetes是一个项目,这个项目下可以编译出好多个binery二进制可执行文件,就是不同的组件。另外kubernetes支持交叉编译,通过Makefile可以编译出不同CPU架构的可执行文件,如amd、windows等
- 其中的每一个目录都是一个应用模块。每个应用模块中都应该有一个options目录,用于作入参校验等。如kube-apiserver目录下:
-
pkg:各种组件功能的核心逻辑
- api、apis:对象的定义
- 每种组件都有一个独立的目录
3.3.命令式vs声明式 系统
- 现在系统大部分会被分为两种模式:
- 命令式
- 声明式
- 命令式(Imperative System)
- 一般就是交互式的系统,它会关注如何做
- 比如说我在一个操作系统里面,我敲一个ls,我来看系统会给我什么反馈,基于他的反馈决定我下一次怎么做,这就是命令式
- 命令式系统需要人工全程参与,根据上一次的结果,决定下一条命令怎么敲
- 可以类比电视遥控器换台,不喜欢的话再换
- 声明式(declarative system)
- 我只约定我需要做什么,不关心系统是怎么做的,即面向终态的
- 可以类比空调遥控器,空调的温度设定为25摄氏度,我期望最终变成25度,但是过程我不管
- 二者对比
- 命令式系统一定是响应非常快,或者比较简单的系统,要求立刻给出反馈
- 声明式系统更适合复杂系统,比如说一个复杂任务,可能20分钟才能做完,过程中不阻塞,等做完后给出个通知提示就好。或者任何时候我要去查询的时候,你只需要把实时的状态返回即可
- kubernetes就是一种声明式系统
- 用户通过定义资源的spec,表达自己的期望状态。Kubernetes通过资源的status,表示实际状态。如何把状态调整到spec,由Kubernetes系统完成,不需要用户做指导和介入。
- kubernetes能够变成业界标准的原因主要有两个。
- 其一:kubernetes只是定义了一堆厂商中立的、通用的API,并且提供了一套框架来支撑这些API,维护最核心的功能。
- kubernetes使用这些通用的API和框架,企图解决业界的一些通用问题,比如高可用怎么做、滚动升级怎么做、故障转移怎么做、扩缩容怎么做等。kubernetes给出的解决方案,和任何的厂商实现、任何的技术实现都没有太大关系,因此从这个角度来看,这种项目一定是一个长寿项目,生命周期一定非常长。
- 对于不同的厂商来说,你用什么样的架构(arm、x86…)、用什么样的网络,用什么样的存储,这是你厂商要去解决的,你自己去选plugin。其实每个开源的plugin后面可能都围绕着一个厂商
- kubernetes把这些东西剥离开,自己只解决最核心的通用问题,这是它最厉害的地方。
- 其一:kubernetes只是定义了一堆厂商中立的、通用的API,并且提供了一套框架来支撑这些API,维护最核心的功能。
- 其二:kubernetes系统足够灵活,任何人任何方案都可以和其对接,你自己提供一个CA网络插件,你有可能就成了一个创业公司,给整个生态都带来了机会,所以大家也愿意跟你一块发展。
3.4.kubernetes声明式系统包含哪些资源
- Node
- Kubernetes的本质就是把一堆节点组成一个集群,如何把几百上千个节点 组成一个集群,涉及集群管理的功能。
- Kubernetes抽象了一个Node的概念
- Node的名称即为主机名,node资源中记录了当前节点的状态:CIDR是什么、状态怎么样、一共有多少资源、可分配资源数量…
- Namespace
- 为了把多个用户或者多个项目的对象隔离开,Kubernetes抽象了一个Namespace的概念
- 比如一个操作系统,不同的文件目录,可以设置不同的权限,进行文件隔离。Namespace就相当于一个虚拟目录,Kubernetes可以为不同Namespace设置不同的访问权限,进行访问控制。
- 当然有些对象,比如Node是为整个集群提供服务的,不属于任何的namespace,因此Kubernetes的对象被分为:Namesapce对象、NonNamespace对象
- Pod
- 前面提到的都是管控层面的资源,真正用来描述应用实例的核心对象是Pod。Pod对象的出现打通了基础架构和应用接入的两个维度
- 以前存在的问题:假设 现在使用了OpenStack集群,OpenStack只管理到操作系统层,应用人员只管到应用部署,上下两层是割裂的。难以控制应用要部署到哪个集群上,那如果要对openStack集群进行升级,就很难知道哪些节点可以一起重启,重启的时候会影响哪些应用,因此使得我基础架构层面的升级变得非常困难。
- Kubernetes使用Pod打通了基础架构和应用接入的两个维度,应用层面只需要将自己的代码构建成容器镜像,然后资源、存储等基础架构的设置 使用 Pod的统一API进行联动,就可以控制 应用 跑在哪些节点上面。也可以随时查看某个节点上正在运行哪些应用,就可以知道重启节点会影响哪些应用了。
- Service
- Pod完成对应用的部署,但是还没有发布出去。想要发布一个服务,一定需要负载均衡、服务发现、服务治理等能力,Service应运而生。
3.5.Kuberntes系统架构
-
下图即为Kubernetes的架构, 乍一看去,跟BOG系统没有什么区别,因此可以看出Borg真的是Kubernetes的前身
-
下图是一个Kuberntes集群,对应Borg的一个Cell
-
控制平面组件(Master节点)
- API Server
- 整个Kubernetes系统的API网关,其实本质上就是一个Web服务,逻辑并不复杂,但是apiserver的地位非常重要,所有的Kubernetes组件都不会直接通信,而是通过API Server进行间接通信
- API Server会对 所有的请求 进行 准入控制、鉴权等,然后对etcd数据库进行crud
- etcd
- 基于Raft协议的 Watchable(后面会讲)分布式键值对存储,所有资源的信息都存储在这里。
- Scheduler
- Scheduler 是控制平面的组件, 负责监视新创建的、未指定运行节点(node)的 Pods, 并选择节点来让 Pod 在上面运行。
- 具体操作:当Scheduler发现etcd中有尚未调度的pod,就会从健康可调度node中选择一台,修改pod的node字段。之后对应node上的kubelet就会调用容器运行时接口,在自己node上创建pod了
- Controller Manager
- 一个好的云平台,一定有大量自动化逻辑,Controllers其实就是帮我们的资源实现大量自动化逻辑的位置。
- controller-manager负责运行控制器进程。从逻辑上讲, 每个控制器都是一个单独的进程,但是为了降低复杂性,它们都被编译到同一个可执行文件,并在同一个进程中运行。
- controller-manager包含了非常多的Controller,每个Controller都会监听自己关心的那部分资源,并负责将资源调谐到 spec中期望的状态
- API Server
-
计算节点组件(Worker节点)
- Kubelet
- 每个节点上都会跑一个Kubelet,负责上报 节点状态,pod状态、pod资源使用情况等信息到ApiServer,由ApiServer写入etcd。
- 同时,Kubelet也会和apiserver交互,当发现 有pod被 Schedule 调度到自己所在node时,就会调用一系列标准接口,拉起pod的进程,并为之挂载网络和存储。
- 厂商无关的标准接口:容器运行时(CRI)、容器网络标准(CNI)、容器存储标准(CSI)
- Kube-Proxy(可选)
- kube-proxy 实际上就是用来实现Service的一部分,是集群中每个节点(node)上所运行的网络代理。
- kube-proxy 维护节点上的一些网络规则, 这些网络规则会允许从集群内部或外部的网络会话与 Pod 进行网络通信。
- 如果操作系统提供了可用的数据包过滤层,则 kube-proxy 会通过它来实现网络规则。 否则,kube-proxy 仅做流量转发。
- Kubelet
-
上述组件并不是kubernetes所包含的所有组件,更详细的请参考:Kubernetes官网架构介绍
4.Kubernetes组件原理简介
4.1.etcd
4.1.1.etcd简介
- ETCD本身是一个有状态的应用,基于Raft协议的分布式存储
- 单节点存储的缺点
- 如果数据库是单节点存储,那效率一定是很高的,但是会带来数据丢失的风险,万一磁盘坏了,数据就没了。
- 因此就需要考虑数据备份,数据备份后安全性又需要保证,可以看出单节点存储本身会有很多问题。
- 分布式存储
- 那么有没有一种方式,可以让数据安全性提高一个等级呢?
- 分布式存储,即使用多台服务器存储同一份数据,一个节点坏了,还有其他拷贝呢,这样的话数据的安全性就得以保证了。
- 但是分布式存储需要解决两个问题:多副本的数据一致性、某个节点宕机后如何继续工作。Raft协议解决了这两个问题:
- 分布式存储需要保证数据的一致性,写数据的时候需要 在多个存储中同时写入,使大家的数据状态都是一致的。
- 节点宕机后整个应用还可以继续工作
- etcd 集群使用 Raft 协议保障多节点集群状态下的数据一致性。etcd 是使用 Go 语言对 Raft 协议一种实现方式。
- Etcd 中文文档
- ETCD的应用场景
- 服务发现,有点类似于concul的服务发现机制
- 共享配置:相当于把它当成一个键值分布式数据库
- 监听机制:
- 一般的数据库,给它一个查询,它给你返回一个结果就结束了。但是在分布式系统里面经常需要用到消息队列实现监听,希望只在某件事情发生时才进行通知。
- 对kubernetes的控制器来说,如果一直轮询请求apiserver来获取 资源的变化,实时性不好,而且对etcd本身的并发能力要求特别高
- ETCD可以充当消息队列的角色,只在 数据发生变化时,通知 对应的监听者。
- 实现方式:当一个客户端去访问ETCD数据库的时候,可以不是通过get的方式,而是watch的方式建立一个长连接,当数据发生变化的时候再来通知。这种方式减少了数据库的压力,而且听起来这就是一个消息中间件,巧妙地解决了组件和组件之间的协调关系。
4.1.2.etcd基本使用
- kubernetes中。演示下etcd访问数据方式
# 声明个别名 [root@VM-226-235-tencentos ~]# alias ks='kubectl -n kube-system' # 获取etcd pod [root@VM-226-235-tencentos ~]# ks get pods | grep etcd NAME READY STATUS RESTARTS AGE etcd-vm-226-235-tencentos 1/1 Running 0 223d # 进入pod内部[pod中没有bash命令,所以使用sh进入] [root@VM-226-235-tencentos ~]# ks exec -it etcd-vm-226-235-tencentos bash kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead. OCI runtime exec failed: exec failed: unable to start container process: exec: "bash": executable file not found in $PATH: unknown command terminated with exit code 126 [root@VM-226-235-tencentos ~]# ks exec -it etcd-vm-226-235-tencentos sh kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead. sh-5.0# # 查看etcd的主进程,发现没有ps命令 sh-5.0# ps -ef sh: ps: command not found # 直接使用etcdctl也可以的,先指定要使用的 etcd API 版本为 v3 sh-5.0# export ETCDCTL_API=3 # 通过与 etcd 服务器建立安全连接,获取以斜杠(/registry/pods)开头的所有键的列表,而不返回与这些键关联的值 # --endpoints 指定 etcd 服务器的地址和端口。本例中 etcd 服务器位于 localhost 的 2379 端口上 # --cert、--key:指定用于进行 TLS 客户端身份验证的证书文件、私钥文件路径 # --cacert:指定用于进行 TLS 服务器身份验证的 CA 证书文件路径 # get --keys-only:具体要执行的操作,且只返回键(key),不返回与键关联的值(value) # --prefix:指定要获取的键的前缀 sh-5.0# etcdctl --endpoints https://localhost:2379 --cert /etc/kubernetes/pki/etcd/server.crt --key /etc/kubernetes/pki/etcd/server.key --cacert /etc/kubernetes/pki/etcd/ca.crt get --keys-only --prefix /registry/pods /registry/pods/default/my-pod /registry/pods/default/mysql-pod /registry/pods/default/nginx-deployment-585449566-f9zt2 /registry/pods/default/nginx-deployment-585449566-gtw92 /registry/pods/default/nginx-deployment-585449566-kzdgv # 对etcd中的一个键值对,添加watch【以ns==default下的mysql-pod为例】 # 该命令执行完,会建立一个长连接,控制台会进入实时展示的状态 # 需要另外开一个窗口,编辑mysql-pod的yaml,查看watch机制的通知。 sh-5.0# etcdctl --endpoints https://localhost:2379 --cert /etc/kubernetes/pki/etcd/server.crt --key /etc/kubernetes/pki/etcd/server.key --cacert /etc/kubernetes/pki/etcd/ca.crt watch --prefix /registry/pods/default/mysql-pod
- 下面演示watch的效果
- apiserver如何利用etcd的watch机制,实现控制器的watch?
- 首先apiserver会对etcd中所有的数据,进行watch,并把watch到的数据添加一份缓存
- 同时,apiserver也对外提供watch机制,所有的kubernetes组件都可以 建立长连接 watch自己感兴趣的资源,当资源发生变化的时候,通过长连接将数据变更事件发往各个组件
- kubernetes组件首先会采用List机制,获取资源数据,然后以 watch机制 获取增量变化
- 当一个组件watch到资源有变化的时候,要做什么处理,就是自己的代码逻辑了
4.2.APIServer
4.2.1.APIServer简介
- apiserver的watch缓存
- 从前面我们知道,apiserver是唯一一个和etcd通信的组件
- 并且apiserver维护了一个watch缓存,List+Watch etcd中的所有资源,使得所有对资源的读操作 都直接走apiserver的watch缓存,写操作才会到达etcd
- 这么做,是保护了etcd不被击穿,不会承受太大压力
- apiserver的版本
- v3版本已经比较成熟,v2当时是存在很多问题的
- apiserver的三大功能
- 认证
- 授权
- 准入控制
4.2.2.APIServer架构
- APIHandler:http服务一般都包括的 url–>function路由转发
- AuthN:authentication认证
- Rate Limit:做限流处理,防止自己被过多流量打挂
- Auditing:记录审计日志,可以用于破坏命令的责任追查
- AuthZ:鉴权,一般会使用RBAC
- Aggregator:apiserver的聚合器
- 如果你想要自己做后续的 数据变形+验证+etcd交互,可以编写自己的apiserver,作为Aggregated APIServer,链接到此处。这种方式让APIServer具备了极大的扩展性。
- Aggregated Server有时也会称为 Extension Server
- 一般kubernetes的内置资源,都会按照上面的流程走完。
- MutatingWebhook:数据变形处理器,包括 apiserver默认的、外置的。
- 很多CRD,都会有自己的数据变形需求,可以开发一个MutatingWebhookService,作为Webhook供Apiserver在此处调用
- 需要注意,Apiserver调用MutatingWebhookService,肯定是需要TLS连接的,所以MutatingWebhookService一般要配置证书的
- SchemaValidating:校验变形后的数据,是否符合Schema规范
- ValidatingWebhook:数据校验器,包括 apiserver默认的、外置的。
- 很多CRD,都会有自己的数据校验需求,可以开发一个ValidatingWebhookService,作为Webhook供Apiserver在此处调用
- 需要注意,Apiserver调用ValidatingWebhookService,肯定是需要TLS连接的,所以ValidatingWebhookService一般要配置证书的
- etcd:上述步骤都走完了之后,才会将数据写入etcd,保护还是很严密的
4.2.3.Aggregated APIServer的使用案例
- 比如 要给pod做 HPA 自动扩缩容,肯定是需要一些cpu、memory等指标数据的,这些指标要在APIServer中取
- 在原生APIServer中是不包含这些指标的,因此可以自己写一个 Metrics APIServer,以 Aggregated APIServer的方式嵌入到APIServer中去,HPA的一些Controller就可以在APIServer中获取这些metrics指标数据了
- 目前kubernetes社区已经有了开源的 metrics APIServer:即 metrics-server
- https://github.com/kubernetes-sigs/metrics-server
- 官方文档
- 安装之后,可以使用kubectl top命令,查看指标
- 比如
kubectl top node
就可以展示node的cpu、memory等使用情况
- 比如
4.3.ControllerManager
4.3.1.ControllerManager介绍
- 每个Controller逻辑是大同小异的,都是一个生产者消费者模型
- Controller里的watcher就是一个生产者:使用watch机制监控APIServer中某些资源的变化,观察到的变化事件都会放入一个队列中
- Controller里的处理逻辑作为消费者:不断从队列中取出数据去做调谐。如果调谐失败,需要有将事件重新放回队列,等待一段时间后重试,以此达到 最终一致性。
- 最终一致性:不能保证一次就能达到spec期望状态,但是会有重试机制,保证经过多轮处理后,最终能够达到spec期望状态。
- ControllerManager其实是一个控制器合集,内置资源的Controller都在这里,比如:
- Deployment Controller
- ReplicaSet Controller
- Service Controller
- …
4.3.2.控制器的工作流程
- Controllerde Informer机制一般都包含两部分
- Lister:负责从APIServer List全量数据
- Informer:负责从APIServer Watch数据的变化
- Controller 处理的基本流程
- 首先Lister会从 APIServer List 全量数据
- 之后Informer 会持续Watch资源的变更事件,并将不同的事件使用对应的 EventHandler(可以自行注册) 处理后,将资源key(namespace+name)加入队列
- 启动一个或多个协程goroutine,作为消费者,从队列中取出资源key并处理
4.3.3.Informer的内部机制
- Informer的组件及其职责
- Reflector:反射器,负责将APIServer发过来的对象数据(一般是json或protobuf),反序列化成一个go struct
- Delta FIFO Queue:循环FIFO队列,负责存储 待处理对象,Delta FIFO Queue是一个循环队列,满的时候会覆盖旧数据
- Informer:通知器,负责从Delta FIFO Queue中不断弹出对象,做两件事:
- 通过Indexer索引器,以key-value形式存入Thread Safe Store本地存储。
- key:namespaceName
- value:对象本身
- 通过Handler,调用当前Event类型 对应的那一个Handler,处理后将对象的key添加到WorkQueue
- 通过Indexer索引器,以key-value形式存入Thread Safe Store本地存储。
- Indexer:本地缓存索引器,负责处理和本地缓存相关的查询、写入操作
- Thread Safe Store:线程安全的本地存储,负责在Informer内部维护一份对象缓存,这样控制器的查询操作,就不用到APIServer查询,直接在本地查询即可
- 提高了查询效率,而且缓解了 APIServer 的压力
- Handler:事件处理器,负责处理资源的不同Event,每种Event都应该有一个对应的Handler,所以应该有很多类型的Resource Handler。比如 Create Handler、Update Handler、Delete Handler…
- WorkQueue:工作队列,负责存储所有待处理对象的key(namespaceName)
- Process NextWorkItem:负责不断从WorkQueue取出key,交给某个Worker处理
- Worker:资源处理工作器,负责真正的对象调谐工作。一般会启动多个Worker,每个Worker都是一个goroutine,并行处理WorkQueue中的事件
- Informer是一个概念,SharedInformer是一个具体实现
- Controller的整个处理流程如下(以Deployment Controller为例):
- Controller 启动时,会通过Lister 先List一下所有Deployment资源,存入Thread Safe Store本地缓存
- 然后 SharedInformer 通过长连接建立 Deployment资源的 watch 连接
- 当发生Deployment资源变化时,APIServer会把事件及对应的Deployment资源对象,发送给Deployment控制器的SharedInformer
- 首先会使用 Reflector将其反序列化为go struct,然后存入Delta FIFO Queue
- Informer 组件会 从Delta FIFO Queue中取出对象,通过IndexerThread Safe Store本地缓存,并通过Handler将对象的key放入WorkQueue
- Process NextWorkItem会从WorkQueue中取出key,找到一个worker对这个deployment资源进行处理
4.3.4.控制器之间协同工作原理
以创建一个Deployment资源为例
- 用户使用kubectl或http请求方式,发起一个创建Deployment资源的请求
- 请求到达APIServer,会经过 认证、鉴权、资源变形、schema校验、其他校验 等一系列处理后,APIServer 将deployment数据写入自己的watch缓存 和 etcd
- Kubernetes 内部的 DeploymentController 早就已经和APIServer建立了Watch长连接
- 当APIServer将数据写入缓存之后,就会把Deployment资源的变化,通知所有关心Deployment资源的watcher,其中就包括DeploymentController
- DeploymentController 根据自己的处理逻辑,编排出对应的 ReplicaSet 资源,向APIServer发送了创建ReplicaSet请求
- 请求到达APIServer,会经过 认证、鉴权、资源变形、schema校验、其他校验 等一系列处理后,APIServer 将ReplicaSet数据写入自己的watch缓存 和 etcd
- Kubernetes 内部的 ReplicaSetController 早就已经和APIServer建立了Watch长连接
- 当APIServer将数据写入缓存之后,就会把ReplicaSet资源的变化,通知所有关心ReplicaSet资源的watcher,其中就包括ReplicaSetController
- ReplicaSetController 根据自己的处理逻辑,编排出对应的 Pod 资源,向APIServer发送了创建Pod请求
- 请求到达APIServer,会经过 认证、鉴权、资源变形、schema校验、其他校验 等一系列处理后,APIServer 将Pod数据写入自己的watch缓存 和 etcd
- Kubernetes 内部的 Scheduler调度器 和 每个Worker Node上的Kubelet 都早就已经和APIServer建立了Watch长连接
- 当APIServer将数据写入缓存之后,就会把Pod资源的变化,通知所有关心Pod资源的watcher,其中就包括 Scheduler调度器.
- 同时,Scheduler调度器还 watch 了所有的node对象
- Scheduler调度器 发现该pod属于新创建,还没有调度,就会经过计算选择一个node,将 node名称 写到 pod的
spec.nodeName
中,即绑定pod与node,并向 APIServer 发起 pod update 请求。 - APIServer一样会更新 watch缓存+etcd,并通知所有关心Pod资源的watcher,其中就包括 Worker Node 上的 kubelet
- 某一个node上的kubelet 发现pod的
spec.nodeName
和自己的名称一致,就知道这个pod被调度到自己身上- 这个node上的kubelet,就会以此调用 CRI、CNI、CSI,启动容器,挂载网络和数据卷
4.4.Scheduler
- 简单了解下,详细内容会有专门的文章讲解
4.5.Kubelet
- Kubelet是每一个Worker Node上都会安装的组件,极为重要,因为该node上所有管理pod都是它拉起来的。比如
- PodWorker:负责管理当前节点上Pod的启停
- OOMWatcher:负责管理当前节点的内存
- GPUManager:负责管理当前节点的GPU
- ProbeManager:负责对当前节点的应用进行健康检查
- ImageGC:负责回收当前节点无用的镜像
- ContainerGC:负责回收当前节点无用的容器
- …
- 对下层,Kubelet通过调用CRI、CNI、CSI 创建容器,挂载网络和数据卷
- 对上层,Kubelet会把node、pod状态等信息,汇报给APIServer
4.6.Kube-Proxy(可选)
- Kube-Proxy 是用来搭建负载均衡的。
- 创建一个Service,关联上相应的Pod,就可以实现负载均衡。
- 实际上这个负载均衡的网络规则配置等,都是Kube-Proxy做的。
4.7.附加组件 add-ons
- 下面是一些附加组件,根据需求安装即可
- 官方文档:https://kubernetes.io/zh-cn/docs/concepts/architecture/
常见问题解答
- 请见:Kubernetes常见问题解答