4. 应用编排和管理:核心原理

本文由 CNCF + Alibaba 云原生技术公开课 整理而来

资源元信息

  • Kubernetes 资源对象:

Kubernetes 的资源对象组成:主要包括了 SpecStatus 两部分。其中 Spec 部分用来描述期望的状态,Status 部分用来描述观测到的状态。

  • Labels

最重要的一个元数据:资源标签 LabelsLabels是一种具有标识型的 key: value 元数据。

示例:

environment: production

release: stable

app.kubernetes.io/version: 5.7.21

failure-domain.beta.kubernetes.io/region: cn-hangzhou

可以看到,前三个标签都打在了 Pod 对象上,分别标识了对应的应用环境、发布的成熟度和应用的版本。从应用标签的例子可以看到,标签的名字包括了一个域名的前缀,用来描述打标签的系统和工具。

Labels 主要用来筛选资源和组合资源,Kubernetes 可以使用类似于 SQL 的 select 查询,来根据 Labels 查询相关的资源。

  • Selector

Selector 就是标签选择器,Labels 不提供唯一性,通过 Selector 才能方便地辨识出一组对象。

API 目前支持两种 Selector:基于相等条件的 和 基于集合条件的。一个 Selector 可以由多个必须条件组成,以 , 分隔。在多个必须条件指定的情况下,所有的条件都必须满足,多个条件之间是逻辑“与”的关系。

基于相等条件的 Selector 允许用 Label 的键或者值进行过滤。匹配的对象必须满足所有指定的 Label 约束,尽管可能也有另外的 Label

有三种运算符是允许的:===!=。前两种代表 相等,后一种代表 不相等。例如:

environment = production        选择所有键为 environment 且值等于 production 的资源

tier != frontend        选择所有键为 tier 且值不等于 frontend 的资源,和那些没有键为 tier 的label的资源

environment=production,tier!=frontend       选择所有键为 environment 且值等于 production,并且键为 tier 且值不等于 frontend 的资源

基于集合条件的 Selector 允许用一组值来过滤键。支持三种操作符:innotinexists(仅针对于key) 。例如:

environment in (production, qa)     选择所有键为 environment 且value等于 production 或者 qa 的资源

tier notin (frontend, backend)      选择所有键为 tier 且值是除了 frontend 和 backend 之外的资源,和那些没有label的键是 tier 的资源

partition       选择所有有一个 Label 的键为 partition 的资源,值是什么不会被检查

!partition      选择所有没有 Label 的键为 partition 的资源,值是什么不会被检查

, 相当于一个 AND 操作符。基于集合条件的 Selector 是一个相等性的宽泛的形式,因为 environment=production 相当于 environment in (production)!= 相当于 notin

基于集合的条件 可以与 基于相等的条件 结合起来使用,例如:partition in (customerA,customerB),environment!=qa

  • Annotations

Annotations 一般是系统或者工具用来存储资源的非标识性数据,用来增加资源的 SpecStatus 的描述。

可以使用 LabelsAnnotations 将元数据附加到资源对象。Labels 可用于选择资源对象并查找满足某些条件的资源对象集合。相比之下,Annotations 不用于标识和选择资源对象。Annotations 中的元数据可以包括 Labels 中不允许使用的字符。

AnnotationsLabels 一样,也是由 key: value 组成:

service.beta.kubernetes.io/alicloud-loadbalancer-cert-id: you-cert-id

nginx.ingress.kubernetes.io/service-weight: "new-nginx: 20, old-nginx: 80"

注意:Annotations 不会被 Kubernetes 直接使用,其主要目的是方便用户阅读查找。

  • Ownereference

Ownereference,即所有者,一般指集合类的资源。

集合类资源的控制器会创建对应的归属资源,比如:ReplicaSet 控制器在操作中会创建 Pod,被创建 PodOwnereference 就指向创建 PodReplicaSet

Ownereference 使得用户可以方便地查找一个创建资源的对象,另外还可以用来实现级联删除的效果。


控制器模式

  • 控制循环:

控制型模型最核心的就在于控制循环,在控制循环中,包括三个逻辑组件:控制器、被控制系统、能够观测系统的传感器。

外界通过修改资源 Spec 来控制资源,控制器比较资源 SpecStatus,从而计算一个 diff, diff 用来决定执行对系统进行怎样的控制操作,控制操作会使系统产生新的输出,并被传感器以资源 Status 形式上报,控制器的各个组件都是独立自主地运行,不断使系统向 Spec 终态趋近。

  • 传感器:

控制循环中的传感器主要由 Reflecotor、Informer、Indexer 三个组件构成。

Reflecotor 通过 ListWatch K8s Server 来获取资源的数据。List 用来在 Controller 重启 及 Watch 中断的情况下,进行系统资源的全量更新;而 Watch 则在多次 List 之间进行资源的增量更新;Reflecotor 在获取新的资源数据后,会在 Delta 队列中塞入一个包括资源对象信息本身以及资源对象事件类型的 Delta 记录, Delta 队列中保证同一个对象在队列中仅有一条记录,从而避免 Reflecotor 重新 ListWatch 的时候产生重复的记录。

Informer 组件不断地从 Delta 队列中弹出 Delta 记录,然后把资源对象交给 Indexer,让 Indexer 把资源记录在一个缓存中,缓存在默认配置下是用资源的命名空间来做索引的,并且可以被 Controller Manager 或多个 Controller 共享。之后,再把这个事件交给事件的回调函数。

