Kubernetes(k8s)是一款开源的优秀的容器编排调度系统,其自己也是一款分布式应用程序。虽然本系列文章讨论的是互联网架构,可是k8s的一些设计理念很是值得深思和借鉴,本人并不是运维专家,本文尝试从本身看到的一些k8s的架构理念结合本身的理解来分析 k8s在稳定性、简单、可扩展性三个方面作的一些架构设计的考量。node
下面,针对这三方面咱们都会来看一些k8s设计的例子,在看k8s是怎么作的同时咱们能够本身思考一下,若是咱们须要研发的一款产品就是相似于k8s这样的须要高可靠的资源状态管理协调系统,咱们会怎么来设计呢?算法
咱们知道,k8s定义了许多资源(好比Pod、Service、Deployment、ReplicaSet、StatefulSet、Job、CronJob
等),在管理资源的时候咱们使用声明式的配置(JSON、YAML等)来对资源进行增删改查操做。咱们提供的这些配置就是描述咱们但愿这些资源最终达成的一个目标状态,叫作Spec,k8s会对观察资源获得资源的状态,叫作Status,当Spec!=Status的时候,k8s的各类控制管理程序就会起做用,进行各类操做使得资源最终能够达到咱们指望的Spec。这种声明式的管理方式和命令式管理方式相比,虽然没有后者这么直接,可是容错性会很强,后面一节会进一步详细提到这点。并且,这种管理方式很是的简洁,只要用户提供合适的Spec定义便可,并不须要对外暴露几十个几百个不一样的API来实现对资源的各个方面作改变。固然,咱们也能够灵活的对一些重要的动做单独开辟管理API(好比扩容,好比修改镜像),这些API底层作的操做就是修改Spec,底层是统一的。数据库
在以前第一季的系列文章S1E2中,我分享过任务表的设计,其实这里的声明式对象管理就是相似这样的思想,咱们在数据库中保存的是咱们要的结果,而后由不一样的任务Job来进行处理最终实现这样的结果(同时也会保存组件当前的状态到数据库),即便任务执行失败也无妨,后续的任务会继续重试,这种方式是可靠性最高的。编程
K8s使用的是声明式的管理方式,也就是水平触发。另外一种作法是叫作命令式的管理,也就是边缘触发。好比咱们在作支付系统,用户充值100元,提现100元而后又充值100元,对于命令式管理就是三条命令。若是提现请求丢失了,用户帐户的余额就出错了,这确定是不能接受的,命令式管理或边缘触发必定须要配合补偿。而声明式的管理就是告诉系统,用户在进行了三次操做后的余额分别是100、0和100,最终就是100,即便提现请求丢失了,最终用户的余额就是100。设计模式
来看下下图的例子,在网络良好的状况下,边缘触发没任何问题。咱们进行了开、关、开三次操做,最后的状态是0。api
在网络出现问题的时候,丢失了关这个操做,对于边缘触发,最终停留在了2这个错误的状态。对于水平触发没有这个问题,虽然当中有一段时间网络很差,状态错误停留在了1,可是网络恢复后咱们立刻能够感知到当前的状态应该是0,状态又能回到0,最终状态也能回到正确的1。试想一下,若是咱们对咱们的Pod进行扩容缩容,若是每次告知k8s应该增长或减小多少个Pod(的这种命令式方式),最终极可能由于网络问题,Pod的状态不是咱们指望的。更好的作法是告诉k8s咱们但愿的状态,无论如今网络是否有问题,某个管理组件是否有问题,pod是否有问题,最终咱们指望k8s帮咱们调整到咱们指望的状态,宁肯慢也不要错。缓存
咱们知道etcd是基于Raft协议的分布式键值数据库/协调系统,自己推荐使用三、五、7这样奇数节点构成集群实现高可用。对于Master节点,咱们能够在每个节点都部署一个etcd,这样节点上的API Server能够和本地的etcd直接通信,而API Server由于是轻(无)状态的,因此能够在以前使用负载均衡器作代理,无论是Node节点也好仍是客户端也好均可以由负载均衡分发请求到合适的API Server上。对于相似于Job的Controller Manager以及Scheduler,显然不适合多个节点同时运行,因此它们都会采用抢占方式选举Leader,只有Leader能承担工做任务,Follower都处于待机状态。总体结构以下图所示:安全
经过前面的介绍咱们大概知道了k8s的一个设计原则是etcd会处于API Server以后,集群内的各类组件是没法直接和数据库对话的,不只仅由于把数据库直接暴露给各组件会特别混乱,更重要的是谁均可以直接读写etcd会很是不安全,须要统一通过API Server作身份认证和鉴权等安全控制(后面咱们会提到API Server的插件链)。网络
对于k8s集群内的各类资源,k8s的控制管理器和调度器须要感知到各类资源的状态变化(好比建立),而后根据变化事件履行本身的管理职责。考虑到解耦,显然这里有MQ的需求,各类管理组件能够监听各类资源的状态变化事件,不须要相互感知到对方的存在,本身作本身的事情便可。若是k8s还依赖一些消息中间件实现这个功能,那么总体的复杂度会上升,并且还须要对消息中间件进行一些安全方面的定制。架构
K8s给出的实现方式是仍然使用API Server来充当简单的消息总线的角色,全部的组件经过watch机制创建HTTP长连接来随时获悉本身感兴趣的资源的变化事件,完成本身的功能后仍是调用API Server来写入咱们组件新的Spec,这份Spec会被其它管理程序感知到而且进行处理。Watch的机制是推的机制,能够实时对变化进行处理,可是咱们知道考虑到网络等各类因素,事件可能丢失,组件可能重启,这个时候咱们须要推拉结合进行补偿,所以API Server还提供了List接口,用于在watch出现错误的时候或是组件重启的时候同步一次最新状态。经过推拉结合的list-watch机制知足了时效性需求和可靠性需求。
集群资源变动操做:
能够看到基于list-watch的API Server实现了简单可靠的消息总线的功能,基于资源消息的事件链,解耦了各组件之间的耦合,配合以前提到的基于声明式的对象管理又确保了管理稳定性。从层次上来讲,master的组件都是控制面的组件,用来控制管理集群的状态,node的组件是执行面的组件,kubelet是一个无脑执行者的角色,它们的交流桥梁是API Server的各类事件,kubelet是没法感知到控制器的存在的。
以下图所示,API Server实现了基于插件+过滤器链的方式(好比咱们熟知的Spring MVC的拦截器链)来实现资源管理操做的前置校验(身份认证、受权、准入等等)。
若是是删除资源,还会有额外的一些环节:
对于复杂的流程式的操做,采用职责链+处理链+插件的方式来实现是很常见的作法。你可能会说这个API Server的设计整体上就不简单,怎么有这么多环节,其实这才是最简单的作法,每个环节都有独立的插件来运做(插件能够独立更新升级,也能够根据需求动态插拔配置),每个插件只是作本身应该作的事情,若是没有这样的设计,恐怕会出现1万行代码的一个大方法。
常见的predicate算法有:
常见的priority算法有:
好比咱们在作相似路由系统这种业务系统的时候能够借鉴这种设计模式。简单一词在于每个小组件简单,它们能够组合起来构成复杂的规则系统,这种设计比把全部逻辑堆在一块儿简单的多。
K8s的设计理念是相似Linux的分层架构:
除了k8s大量内部组件的实现使用了插件的架构,k8s在总体设计上就把核心和外部的一些资源和服务抽象为了统一的接口,能够插件方式插入具体的实现,以下图所示:
此外,因为在kubernetes中一切皆资源,k8s 1.7以后,提供了CRD(CustomResourceDefinitions)的自定义资源二次开发能力来扩展k8s API,经过此扩展,能够向k8s API中增长新类型,会比修改k8s的源代码或者是建立自定义的API server来的更加的简洁和容易,而且不会随着k8s内核版本的升级,而出现须要代码从新合并的须要,以及兼容性方面的问题。这一功能特性的提供大大提高了k8s的扩展能力。
K8s在存储方面的解耦设计特别值得一提。以下图所示,咱们来看一下k8s在存储这块的解耦设计:
K8s中除了存储抽象的V、PV、PVC、SC,还有其它的一些组件也有相似层次的抽象以及动态绑定的理念。
咱们在使用OO语言进行编程的时候,很天然知道咱们须要先定义类,而后再实例化类来建立对象,若是类特别复杂(有不一样的实现)的话,咱们可能会使用工厂模式(或反射,外层传入目标类型名称)来建立对象。能够和k8s存储抽象比较一下,是否是这个意思,这其实就是一种解耦的方式,在架构设计中,甚至表结构设计中,咱们彻底能够引入类和实例的概念。好比工做流系统的工做流能够认为是一个类模板,每一次发起的工做流就是这个工做流的实例。
好了,本文大概窥探了一下k8s的架构,不知道你是否感觉到了k8s的精良设计,对内考虑了高可用以及高可靠,对外考虑到了高可扩展性。几乎任何操做都容许失败,最终实现一致的状态,几乎任何组件都容许扩展和替换,让用户实现本身的定制需求。
若是你的业务系统也是一套复杂的资源协调系统(k8s抽象的是运维相关的资源,咱们的业务系统能够抽象的是其它资源),那么k8s的设计理念有至关多的点能够借鉴。举一个例子,咱们在作一套很复杂的流程引擎,咱们就能够考虑: