做者 | 张振 阿里巴巴高级技术专家nginx
咱们知道,Kubernetes 的资源对象组成:主要包括了 Spec、Status 两部分。其中 Spec 部分用来描述指望的状态,Status 部分用来描述观测到的状态。json
今天咱们将为你们介绍 K8s 的另一个部分,即元数据部分。该部分主要包括了用来识别资源的标签:Labels, 用来描述资源的注解;Annotations, 用来描述多个资源之间相互关系的 OwnerReference。这些元数据在 K8s 运行中有很是重要的做用。缓存
第一个元数据,也是最重要的一个元数据——资源标签。资源标签是一种具备标识型的 Key:Value 元数据,以下图所示,展现了几个常见的标签。微信
前三个标签都打在了 Pod 对象上,分别标识了对应的应用环境、发布的成熟度和应用的版本。从应用标签的例子能够看到,标签的名字包括了一个域名的前缀,用来描述打标签的系统和工具, 最后一个标签打在 Node 对象上,还在域名前增长了版本的标识 beta 字符串。并发
标签主要用来筛选资源和组合资源,可使用相似于 SQL 查询 select,来根据 Label 查询相关的资源。 app
最多见的 Selector 就是相等型 Selector。如今举一个简单的例子:less
假设系统中有四个 Pod,每一个 Pod 都有标识系统层级和环境的标签,咱们经过 Tie:front 这个标签,能够匹配左边栏的 Pod,相等型 Selector 还能够包括多个相等条件,多个相等条件之间是逻辑”与“的关系。运维
在刚才的例子中,经过 Tie=front,Env=dev 的 Selector,咱们能够筛选出全部 Tie=front,并且 Env=dev 的 Pod,也就是下图中左上角的 Pod。另一种 Selector 是集合型 Selector,在例子中,Selector 筛选全部环境是 test 或者 gray 的 Pod。异步
除了 in 的集合操做外,还有 notin 集合操做,好比 tie notin(front,back),将会筛选全部 tie 不是 front 且不是 back 的 Pod。另外,也能够根据是否存在某 lable 的筛选,如:Selector release,筛选全部带 release 标签的 Pod。集合型和相等型的 Selector,也能够用“,”来链接,一样的标识逻辑”与“的关系。分布式
另一种重要的元数据是:annotations。通常是系统或者工具用来存储资源的非标示性信息,能够用来扩展资源的 spec/status 的描述,这里给了几个 annotations 的例子:
第一个例子,存储了阿里云负载器的证书 ID,咱们能够看到 annotations 同样能够拥有域名的前缀,标注中也能够包含版本信息。第二个 annotation存储了 nginx 接入层的配置信息,咱们能够看到 annotations 中包括“,”这样没法出如今 label 中的特殊字符。第三个 annotations 通常能够在 kubectl apply 命令行操做后的资源中看到, annotation 值是一个结构化的数据,其实是一个 json 串,标记了上一次 kubectl 操做的资源的 json 的描述。
最后一个元数据叫作 Ownereference。所谓全部者,通常就是指集合类的资源,好比说 Pod 集合,就有 replicaset、statefulset,这个将在后序的课程中讲到。
集合类资源的控制器会建立对应的归属资源。好比:replicaset 控制器在操做中会建立 Pod,被建立 Pod 的 Ownereference 就指向了建立 Pod 的 replicaset,Ownereference 使得用户能够方便地查找一个建立资源的对象,另外,还能够用来实现级联删除的效果。** **
这里经过 kubectl 命令去链接咱们 ACK 中已经建立好的一个 K8s 集群,而后来展现一下怎么查看和修改 K8s 对象中的元数据,主要就是 Pod 的一个标签、注解,还有对应的 Ownerference。
首先咱们看一下集群里如今的配置状况:
而后咱们这里其实也可以看到有一个 kubectl apply 的时候,kubectl 工具增长了一个 annotation,这也是一个 json 串。
控制型模式最核心的就是控制循环的概念。在控制循环中包括了控制器、被控制的系统,以及可以观测系统的传感器,三个逻辑组件。
固然这些组件都是逻辑的,外界经过修改资源 spec 来控制资源,控制器比较资源 spec 和 status,从而计算一个 diff,diff 最后会用来决定执行对系统进行什么样的控制操做,控制操做会使得系统产生新的输出,并被传感器以资源 status 形式上报,控制器的各个组件将都会是独立自主地运行,不断使系统向 spec 表示终态趋近。
控制循环中逻辑的传感器主要由 Reflector、Informer、Indexer 三个组件构成。
Reflector 经过 List 和 Watch K8s server 来获取资源的数据。List 用来在 Controller 重启以及 Watch 中断的状况下,进行系统资源的全量更新;而 Watch 则在屡次 List 之间进行增量的资源更新;Reflector 在获取新的资源数据后,会在 Delta 队列中塞入一个包括资源对象信息自己以及资源对象事件类型的 Delta 记录,Delta 队列中能够保证同一个对象在队列中仅有一条记录,从而避免 Reflector 从新 List 和 Watch 的时候产生重复的记录。
Informer 组件不断地从 Delta 队列中弹出 delta 记录,而后把资源对象交给 indexer,让 indexer 把资源记录在一个缓存中,缓存在默认设置下是用资源的命名空间来作索引的,而且能够被 Controller Manager 或多个 Controller 所共享。以后,再把这个事件交给事件的回调函数
控制循环中的控制器组件主要由事件处理函数以及 worker 组成,事件处理函数之间会相互关注资源的新增、更新、删除的事件,并根据控制器的逻辑去决定是否须要处理。对须要处理的事件,会把事件关联资源的命名空间以及名字塞入一个工做队列中,而且由后续的 worker 池中的一个 Worker 来处理,工做队列会对存储的对象进行去重,从而避免多个 Woker 处理同一个资源的状况。
Worker 在处理资源对象时,通常须要用资源的名字来从新得到最新的资源数据,用来建立或者更新资源对象,或者调用其余的外部服务,Worker 若是处理失败的时候,通常状况下会把资源的名字从新加入到工做队列中,从而方便以后进行重试。
这里举一个简单的例子来讲明一下控制循环的工做原理。
ReplicaSet 是一个用来描述无状态应用的扩缩容行为的资源, ReplicaSet controler 经过监听 ReplicaSet 资源来维持应用但愿的状态数量,ReplicaSet 中经过 selector 来匹配所关联的 Pod,在这里考虑 ReplicaSet rsA 的,replicas 从 2 被改到 3 的场景。
首先,Reflector 会 watch 到 ReplicaSet 和 Pod 两种资源的变化,为何咱们还会 watch pod 资源的变化稍后会讲到。发现 ReplicaSet 发生变化后,在 delta 队列中塞入了对象是 rsA,并且类型是更新的记录。
Informer 一方面把新的 ReplicaSet 更新到缓存中,并与 Namespace nsA 做为索引。另一方面,调用 Update 的回调函数,ReplicaSet 控制器发现 ReplicaSet 发生变化后会把字符串的 nsA/rsA 字符串塞入到工做队列中,工做队列后的一个 Worker 从工做队列中取到了 nsA/rsA 这个字符串的 key,而且从缓存中取到了最新的 ReplicaSet 数据。
Worker 经过比较 ReplicaSet 中 spec 和 status 里的数值,发现须要对这个 ReplicaSet 进行扩容,所以 ReplicaSet 的 Worker 建立了一个 Pod,这个 pod 中的 Ownereference 取向了 ReplicaSet rsA。
而后 Reflector Watch 到的 Pod 新增事件,在 delta 队列中额外加入了 Add 类型的 deta 记录,一方面把新的 Pod 记录经过 Indexer 存储到了缓存中,另外一方面调用了 ReplicaSet 控制器的 Add 回调函数,Add 回调函数经过检查 pod ownerReferences 找到了对应的 ReplicaSet,并把包括 ReplicaSet 命名空间和字符串塞入到了工做队列中。
ReplicaSet 的 Woker 在获得新的工做项以后,从缓存中取到了新的 ReplicaSet 记录,并获得了其全部建立的 Pod,由于 ReplicaSet 的状态不是最新的,也就是全部建立 Pod 的数量不是最新的。所以在此时 ReplicaSet 更新 status 使得 spec 和 status 达成一致。
Kubernetes 控制器模式依赖声明式的 API。另一种常见的 API 类型是命令式 API。为何 Kubernetes 采用声明式 API,而不是命令式 API 来设计整个控制器呢?
首先,比较两种 API 在交互行为上的差异。在生活中,常见的命令式的交互方式是家长和孩子交流方式,由于孩子欠缺目标意识,没法理解家长指望,家长每每经过一些命令,教孩子一些明确的动做,好比说:吃饭、睡觉相似的命令。咱们在容器编排体系中,命令式 API 就是经过向系统发出明确的操做来执行的。
而常见的声明式交互方式,就是老板对本身员工的交流方式。老板通常不会给本身的员工下很明确的决定,实际上可能老板对于要操做的事情自己,还不如员工清楚。所以,老板经过给员工设置可量化的业务目标的方式,来发挥员工自身的主观能动性。好比说,老板会要求某个产品的市场占有率达到 80%,而不会指出要达到这个市场占有率,要作的具体操做细节。
相似的,在容器编排体系中,咱们能够执行一个应用实例副本数保持在 3 个,而不用明确的去扩容 Pod 或是删除已有的 Pod,来保证副本数在三个。
在理解两个交互 API 的差异后,能够分析一下命令式 API 的问题。
在大规模的分布式系统中,错误是无处不在的。一旦发出的命令没有响应,调用方只能经过反复重试的方式来试图恢复错误,然而盲目的重试可能会带来更大的问题。
假设原来的命令,后台实际上已经执行完成了,重试后又多执行了一个重试的命令操做。为了不重试的问题,系统每每还须要在执行命令前,先记录一下须要执行的命令,而且在重启等场景下,重作待执行的命令,并且在执行的过程当中,还须要考虑多个命令的前后顺序、覆盖关系等等一些复杂的逻辑状况。
然而,由于巡检逻辑和平常操做逻辑是不同的,每每在测试上覆盖不够,在错误处理上不够严谨,具备很大的操做风险,所以每每不少巡检系统都是人工来触发的。
假若有多方并发的对一个资源请求进行操做,而且一旦其中有操做出现了错误,就须要重试。那么最后哪个操做生效了,就很难确认,也没法保证。不少命令式系统每每在操做前会对系统进行加锁,从而保证整个系统最后生效行为的可预见性,可是加锁行为会下降整个系统的操做执行效率。
不须要额外的操做数据。另外由于状态的幂等性,能够在任意时刻反复操做。在声明式系统运行的方式里,正常的操做实际上就是对资源状态的巡检,不须要额外开发巡检系统,系统的运行逻辑也可以在平常的运行中获得测试和锤炼,所以整个操做的稳定性可以获得保证。
最后,由于资源的最终状态是明确的,咱们能够合并屡次对状态的修改。能够不须要加锁,就支持多方的并发访问。
最后咱们总结一下:
这里为你们简单总结一下本文的主要内容:
阿里巴巴云原生微信公众号(ID:Alicloudnative)关注微服务、Serverless、容器、Service Mesh 等技术领域、聚焦云原生流行技术趋势、云原生大规模的落地实践,作最懂云原生开发者的技术公众号。