1.2 Kubernetes 模型设计
Kubernetes 创建初期,其本身在业界地位并不占优,前有长期占有主流市场的Mesos和基于Mesos 的DCOS 围追堵截,后有Docker Swarm 依托自己的容器事实标准异军突起,反倒是Kubernetes 只有谷歌的品牌。Kubernetes 为什么能最后胜出,成为容器云的实施标准呢?最根本原因就是其对管理范畴的所有对象进行了抽象,通过模型标准化将容器云平台各个维度的问题解决得非常完美。
1.2.1 对象的通用设计原则
了解Kubernetes 的第一步,就是了解Kubernetes 如何抽象和定义这个世界。Kubernetes在设计对象时遵循如下原则:
(1)Kubernetes 将业务模型化,这些对象的操作都以API 的形式发布出来,因此其所有API 设计都是声明式的。
(2)控制器的行为应该是可重入和幂等的,通过幂等的控制器使得系统一致朝用户期望状态努力,且结果稳定。
(3)所有对象应该是互补和可组合的,而不是简单的封装。通过组合关系构建的系统,通常能保持很好的高内聚、松耦合特性。
(4)API 操作复杂度应该与对象数量成线性或接近线性比例,这制约了系统的规模上限,如果操作复杂度和对象成指数比例,那么随着对象的增加,操作的复杂度会迅速上升到用户无法接受的程度。
(5)API 对象状态不能依赖于网络连接状态。众所周知,在分布式环境下,网络连接断开是经常发生的事情,如果希望API 对象的状态能应对网络的不稳定,那么API 对象的状态就不能依赖于网络连接状态。
(6)尽量避免让操作机制依赖于全局状态,因为在分布式系统中要保证全局状态的同步是非常困难的。
1.2.2 模型设计
1.2.2.1 TypeMeta
TypeMeta 是Kubernetes 对象的最基本定义,它通过引入GKV(Group,Kind,Version)模型定义了一个对象的类型。下面分别介绍一下Group、Kind、Version。
(1)Group
Kubernetes 定义了非常多的对象,如何将这些对象进行归类是一门学问,将对象依据其功能范围归入不同的分组,比如把支撑最基本功能的对象归入core 组,把与应用部署有关的对象归入apps 组,会使这些对象的可维护性和可理解性更高。
(2)Kind
定义一个对象的基本类型,比如Node、Pod、Deployment 等。
(3)Version
社区每个季度会推出一个Kubernetes 版本,随着Kubernetes 版本的演进,对象从创建之初到能够完全生产化就绪的版本是不断变化的。与软件版本类似,通常社区提出一个模型定义以后,随着该对象不断成熟,其版本可能会从v1alpha1 到v1alpha2,或者到v1beta1,最终变成生产就绪版本v1。
Kubernetes 通过Version 属性来控制版本。当不同版本的对象定义发生变更时,有可能会涉及数据迁移,Kubernetes API Server 允许通过Conversion 方法转换不同版本的对象属性。这是一种自动数据迁移的机制,当集群版本升级以后,已经创建的老版本对象会被自动转换为新版本。
这里所说的版本是对外版本(External Version),即用户通过API 能看到的版本。事实上资源定义都有对内版本(Internal Version),在Kubernetes API Server 处先将对外版本转换成对内版本,再进行持久化。
1.2.2.2 Metadata
TypeMeta 定义了 “我是什么”,Metadata 定义了 “我是谁”。为方便管理,Kubernetes将不同用户或不同业务的对象用不同的Namespace 进行隔离。Metadata 中有两个最重要的属性——Namespace 和Name,分别定义了对象的Namespace 归属及名字,这两个属性唯一定义了某个对象实例。
我们知道,所有对象都会以API 的形式发布供用户访问,Typemeta、Namespace 和Name 唯一确定了该对象所在的 API 访问路径,该路径也会被自动生成并保存在对象Metadata 属性的selfLink 中,如下所示:
此外,Metadata 中还有Label、Annotation、Finalizer 和ResourceVersion 四个字段,可用作资源对象的配置管理。
1.Label
在传统的面向对象设计系统中,对象组合的方法通常是内嵌或引用,即将对象A 内嵌到对象B 中,或者将对象A 的ID 内嵌到对象B 中。这种设计的弊端是各对象之间的关系是固化的,一个对象可能对多个其他对象发生关联,如果该对象发生变更,系统需要遍历所有其关联对象并做修改。
Kubernetes 采用了更巧妙的方式管理对象和对象的松耦合关系,其依赖的就是Label和Selector。Label,顾名思义就是给对象打标签,一个对象可以有任意对儿标签,其存在形式是键值对儿。不像名字和UID,标签不需要独一无二,多个对象可以有同一个标签,每个对象可以有多组标签。
Label 定义了这些对象的可识别属性,Kubernetes API 支持以Label 作为过滤条件查询对象。因此Label 通常用最简单的形式定义:
其他对象只需要定义Label Selector 就可以按条件查询出其需要关联的对象。Label 的查询可以基于等式,如app=web 或app!=db,或基于集合,如app in (web, db)或app notin (web, db),可以只查询Label 键,如app。Label 对多个条件查询只支持 “与” 操作,如app=web, tier=front。
2.Annotation
Annotation 与Label 一样用键值对儿来定义,但其功能与Label 不一样,所以在用法上也有不同的原则,API 也不支持只用Annotation 做条件过滤。虽然Kubernetes 把对象做了很好的抽象,在实际运用中特别是在生产化落地过程中,总是需要保存一些在对象内置属性中无法保存的信息,Annotation 就是用于满足这类需求的,事实上Annotation 是对象的属性扩展。社区在开发新功能(需要对象发生变更)之前,往往会先把需要变更的属性放在Annotation 中,当功能经历完实验阶段再将其移至正式属性中。
Annotation 作为属性扩展,更多是面向系统管理员和开发人员的,因此Annotation 需要像其他属性一样做合理归类。与Java 开发中的包名设计类似,通常需要将系统以不同的功能规划为不同的Annotation Namespace,其键应以如下形式存在:<namespace>/key: value,比如一个最常用的场景,为Pod 标记Annotation 以告知Prometheus 为其抓取系统指标,具体代码如下:
3.Finalizer
如果只看社区实现,那么该属性毫无存在感,因为在社区代码中,很少有对Finalizer的操作。但在企业化落地过程中,它是一个十分重要、值得重点强调的属性。因为Kubernetes不是一个独立存在的系统,它最终会跟企业资源和系统整合,这意味着Kubernetes 会操作这些集群的外部资源或系统。试想一个场景:用户创建了一个Kubernetes 对象,假设对应的控制器需要从外部系统获取资源,当用户删除该对象时,控制器接收删除事件后,会尝试释放该资源。可是如果此时外部系统无法连通,并且同时控制器发生了重启,会有何后果?答案是该对象永远泄露了。
Finalizer 本质上是一个资源锁,Kubernetes 在接收某对象的删除请求时,会检查Finalizer是否为空,如果为空则只对其做逻辑删除,即只会更新对象中的metadata.deletionTimestamp字段。具有Finalizer 的对象,不会立刻删除,需等到Finalizer 列表中所有字段被删除后,也就是只有该对象相关的所有外部资源已被删除,这个对象才会被最终删除。
因此,如果控制器需要操作集群的外部资源,则一定要在操作外部资源之前为对象添加Finalizer,确保资源不会因对象被删除而泄露。同时控制器需要监听对象的更新时间,当对象的deletionTimestamp 不为空时,处理对象删除逻辑,回收外部资源,并清空自己之前添加的Finalizer。
4.ResourceVersion
通常在多线程操作相同资源时,为保证实物的一致性,需要在对象进行访问时加锁,以确保在一个线程访问该对象时,其他线程无法修改该对象。排他锁的存在可以确保某一对象在同一时刻只有一个线程在修改,但其排他的特性会让其他线程等待锁,使得系统的整体效率显著降低。
ResourceVersion 可以被看作一种乐观锁, 每个对象在任意时刻都有其ResourceVersion,当Kubernetes 对象被客户端读取以后,ResourceVersion 信息也被一并读取。客户端更改对象并回写API Server 时,ResourceVersion 会增加,同时API Server 需要确保回写的版本比服务器端的当前版本高,在回写成功后服务器端的版本会更新为新的ResourceVersion 。 因此, 当两个线程同时访问某对象时, 假设它们获取的对象ResourceVersion 为1。紧接着第一个线程修改了对象,资源版本会变为2,回写至API Server以后,该对象服务器端的ResourceVersion 会被更新为2。此时如果第二个线程对该对象在1 的版本基础上做了更改,回写API Server 时,所带的新的版本信息也为2,那么API Server校验会发现第二个线程新写入的对象ResourceVersion 与服务器端的ResourceVersion 相冲突,即写入失败,需要第二个线程读取最新版本,以便重新更新。
此机制确保了分布式系统中的任意多线程能够无锁并发访问对象,极大地提升了系统的整体效率。
1.2.2.3 Spec 和Status
Spec 和Status 才是对象的核心,Spec 是用户的期望状态,由创建对象的用户端来定义。Status 是对象的实际状态,由对应的控制器收集实际状态并更新。与 TypeMeta 和Metadata 等通用属性不同,Spec 和Status 是每个对象独有的,后续的章节会通过介绍一些核心对象来帮助读者深入理解这两个概念。
为方便对Kubernetes 对象的理解,图1-3 展示了按照业务目的归类的常用Kubernetes对象及其分组。Kubernetes 对象设计完全遵循互补的原则。鼓励API 对象尽量实现面向对象设计时的要求,即 “高内聚,松耦合”,对业务相关的概念有一个合适的分解,提高分解出来的对象的可重用性。高层API 对象设计一定是从业务出发的,低层API 对象能够被高层API 对象所使用,从而实现减少冗余、提高重用性的目的。
图1-3 常用Kubernetes 对象及其分组
1.2.3 核心对象概览
Kubernetes 的对象设计避免了简单封装和内部隐藏机制。简单封装是指,A 对象封装了B 对象的定义,实际没有提供新的功能,反而增加了对所封装API 的依赖性。内部隐藏的机制也非常不利于系统维护的设计方式。如图 1-4 所示,StatefulSet、ReplicaSet 和DaemonSet,是三种Pod 的集合,Kubernetes 用不同的API 对象来定义它们,而不是将它们封装在同一个资源对象中,内部再通过特殊的隐藏算法来区分这个资源对象是有状态的、无状态的,还是节点服务。Pod 是 Kubernetes 应用程序的基本执行单元,即它是Kubernetes 对象模型中创建或部署的最小和最简单的单元。多数核心对象都是为Pod 对象服务的,但是它们都是从Pod 对象中剥离出来的,有自己的API 定义。Secret、ConfigMap和PVC 是不同的资源对象定义,都可以作为存储卷在Pod 中使用。而在Pod 中使用时,只需要指定该对象的名称即可,无须将其具体信息在Pod 资源对象中进行扩展。
图1-4 核心对象间的关系图
接下来,我们介绍一下Kubernetes 中与Pod 相关的核心对象。
1.Namespace
Namespace 是Kubernetes 进行归类的对象,当一个集群有多个用户或一个用户有多个应用需要管理时,需要将所有被管理的对象进行隔离。Kubernetes 引入了Namespace 对象,类似文件目录,不同对象被划分到不同的Namespace 后,可以通过权限控制来限制用户以何种权限访问哪些Namespace 的哪些对象,进而构建一个多租户、彼此隔离的通用集群。
2.Pod
容器云平台需要解决的最核心的问题是应用运行,Kubernetes 将容器化应用运行的实体抽象为Pod,Pod 类似豆荚,它是一个或者多个容器镜像的组合。当应用启动以后,每一个容器镜像对应一组进程,而同一个 Pod 的所有容器中的进程默认公用同一网络Namespace,并且共用同一网络标识。Pod 具有基本的自恢复能力,当某个副本出现问题时,它会按照预定策略被重启。
当然,应用运行通常需要配置文件,这些配置文件又有可以明文读写的配置,也包含需要加密和严格权限控制的密码证书等配置, Kubernetes 为这些配置分别定义了Configmap 和Secret。Configmap 和Secret 与PersistVolumeClaim 类似,都可以作为卷加载给运行的Pod,Pod 中运行的进程可以像访问本地文件一样访问它们。Configmap 和Secret没有本质区别,Secret 只是将内容进行base64 编码,我们知道base64 编码是一种对称加密算法,可以轻松解密,事实上没有太多安全性可言。但Kubneretes 支持Secret 在持久化时的加密存储,这样保存在硬盘的Secret 数据是无法解密的。其次,Kubernetes 可以通过权限严格控制能够访问Secret 的用户,以保证密码和证书信息的安全。
Pod 除了包含用户希望运行的容器镜像和配置文件,还允许用户定义其运行所需的资源,用户创建Pod 以后,Kubernetes 会为其选择一个最佳节点运行。计算节点被抽象成Node 对象,节点数量和每个节点的资源汇总起来就是整个集群能提供的算力。每个计算节点负责汇报自己的心跳信息,并上报节点的资源总量和可用资源。
3.ServiceAccount
Pod 中运行的进程有时需要与Kubernetes API 通信,在启用了安全配置的集群后,Pod一定要以某种身份与 Kubernetes 通信, 这个身份就是系统账户(ServiceAccount)。Kubernetes 会默认为每个 Namespace 创建一个 default ServiceAccount, 并且为每个ServiceAccount 生成一个JWT Token,这个Token 保存在Secret 中。用户可以在其Pod 定义中指定ServiceAccount(默认为default),其对应的Token 会被挂载在Pod 中,Pod 中的进程可以通过该Token 与Kubernetes 进行通信。
4.ReplicaSet
Pod 只是单个应用实例的抽象,要构建高可用应用,通常需要构建多个同样的副本,提供同一个服务。Kubernetes 为此抽象出副本集ReplicaSet,其允许用户定义Pod 的副本数,每一个Pod 都会被当作一个无状态的成员进行管理,Kubernetes 保证总是有用户期望的数量的Pod 正常运行。当某个副本宕机以后,控制器将会创建一个新的副本。当因业务负载发生变更而需要调整扩缩容时,可以方便地调整副本数量。
5.Deployment
对于无状态在线应用,Kubernetes 提供了更高级的版本变更控制。版本变更是一个日常频繁发生的关键操作,如何在不中断业务的前提下更新版本,一直是业界努力解决的问题。Deployment 就是一个用来描述发布过程的对象,其实现机制是,当某个应用有新版本发布时,Deployment 会同时操作两个版本的ReplicaSet。其内置多种滚动升级策略,会按照既定策略降低老版本的Pod 数量,同时创建新版本的Pod,并且总是保证正在运行的Pod总数与用户期望的副本数一致,并依次将该Deployment 中的所有副本都更新至新版本。图1-5 展示了Deployment 的滚动升级策略。
图1-5 Deployment 的滚动升级策略
由于Deployment 会维护ReplicaSet,ReplicaSet 会创建Pod,所以通过Deployment 维护无状态的应用是第一选择,它可以满足诸多需求,缩短应用上线的时间,在不造成停机的情况下创建弹性部署,能够使用户更快或更频繁地发布应用和功能。使用Deployment(而不使用单个Pod)部署应用程序的优势如下:
(1)能够创建并保证目标数量的Pod 在运行状态,且使应用的服务能力在遇到Pod宕机时也不会降级。
(2)可按既定策略滚动升级,同时支持升级暂停、恢复和回滚。选择滚动升级策略非常灵活,正确的策略对于交付弹性应用程序和基础架构都是至关重要的。
(3)可以便利地扩容和缩容,以应对负载的频繁变化。
6.Service 和Ingress
即使在传统平台中,为支持应用的高可用,也需要在应用实例之上构建负载均衡。Service 和Ingress 就是描述负载均衡配置的对象,它允许用户定义发布服务的协议和端口,并定义Selector 选择后端服务的Pod。Selector 本身是一个Label 过滤器,它会选择所有Label 与该Selector 匹配的Pod 作为目标。Kubernetes 会为Service 和其选择出来的Pod 创建一个关联对象,Endpoint 里面记录了所有Pod 的IP 地址及就绪状态,这些信息会被相应组件作为期望状态进行负载均衡配置。Ingress 在服务的基础上定义API 网关的对象。通过Ingress,用户可以定义七层转发规则、网关证书等高级路由功能。负载均衡和请求路由是容器云平台至关重要的功能,Service 和Ingress 会分别在第5 章和第6 章中进行详述。
7.PersistentVolume 和PersistentVolumeClaim
PersistentVolume(PV)是集群中的一块存储卷,可以由管理员手动设置,或当用户创建PersistentVolumeClaim(PVC)时根据StorageClass 动态设置。PV 和PVC 与Pod 生命周期无关。也就是说,当Pod 中的容器重新启动、Pod 重新调度或者删除时,PV 和PVC不会受到影响,Pod 存储于PV 里的数据得以保留。对于不同的使用场景,用户通常需要不同属性(例如性能、访问模式等)的PV。因此,集群一般需要提供各种类型的PV,由StorageClass 来区分。 一般集群环境都设置了默认的 StorageClass 。 如果在PersistentVolumeClaim 中未指定StorageClass,则使用集群的默认StorageClass。
8.CustomResourceDefinition
CustomResourceDefinition 是指自定义资源定义,简称CRD,是Kubernetes 1.7 中引入的一项强大功能,它允许用户将自己的自定义对象添加到Kubernetes 集群中。当创建新CRD 的定义时,API Server 将为指定的每个版本创建一个新的RESTful 资源路径。当集群中成功地创建了CRD,就可以像Kubernetes 原生的资源一样使用它,利用Kubernetes 的所有功能,例如其CLI、安全性、API 服务、RBAC 等。CRD 的定义是在集群范围内的,CRD 的资源对象的作用域可以是命名空间(Namespaced)或者集群范围(Cluster-wide)。与现有的内置对象一样,删除Namespace 也会删除该Namespace中所有自定义的对象,但不会删除CRD 的定义。Kubernetes 还提供一系列Codegen 工具(deepcopy-gen、client-gen、lister-gen、informer-gen 等),能够自动生成该CRD 资源的Golang 版本的Clientset、Lister 及Informer,这为该资源编写控制器提供了很大便利。
CRD 就像数据库的开放式表结构,允许用户自定义Schema。有了这种开放式设计,用户可以基于CRD 定义一切需要的模型,满足不同业务的需求。社区鼓励基于CRD 的业务抽象,众多主流的扩展应用都是基于CRD 构建的,比如Istio、Knative。甚至基于CRD推出了Operator Mode 和Operator SDK,可以以极低的开发成本定义新对象,并构建新对象的控制器。
1.2.4 控制器模式
声明式系统的工作原理是什么?当用户定义对象的期望状态时,Kubernetes 通过何种机制确保实际状态与期望状态最终保持一致?在定义了如此多的对象后,这些对象又是如何联动起来,完成一个个业务流的呢?秘密就是控制器模式,Kubernetes 定义了一系列的控制器,事实上几乎所有的Kubernetes 对象都被一个或数个控制器所监听,当对象发生变化时,控制器会捕获对象变化并完成配置操作。
Kubernetes 的功能组件会在后面章节中展开,但本节深入理解控制器模式有助于理解Kubernetes 的运作机制。API Server 是Kubernetes 的大脑,保存了所有对象及其状态。开源项目client-go 对控制器的编写提供了完备的自动化支持,任何Kubernetes 对象都可以由client-go 创建供控制器使用的Informer()和Lister()接口。如图1-6 所示,控制器的工作流程就是围绕着Informer()和Lister()的。
Informer()用于接收资源对象的变化的Event,针对Add、Update 和Delete 的事件,可以注册相应的EventHandler。在EventHandler 内,根据传入的object 调用controller.KeyFunc计算出字符串key,并把它加入控制器的队列中。
图1-6 控制器的工作流程
Lister()是给控制器提供主动查询资源对象的接口,我们根据labels.Selector 来指定筛选条件。
控制器模式是一个标准的生产者-消费者模式。一方面,控制器在启动后,Informer会监听其所关注的对象变化。一旦对象发生了创建、更新和删除等事件,这些事件会由核心组件API Server 推送给控制器。控制器会将对象保存在本地缓存中,并将对象的主键推送至消息队列,此为生产者。
另一方面,控制器会启动多个工作子线程(Worker),从队列中依次获取对象主键,并从缓存中读取完整状态,按照期望状态完成配置更改,并将最终状态回写至API Server,此为消费者。
Kubernetes 就是基于此模式保证了整个系统的最终一致性。
Kubernetes 运行一组控制器,以使资源的当前状态与所需状态保持匹配。对于基于事件的体系结构,控制器利用事件去触发相应的自定义代码,这部分都是由SharedInformer完成的。例如,创建Deployment 的控制器的核心代码如下:
具体地,如图1-7 所示,SharedInformer 有Reflector、Informer、Indexer 和Thread Safe Store 四个组件。
图1-7 Informer 的内部机制
Reflector 用于监听特定的Kubernetes API 资源对象,可以是Kubernetes 内建的或者是自定义的资源。其具体实现是通过ListAndWatch 的方法进行的。首先,Reflector 将资源版本号设置为0,使用List 操作获得指定资源对象,这可能会导致本地的缓存相对于etcd里面的内容存在延迟。然后,Reflector 通过Watch 操作监听到API Server 处资源对象的版本号变化,并将最新的数据放入Delta FIFO 队列中,使得本地的缓存数据与etcd 的数据保持一致。如果resyncPeriod 不为零,那么Reflector 会以resyncPeriod 为周期定期执行Delta FIFO 的Resync 函数,这样就可以使Informer 定期处理所有的对象。
Informer 的内部机制是从Delta FIFO 队列中弹出对象,一方面将对象存入本地存储以供检索,另一方面触发事件以调用资源事件回调函数。控制器后续的典型模式是获取资源对象的key,并将该key 排入工作队列以进一步处理。Indexer 提供对象的索引功能。
Indexer 可以根据多个索引函数维护索引。Indexer 使用线程安全的数据存储来存储对象及其键。在Store 中定义了一个名为MetaNamespaceKeyFunc 的默认函数,该函数生成对象的键的格式是<namespace>/<name>的组合。
1.2.5 控制器的协同工作原理
单个Kubernetes 资源对象的变更,会触发多个控制器对该资源对象的变更进行响应,继而还能引发其相关的其他对象发生变更,从而触发其他对象控制器的配置逻辑,这一模式使得整个系统成为声明式系统。图1-8 简要描述了用户创建一个Deployment 对象时各个控制器是如何协同工作的。
图1-8 协同工作流程示例
除API Server 和etcd 外,所有Kubernetes 组件,不论其名称是Scheduler、Controller Manager、或是kubelet,其本质都是一致的,都可以被称为控制器,因为这些组件中都有一个控制循环。它们监听API Server 中的对象变更,在自己关注的对象发生变更后完成既定的控制逻辑,再将控制逻辑执行完成后的结果更新回API Server,并持久化到etcd 中。
API Server 作为集群的API 网关,接收所有来自用户的请求。用户创建Deployment之后,该请求被发送至API Server,经过认证、鉴权和准入三个环节,该Deployment 对象被保存至etcd。
Controller Manager 中的Deployment Controller 监听API Server 中所有Deployment 的变更事件,此时其捕获了 Deployment 的创建事件,并开始执行控制逻辑。Deployment Controller 读取Deployment 对象的Selector 定义,通过该属性过滤当前Namespace 中的所有ReplicaSet 对象,并判断是否有ReplicaSet 对象的OwnerReference 属性为此Deployment。由于此Deployment 刚刚创建,所以没有满足此查询条件的ReplicaSet,于是Deployment Controller 会读取 Deployment 中定义的 podTemplate, 将其做哈希计算, 得到值为[pod-template-hash],并依照如下约定创建新的ReplicaSet:
● 新的ReplicaSet 的命名格式为[deployment-name]-[pod-template-hash]。
● 为ReplicaSet 添加label,此Label 为pod-template-hash: [pod-template-hash]。
● 将Deployment 的值赋给ReplicaSet 的OwnerReference。
Deployment Controller 将新的ReplicaSet 创建请求发送至API Server,API Server 经过认证授权和准入步骤,将该对象保存至etcd。
ReplicaSet Controller 监听API Server 中所有ReplicaSet 对象的变更,新对象的创建令其唤醒并开始执行控制逻辑。ReplicaSet Controller 读取ReplicaSet 对象的Selector 定义,并通过该属性过滤当前 Namespace 中的所有的 Pod 对象,并判断是否有 Pod 对象的OwnerReference 为该ReplicaSet。由于此ReplicaSet 刚刚创建,所以没有满足此查询条件的Pod,于是ReplicaSet 会按照如下约定创建Pod:
● 读取Replicas 定义,Replicas 的数量代表需要创建Pod 的数量。
● 以 ReplicaSet 名作为 Pod 的GenerateName,该属性会被当作 Pod 名的前缀,Kubernetes 在此基础上加一个随机字符串作为Pod 名。
● 该ReplicaSet 可以作为Pod 的OwnerReference 来使用。
ReplicaSet Controller 将新建Pod 的请求发送至API Server,API Server 将Pod 悉数保存。此时调度器被唤醒,其监听API Server 中所有nodeName 为空的Pod,即未经过调度的Pod。经过一系列的调度算法,不满足Pod 需求的节点被过滤,满足Pod 需求的节点按照空闲资源、端口占用情况、实际资源利用率等信息被排序,评分最高的节点名被更新至nodeName 属性中,该属性经API Server 保存至etcd。
当运行在Pod 上的被调度节点的kubelet 监听到有归属于自己节点的新Pod 时,开始加载Pod 清单,下载Pod 所需的配置信息,调用容器运行时接口启动容器,调用容器网络接口加载网络,调用容器存储接口挂载存储,并完成Pod 的启动。
Kubernetes 就是依靠这样的联动机制,通过分散的业务控制逻辑满足用户的需求。从用户的角度来看,只是发送了一个Deployment 创建请求,但事实上,为满足该需求,可能会涉及数个甚至更多Kubernetes 组件。此架构模式的优势是每个组件各司其职,巧妙而灵活,代码易维护,缺点是运维复杂度相对较高,在整个业务流中有任何组件出现故障都会使Kubernetes 不可用。