控制循环中的控制器组件主要由事件处理函数以及 worker 组成,事件处理函数之间会相互关注资源的新增、更新、删除的事件,并根据控制器的逻辑去决定是否需要处理。对于需要处理的事件,会把事件关联资源的命名空间以及名字塞入一个工作队列中,并且由后续的 worker 池中的一个 worker 来处理,工作队列会对存储的对象进行去重,从而避免多个 worker 处理同一个资源的情况。

worker 在处理资源对象时,一般需要用资源的名字来重新获得最新的资源数据,用来创建或者更新系统对象,或者调用其他的外部服务。如果处理失败,一般会把资源的名字重新加入到工作队列中,从而方便之后进行重试。

  • 控制循环示例:

ReplicaSet 是一个用来描述无状态应用的扩缩容行为的资源,ReplicaSet Controler 通过监听 ReplicaSet 资源来维持应用希望的状态数量,ReplicaSet 中通过 Selector 来匹配所关联的 Pod。在这里考虑 ReplicaSet 扩容的场景。

首先,Reflector 会 WatchReplicaSetPod 两种资源的变化,发现 ReplicaSet 发生变化后,在 Delta 队列中塞入了对象,而且类型是更新的记录。

Informer 一方面把新的 ReplicaSet 更新到缓存中,并与 Namespace 作为索引;同时调用 Update 的回调函数,ReplicaSet Controler 发现 ReplicaSet 发生变化后会把字符串塞入到工作队列中,工作队列后的一个 worker 从工作队列中取到了这个字符串的 key,并且从缓存中取到了最新的 ReplicaSet 数据。

worker 通过比较 ReplicaSetSpecStatus 里的数值,发现需要对这个 ReplicaSet 进行扩容,因此 ReplicaSet 的 worker 创建了一个 Pod,这个 Pod 中的 Ownereference 指向了 ReplicaSet

然后 Reflector WatchPod 的新增事件,在 Delta 队列中额外加入了 Add 类型的 Delta 记录,一方面把新的 Pod 记录通过 Indexer 存储到缓存中,另一方面调用 ReplicaSet 控制器的 Add 回调函数,Add 回调函数通过检查 Pod OwnerReferences 找到对应的 ReplicaSet,并把包括 ReplicaSet 命名空间和字符串塞入到工作队列中。

ReplicaSet 的 worker 在得到新的工作项之后,从缓存中取到了新的 ReplicaSet 记录,并得到了其所有创建的 Pod。因为 ReplicaSet 的状态不是最新的,也就是所有创建 Pod 的数量不是最新的,所以在此时 ReplicaSet 更新 Status 使得 SpecStatus 达成一致。


API 设计

  • API 设计方法:

Kubernetes 控制器模式依赖声明式 API。 另一种常见的 API 类型是命令式 API。

两种 API 在交互行为上存在差别。在生活中,常见的命令式的交互方式是家长和孩子的交流方式,因为孩子缺乏目标意识,无法理解家长期望,家长往往通过一些命令叫孩子做一些事情,比如吃饭、睡觉、刷牙类似的命令。在容器编排体系中,命令式 API 就是通过向系统发出明确的操作来执行的。

而生活中常见的声明式交互方式,就是老板和员工的交流方式。老板一般不会对员工下很明确的指令,而是通过设置可量化的业务目标,让员工自己完成,至于如何完成业务目标老板并不关心。类似的,在容器编排体系中,可以编辑 YAML 文件,指定 Pod 副本数保持几个,而不是明确的去增加 Pod 或 删除已有 Pod 来保持副本数。

  • 命令式 API 的问题:

命令式 API 最大的一个问题在于错误处理。在大规模的分布式系统中,错误是无处不在的,一旦发出的命令没有响应,调用方只能通过反复重试的方式来试图恢复错误,然而盲目的重试可能会带来更大的问题。

实际上许多命令式的交互系统后台还会做一个巡检系统,用来修正命令处理超时、重试等场景造成的数据不一致的问题。由于巡检逻辑和日常操作逻辑是不一样的,往往在测试上覆盖不够,在错误处理上不够严谨,具有很大的操作风险,因此很多巡检系统往往都是人工来触发的。

最后,命令式 API 在处理多并发访问时,也很容易出现问题。

  • 声明式 API 的优势:

相对的,声明式 API 系统里天然地记录了系统现在和最终的状态。不需要额外的操作数据,另外由于状态的幂等性,可以在任意时刻反复操作。

在声明式系统运行的方式里,正常的操作实际上就是对资源状态的巡检,不需要额外开发巡检系统,系统的运行逻辑也可以在日常运行中得到测试和锤炼,因此整个操作系统的稳定性能够得到保证。

最后,因为资源的最终状态的明确的,所以可以合并多次对状态的修改,可以不需要加锁就支持多并发访问。

  • 控制器模式总结:

Kubernetes 所采用的控制器模式,是由声明式 API 驱动的,确切来说是基于对 Kubernetes 资源对象的修改来驱动的。

Kubernetes 资源背后,是关注该资源的控制器,这些控制器将异步的控制系统想设置的 Spec 期望状态趋近。这些控制器又是自主运行的,使得系统的自动化和无人值守称为可能。

Kubernetes 的控制器和资源都是可以自定义的,因此可以方便的扩展控制器模式。特别是对于有状态的应用,往往可以通过自定义资源和控制器的方式,来自动化运维操作。这正是 Kubernetes Operator 的使用场景。


猜你喜欢

转载自blog.csdn.net/miss1181248983/article/details/109821244