开发者学堂课程【应用编排与管理核心原理:【公开课】应用编排和管理核心原理】学习笔记,与课程紧密联系,让用户快速学习知识。
课程地址:https://developer.aliyun.com/learning/course/828/detail/13947
【公开课】应用编排和管理核心原理
内容:
一、课程目标
二、资源元信息
三、操作演示
四、控制器模式
五、控制器模式总结
一、课程目标
学习 K8s 的应用编排和管理核心原理,讲解分为四个部分和课后习题,首先讲解 K8s 的重要资源元信息,第二部分是利用阿里云服务器演示如何查看和修改 K8s 的资源的元数据,第三部分是分析控制器模式,第四部分是对控制器模式的总结。
二、资源元信息
1. Kubernetes 资源对象的组成
(1)Spec 部分:用于描述期望的状态
(2)Status 部分:用于描述观测到的状态
以上都是之前提及的。
(3)元数据Metadata
①用于识别资源的标签 Labels
②用于描述资源的注解 Annotations
③用于描述资源之间相互关系 OwnerReference
这些元数据在 K8s 运行中有非常重要的作用。
2.Labels
(1)标签 Labels 是一种标识型的 Key:Value 元数据,它也是最重要的元数据。
以下都是常用的资源标签
①environment:production
②release:stable
③app.kubernetes.io/version:5.7.21
④failure-domain.beta.kubernetes.io/region: cn-hangzhou
可以看到,前三个标签都标明了对应应用的环境,版本等,同时从应用名字的标签可以看到,标签的名字可以包括域名的前缀,用于描述打标签的系统和工具对标签的重置。而最后的标签打在了与前面三个不同的对象上,同时在域名前增加了版本的标识 beta 字符串。
(2)Labels 的作用
①用于筛选资源
②唯一的组合资源的方法
(3)可以使用 selector 在 SQL 查询语句中的相关资源
类似于SQL ‘select*where….’
(4)Selector
①最常见的 Selector 是相等型 Selector 如:Tie=front 和 Env=dev ,举个例子,假设系统主要有四个 Pod,每个 Pod 都有标记系统环境和层级的标签,通过 Tie:front 这个可以匹配左边的 pod ,相等型的 Selector 还可以包括多个相等条件,且多个相等条件之间是逻辑与的关系,在刚才的例子中,通过Tie=front ,Env=dev Selector 可以筛选出所有的 Tie:front 而且还可以筛选出 Env:prod 。
②第二种 Selector 是集合型 Selector 如:Env in (test,gray) ,在例子中 Selector 筛选的所有环境是 test 或者 gray 的Pod。除 in 的集合操作外,还有其他集合操作如 notin 操作,比如:tie notin (front,back),将筛选所有的 tie 不是 front 且不是 back 的 Pod,另外也可以根据是否存在某 Label 来筛选,如:Selector release ,筛选所有带 release 标签的 Pod。
集合型和相等型的 Selector 之间可以用逗号连接,同样表示逻辑与的关系。
4. Annotations
(1)Key: Value
常见的 Annotations 有:
①存储了阿里云负载均衡器的证书ID:
service.beta.kubernetes.io/alicloud-loadbalancer-cert-id:your-cert-id
可以看到,annotations 同样具有域名的前缀,标注中也可以包含版本信息。
②存储了nginx 接入层的配置信息:
nginx.ingress.kubernetes.io/service-weight: “new-nginx:20,old-nginx:60"
可以看到,annotations 包括逗号等无法在 Labels 中出现的特殊字符。
③在 kubectl apply 命令操作后的资源可以看到:
kubectl.kubernetes.io/last-applied-configuration:
Annotations 的值是一个结构化的数据,实际上是一个 json 串,标记了上一次 kubectl 操作的资源的 json 的描述。
(2)作用:
①用于存储资源的非标识性信息
②可以扩展资源的 spec/status 描述
(3)特点:
①一般比 label 更大
②可以包含特殊字符
③可以结构化也可以非结构化
5. OwnerReference
(1)“所以者”即集合类资源,如:Pod 的集合中的replicaset,statefulset
(2)集合类资源的控制器会创建对应的归属资源,如:Replicaset 控制器在操作中会创建 Pod,被创建的 Pod 在 OwnerReference 就指向了创建 Pod 的 replicaset。
如图:
(3)作用
①使用户方便反向查找一个创建资源的对象
②用于实现进行级联删除的效果
三、操作演示
通过 kubectl 命令去连接 ACK 中已经创建好的一个 k8s 集群,然后展示如何查看和修改 k8s 对象里的元数据,主要是 Pod 的标签、注解和对应的 OwnerReference
1.创建 port
首先查看集群现在的配置情况,如图:
可以看到,集群中最开始是没有 port 的。
然后用事先准备好的一个 Pod 的 yaml 文件,用它来进行创建一个 Pod ,如图:
2.查看 pod 中的标签
然后用 show-labels 的方式查看 Pod 打的标签,如图:
可以看到,两个 Pod 都打上了一个部署环境和层级的标签,然后还可以通过另外的方式查看具体一个资源的信息,如图:
首先查看 nginx1 第一个 Pod 的一个信息,再利用 -o 方式,用 yaml 文件输出,如图:
可以看到,第一个 Pod 元数据里包括了一个 labels 的字段,里面有两个 label。
3.如何修改 port 中已有的 labels
如给 labels 加上标签,把部署环境从开发环境换成测试环境,该如何操作?
如图:
指定 Pod 名字,在环境上加一个它的一个值 test ,是无法成功的。
可以看到,页面显示 label 已经有值,如果想覆盖这个值需要额外加上一个覆盖选项 overwrite 。
加上覆盖选项后,如图:
观察 labels 的设置情况,可以看到,nginx1 已经加上了一个部署环境 test 的标签。
同样的,如果想去掉 Pod 中的一个标签也是一样的操作,但是 env 后不是加上=,而是只加上 labels 名字,后面不加=改成使用-表示去除。
如图:
可以看到,这个labels 已经去标成功,然后看配置的 label 值,能发现 nginx1 的一个 Pod 少了一个 Tie=front 标签,并且 port 少了一个 Tie=front 标签。
4.如何用 labels selector 进行匹配
(1)使用相等型 labels selector
如图:
有了这两个 Pod 标签之后,可以用 labels selector 来进行匹配,首先,labels selector 是通过-l这个选项进行指定的,指定的时候,先试一下用相等型的一个 label 来筛选,这里指定的是部署环境等于测试环境的一个 Pod 。
如图:
z
假设有多个相等的条件需要指定,实际上这是一个逻辑与的关系,如果 env 等于 dev ,此时一个 Pod 都无法拿到。然后再假设 env=dev ,但若 tie=front,此时可以匹配到第二个 Pod,也就是 nginx2。
(2)使用集合型 labels selector
如图:
可以看到,如果这次还是想匹配出所有部署环境是 test 或者是 dev 的一个Pod,需要在 show-labels 后面加上引号,然后在括号里指定所有部署环境的一个集合。这样可以把两个创建的 Pod 都筛选出来。
5.如何对 Pod 增加注解
如图
可以看到,增加注解和前面的打标是一样的操作,但是需要把 label 命令改成 annotate 命令,然后一样指定类型和对应的名字。但是后面不是加上 label 的k:v 而是加上 annotate 的 k:v。这里可以指定一个任意的字符串,比如加上空格或者逗号。
然后,再看 Pod 的一些元数据,如图:
可以看到,这个 Pod 的一些元数据里面的 annotation ,还有一个 my-annotate 这个 Annotations。
然后还能看到,这里其实有一个 kubectl apply 的时候,kubectl 工具增加了一个 annotation ,这也是一个 json 串,里面也有很多特殊字符。
6.OwnerReference
下面演示 Pod 的 OwnerReference 是怎么出来的,原来的 Pod 都是直接通过创建 Pod 这个资源方式来创建的,这次可以换另一种方式来进行创建:通过创建一个 Replicaset 对象来创建 Pod 。
如图:
首先创建一个 Replicaset 对象,然后可以具体查看这个 Replicaset 对象,如图:
需要关注的是,这个 Replicaset 里面的 spec 里提到会创建两个 Pod ,然后 selector 通过匹配部署环境是 product 生产环境的这个标签来进行匹配。然后在看一下现在集群中 Pod 情况,如图:
可以看到,集群中多了两个 Pod ,仔细查看这两个 Pod ,会发现 Replicaset 创建出来的 Pod 有一个特点,即它带有 OwnerReference,然后 OwnerReference 里面指向了是一个 replicasets 类型,名字就叫做 nginx-replicasets。
四、控制器模式
1.控制循环
控制器模式最核心的概念就是控制循环的概念,在控制循环中,包括了控制器、被控制的系统以及能够观测系统的传感器这三个逻辑组件,外界通过修改资源的 spec 来机制资源,控制器比较资源的 spec、status ,从而计算一个 diff,diff 最终会用于决定系统执行什么控制操作,控制操作会使系统产生新的输出,并被传感器以资源 status 的形式上报,控制器的各个组件都将会独立自主的运行,不断的使系统向 spec 表示终态(status->spec)趋近。
2.sensor
(1)Reflector 和 Informer
如图:
可以看到,控制循环中逻辑的成分主要由 Reflector、Informer、Indexer 三个组件构成
①Reflector
Reflector 通过 List 和 Watch K8s server 来获取资源数据,List 用于 Controller 重启以及 Watch 中断的情况下进行系统资源的全量更新;而 Watch 则在多次 List 之间进行增量的资源更新。Reflector 在获取新的资源数据后,会在一个 Delta 队列中塞入一个包括资源对象信息本身以及资源对象事件类型的 Delta 记录,Delta 队列中可以保证同一个对象在队列中仅有一条记录,从而避免 Reflector 重新 List 和 Watch 的时候产生重复的记录。
②Informer
Informer 组件不断地从 Delta 队列中弹出 delta 记录,一方面把资源事件交给资源的回调函数,同时又把资源对象交给 indexer,让 indexer 把资源记录在一个缓存中,缓存在默认设置下是用资源的命名空间来做索引的,并且可以被 Controller Manager或多个 Controller 所共享。
③控制模式中的控制器
控制循环中的控制器组件主要由事件处理函数以及 worker 组成,事件处理函数之间会相互关注资源的新增、更新、删除的事件,并根据控制器的逻辑去决定是否需要处理。对需要处理的事件,会把事件关联资源的命名空间以及名字塞入一个工作队列中,并且由后续的 worker 池中的一个 Worker 来处理,工作队列会对存储的对象进行去重,从而避免多个 Woker 处理同一个资源的情况。Worker 在处理资源对象时,一般需要用资源的名字来重新获得最新的资源数据,用来创建或者更新资源对象,或者调用其他的外部服务,Worker 如果处理失败的时候,一般情况下会把资源的名字重新加入到工作队列中,从而方便之后进行重试。
3.控制循环例子-扩容
(1)控制循环的工作原理:Replicaset 是一个用于描述无状态应用的扩缩容行为的资源,在 Replicaset Controller 通过监听 Replicaset 资源来维持应用希望的资源数量,Replicaset 中,通过 selector 来匹配所有关联的 Pod,在这里,考虑 Replicaset 中 rsa 的 Replicaset ,如图:
replicas 从2改成3的场景。
首先,Reflector 会 watch 到 ReplicaSet 和 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 回调函数通过检查 podownerReferences 找到了对应的 ReplicaSet,并把包括 ReplicaSet 命名空间和字符串塞入到了工作队列中。ReplicaSet 的 Noker 在得到新的工作项之后,从缓存中取到了新的 ReplicaSet 记录,并得到了其所有创建的 Pod,因为 ReplicaSet 的状态不是最新的,也就是所有创建 Pod 的数量不是最新的。因此在此时 ReplicaSet 的 spec 和 status 达成一致。
详情可见下图:
五、控制器模式总结
Kubernetes 控制器的模式依赖声明式的 API ,另外一种常见的 API 还有命令式的 API ,Kubernetes 采用的是声明式的 API 而不是命令式的 API 来设计整个控制器。
1.比较两种 API 在交互行为上的差别
在生活中,常见的命令式的交互方式就是家长和孩子交流的方式。因为孩子欠缺目标意识,无法理解家长期望,家长往往通过命令,教孩子一些明确的动作,比如说:吃饭、睡觉类似的命令。容器编排体系中,命令式AP就是通过向系统发出明确的操作来执行的。而常见的声明式 API 就是老板对自己员工的交流方式。老板一般不会给自己的员工下很明确的决定,实际上可能老板对于要执行操作的事情本身,还不如员工清楚。因此,老板通过给员工设置可量化的业务目标的方式,来发挥员工白身的主观能动性。比如说:老板要求某个产品的市场占有率能达到80%,而不会指出要达到这个市场占有率,要做的具体操作细节。类似的,在容器编排体系中,可以指定一个实例的应用副本数保持在3个,而不是明确的说需要通过扩容或者删除一个 Pod 来保证副本数保持在3个。
2.两种 API 方式的问题
(1)命令式 API
命令式 API 存在的最大问题是错误处理。在大规模的分布系统中,错误是无处不在的,一旦发布的命令没有响应,主要通过反复重试的方式来恢复错误,然而盲目的重试可能会带来更大的问题,假设实际上后台的命令完成了,重试会导致多出一个重试命令的操作,因此为了避免重试带来的问题,系统往往会在执行命令前记录下需要执行的命令,在重启的情况下重做待执行的命令,并充分考虑多个命令执行的先后顺序、覆盖关系等等复杂的逻辑情况。实际上许多命令式 API 交互的后台往往还会做巡检的系统,用于修正命令处理超时、重试等异常场景造成数据不一致的问题,然而,由于巡检的逻辑和日常操作的逻辑不同,往往在测试上覆盖不够,在错误处理上不够严谨,具有很大的操作风险,因此巡检系统往往都是由人工触发。最后,命令式 API 在处理在多并发访问上也很容易出现问题,假设有多方并发对资源请求操作,在操作中又出现了错误需要重试,哪个操作出现错误是很难判断的,因此系统往往会在操作前进行加锁,但是加锁会降低整个系统操作的效率。
(2)声明式 API
相比较 命令式 API ,声明式 API 可天然的记录系统现在和最终的状态,不需要额外记录数据,而且状态的幂等操作可在任意时刻反复操作,在声明式 API 系统运行的操作里,正常的操作实际上就是对资源状态的巡检,不需要额外开发巡检系统,系统的运行逻辑也可以在日常运行中得到锤炼和测试,可以保证操作的稳定性。最后,由于资源的最终状态是明确的,因此可以合并多次资源的修改,可以不需要进行加锁等操作。
3.控制器模式总结
如图:
控制器模式是由声明式的 API 即由 K8S 资源对象的修改来驱动的,在 K8S 资源后是对关注该资源的控制器,然后由控制器异步地控制系统向设置的终态趋近,这些控制器是自主运行的,这使系统的自动化和无人值守化成为可能,由于 K8S 资源和控制器都是可以自定义的,因此方便扩展控制器模式,特别的,对于有状态应用,往往会通过自定义资源和控制器的方式来自动化运维操作也就是 operator 的场景。