在分布式调度中为了保证服务的高可用和容灾需求,通常都会讲服务在多个区域、机架、节点上平均分布,从而避免单点故障引起的服务不可用,在k8s中自然也实现了该算法即SelectorSpread, 本文就来学习下这个算法的底层实现细节

1. 设计要点

1.1 zone与node

zone即代表一个区域,node则是一个具体的节点,而该打散算法的目标就是将pod在zone和node之间进行打散操作

1.2 namespace

namespace是k8s中进行资源隔离的实现,同样的筛选也是如此,在筛选的过程中,不同namespace下面的pod并不会相互影响

1.3 计数与聚合

SelectorSpread算法是scheduler中优先级算法的一种,其实现了优先级算法的map/reduce方法,其中map阶段需要完成对各个节点亲和性的统计, 也就是统计该节点上的匹配的pod的数量,而reduce阶段则是聚合所有匹配的数量,进行统计打分

1.4 参考对象

在k8s中有很多上层对象诸如service、replicaSet、statefulset等,而算法打散的对象也是依据这些上层对象,让单个service的多个pod进行平均分布

1.5 选择器

在传统的基于数据库的设计中,数据之间的关联关系通常是基于外键或者对象id来实现模型之间的关联,而在kubernetes中则是通过selector来进行这种关系的映射,通过给对象定义不同的label然后在label上构造选择器,从而实现各种资源之间的相互关联

2. 实现原理

2.1 选择器

2.1.1 选择器接口

选择器接口其关键方法主要是通过Matches来进行一组标签的匹配,先关注这些就可以了,后续需要再去关注其核心实现

type Selector interface {// Matches returns true if this selector matches the given set of labels.Matches(Labels) bool// String returns a human readable string that represents this selector.String() string// Add adds requirements to the SelectorAdd(r ...Requirement) Selector
}

2.1.2 资源筛选

Selector数组的实现其实也很简单,就是遍历所有相关联的资源,然后用当前的pod上的Label标签去搜索,如果发现有资源包含当前pod的标签,就把对应资源的所有Selector都获取出来,加入到selectors数组中


func getSelectors(pod *v1.Pod, sl algorithm.ServiceLister, cl algorithm.ControllerLister, rsl algorithm.ReplicaSetLister, ssl algorithm.StatefulSetLister) []labels.Selector {var selectors []labels.Selectorif services, err := sl.GetPodServices(pod); err == nil {for _, service := range services {selectors = append(selectors, labels.SelectorFromSet(service.Spec.Selector))}}if rcs, err := cl.GetPodControllers(pod); err == nil {for _, rc := range rcs {selectors = append(selectors, labels.SelectorFromSet(rc.Spec.Selector))}}if rss, err := rsl.GetPodReplicaSets(pod); err == nil {for _, rs := range rss {if selector, err := metav1.LabelSelectorAsSelector(rs.Spec.Selector); err == nil {selectors = append(selectors, selector)}}}if sss, err := ssl.GetPodStatefulSets(pod); err == nil {for _, ss := range sss {if selector, err := metav1.LabelSelectorAsSelector(ss.Spec.Selector); err == nil {selectors = append(selectors, selector)}}}return selectors
}

2.1 算法注册与初始化

2.1.1 算法注册

在构建算法的时候,首先会从参数中获取各种资源的Lister, 其实就是筛选对象的一个接口,可以从该接口中获取集群中对应类型的所有资源

    factory.RegisterPriorityConfigFactory(priorities.SelectorSpreadPriority,factory.PriorityConfigFactory{MapReduceFunction: func(args factory.PluginFactoryArgs) (priorities.PriorityMapFunction, priorities.PriorityReduceFunction) {return priorities.NewSelectorSpreadPriority(args.ServiceLister, args.ControllerLister, args.ReplicaSetLister, args.StatefulSetLister)},Weight: 1,},)

2.1.2 算法初始化

算法初始化则是构建一个SelectorSpread对象,我们可以看到其map和reduce的关键实现分别对应内部的两个方法

func NewSelectorSpreadPriority(serviceLister algorithm.ServiceLister,controllerLister algorithm.ControllerLister,replicaSetLister algorithm.ReplicaSetLister,statefulSetLister algorithm.StatefulSetLister) (PriorityMapFunction, PriorityReduceFunction) {selectorSpread := &SelectorSpread{serviceLister:     serviceLister,controllerLister:  controllerLister,replicaSetLister:  replicaSetLister,statefulSetLister: statefulSetLister,}return selectorSpread.CalculateSpreadPriorityMap, selectorSpread.CalculateSpreadPriorityReduce
}

2.2 CalculateSpreadPriorityMap

2.2.1 构建选择器

在进行Map核心统计阶段之前会先根据当前的pod获取其上的选择器Selector数组,即当前pod有那些选择器相关联,这个是在创建meta的时候完成

    var selectors []labels.Selectornode := nodeInfo.Node()if node == nil {return schedulerapi.HostPriority{}, fmt.Errorf("node not found")}priorityMeta, ok := meta.(*priorityMetadata)if ok {// 在priorityMeta构建的时候已经完成selectors = priorityMeta.podSelectors} else {// 获取当前pod的所有的selector 包括service  rs rcselectors = getSelectors(pod, s.serviceLister, s.controllerLister, s.replicaSetLister, s.statefulSetLister)}if len(selectors) == 0 {return schedulerapi.HostPriority{Host:  node.Name,Score: int(0),}, nil}

2.2.2 统计匹配计数

统计计数其实就是根据上面的selector数组逐个遍历当前node上面的所有pod如果发现全都匹配则计数一次,最后返回当前节点上匹配的pod的数量(这里的匹配是指的所有都匹配即跟当前的pod的所有label匹配都一样)

func countMatchingPods(namespace string, selectors []labels.Selector, nodeInfo *schedulernodeinfo.NodeInfo) int {//  计算当前node上面匹配的node的数量if nodeInfo.Pods() == nil || len(nodeInfo.Pods()) == 0 || len(selectors) == 0 {return 0}count := 0for _, pod := range nodeInfo.Pods() {// 这里会跳过不同namespace和被删除的podif namespace == pod.Namespace && pod.DeletionTimestamp == nil {matches := true// 遍历所有的选择器,如果不匹配,则会立马跳出for _, selector := range selectors {if !selector.Matches(labels.Set(pod.Labels)) { matches = falsebreak}}if matches {count   // 记录当前节点上匹配的pod的数量}}}return count
}

2.2.3 返回统计结果

最后返回对应node的名字和node上的匹配的pod的数量

    count := countMatchingPods(pod.Namespace, selectors, nodeInfo)return schedulerapi.HostPriority{Host:  node.Name,Score: count,}, nil

2.4 CalculateAntiAffinityPriorityReduce

2.4.1 计数器

计数器主要包含三个:单个node上最大的pod数量、单个zone里面最大pod的数量、每个zone中pod的数量

    countsByZone := make(map[string]int, 10)maxCountByZone := int(0)maxCountByNodeName := int(0)

2.4.2 单节点最大统计与zone区域聚合

    for i := range result {if result[i].Score > maxCountByNodeName {maxCountByNodeName = result[i].Score // 寻找单节点上的最大pod数量}zoneID := utilnode.GetZoneKey(nodeNameToInfo[result[i].Host].Node())if zoneID == "" {continue}// 进行zone所有node匹配pod的聚合countsByZone[zoneID]  = result[i].Score}

2.4.3 zone最大值统计

    for zoneID := range countsByZone {if countsByZone[zoneID] > maxCountByZone {maxCountByZone = countsByZone[zoneID]}}

2.4.4 核心计算打分算法

核心打分算法流程包含两个级别:node级别和zone级别,其算法为:node: 10 * ((单节点最大匹配数量)-当前node的匹配数量)/最大节点匹配数量) = fscodezone: 10 * ((单zone最大匹配数量)-当前zone的匹配数量)/最大zone匹配数量) = zoneScore合并: fScore (1.0 - zoneWeighting)) (zoneWeighting zoneScore (zoneWeighting=2/3)即优先进行zone级别分布,其次再是node

比如分别有3个node其匹配pod数量分别为:node1:3, node2:5, node3:10 则打分结果为:node1: 10 * ((10-3)/10) = 7node2: 10 * ((10-5)/10) = 5node3: (10* ((10-5)/10) = 0可以看到其上匹配的pod数量越多最终的优先级则越小假设分别有3个zone(跟node编号相同), 则zone得分为:zone1=7, zone2=5, zone3=0最终计分(zoneWeighting=2/3): node1=7, node2=5, node3=0

    maxCountByNodeNameFloat64 := float64(maxCountByNodeName)maxCountByZoneFloat64 := float64(maxCountByZone)MaxPriorityFloat64 := float64(schedulerapi.MaxPriority)for i := range result {// initializing to the default/max node score of maxPriorityfScore := MaxPriorityFloat64if maxCountByNodeName > 0 {fScore = MaxPriorityFloat64 * (float64(maxCountByNodeName-result[i].Score) / maxCountByNodeNameFloat64)}// If there is zone information present, incorporate itif haveZones {zoneID := utilnode.GetZoneKey(nodeNameToInfo[result[i].Host].Node())if zoneID != "" {zoneScore := MaxPriorityFloat64if maxCountByZone > 0 {zoneScore = MaxPriorityFloat64 * (float64(maxCountByZone-countsByZone[zoneID]) / maxCountByZoneFloat64)}fScore = (fScore * (1.0 - zoneWeighting))   (zoneWeighting * zoneScore)}}result[i].Score = int(fScore)if klog.V(10) {klog.Infof("%v -> %v: SelectorSpreadPriority, Score: (%d)", pod.Name, result[i].Host, int(fScore),)}}

今天就到这里吧,其实可以看出在分布的时候,是会优先尝试zone分布,然后在进行节点分布,我比较好奇zoneWeighting=2/3这个值是怎么来的,从注释上看,老外也没有证明,可能就是为了倾斜zone吧,大家周末愉快

微信号:baxiaoshi2020

关注公告号源码分析文章

更多文章关注 www.sreguide.com

本文由博客一文多发平台 OpenWrite 发布

图解kubernetes服务打散算法的实现源码相关推荐

  1. 波函数坍缩算法的实现源码

    15号,水饺快见底了..... 这是WaveFunctionCollapse波函数坍缩算法的实现源码. WFC算法可通过解数独游戏的过程来直观的理解,通常解数独游戏时都会先去找一个格子,这个格子可能填 ...

  2. python代码弄成网站_原创:用python把链接指向的网页直接生成图片的http服务及网站(含源码及思想)...

    原创:用python把链接指向的网页直接生成图片的http服务及网站(含源码及思想) 总体思想: 希望让调用方通过 http调用传入一个需要生成图片的网页链接生成一个网页的图片并返回图片链接 最终调用 ...

  3. 原创:用python把链接指向的网页直接生成图片的http服务及网站(含源码及思想)...

    原创:用python把链接指向的网页直接生成图片的http服务及网站(含源码及思想) 总体思想:     希望让调用方通过 http调用传入一个需要生成图片的网页链接生成一个网页的图片并返回图片链接 ...

  4. Java生鲜电商平台-秒杀系统微服务架构设计与源码解析实战

    Java生鲜电商平台-秒杀系统微服务架构设计与源码解析实战 Java生鲜电商平台-  什么是秒杀 通俗一点讲就是网络商家为促销等目的组织的网上限时抢购活动 比如说京东秒杀,就是一种定时定量秒杀,在规定 ...

  5. java如何通过grpc连接etcd_grpc通过 etcd 实现服务发现与注册-源码分析

    介绍 下面介绍 jupiter-0.2.7 版本中 grpc 通过 etcd 实现服务发现与注册. 服务发现与注册的实现解析 服务注册 服务注册的流程图: etcd的服务注册代码模块在 jupiter ...

  6. Spring Cloud微服务系列-Eureka Client源码解析(一)

    导语   Eureka Client 是为了简化开发人员的开发工作,将很多的Eureka Server交互的工作进行了封装,在使用的时候自动完成,在应用的不同阶段来完成不同的功能实现.下面就来了解一下 ...

  7. android 原生开发 3d地图 下载_arcgis api 3.x for js 入门开发系列二不同地图服务展示(附源码下载)...

    前言 关于本篇功能实现用到的 api 涉及类看不懂的,请参照 esri 官网的 arcgis api 3.x for js:esri 官网 api,里面详细的介绍 arcgis api 3.x 各个类 ...

  8. 高斯正算C语言程序,一个老师给的高斯投影正、反算c++源码(最新整理)

    <一个老师给的高斯投影正.反算c++源码(最新整理)>由会员分享,可在线阅读,更多相关<一个老师给的高斯投影正.反算c++源码(最新整理)(4页珍藏版)>请在人人文库网上搜索. ...

  9. 高斯投影正反算C语言程序代码,一个老师给的高斯投影正反算c++源码.doc

    一个老师给的高斯投影正反算c源码 //高斯投影正.反算 //6度带宽?? 54年北京坐标系 //高斯投影由经纬度 Unit:DD 反算大地坐标 含带号,Unit:Metres void GaussPr ...

最新文章

  1. AI一分钟 | 小米智能音箱mini版曝光,或售199元;特朗普被指利用AI竞选成功
  2. 《乐高EV3机器人搭建与编程》——2.2 颜色设计
  3. Thinkphp3.2学习(一)
  4. python使用函数的优点-Lambda表达式在Python中的优点和缺点
  5. php显示无法找到该网页,window_Win8系统IE浏览器提示无法找到该网页的解决方法,  我们在浏览网页的时候, - phpStudy...
  6. 字节跳动 java面经_字节跳动Java面经(已offer)
  7. [Leedcode][JAVA][第11题][盛最多水的容器][双指针][贪心]
  8. LIMIT M,N分页性能优化方案
  9. How many ways?? - hdu2157(矩阵快速幂-模板)
  10. linux安装jdk1.8之后报错Error: dl failure on line 893的解决办法
  11. 数据结构电视大赛投票系统
  12. Elasticsearch节点类型
  13. unity摄影机depth模式_[蛮牛教程] Unity3D 浅析-Camera(摄像机)
  14. 甘特图控件VARCHART XGantt如何开始使用
  15. thinkphp之url的seo优化
  16. Python: 鲁卡斯队列
  17. 室内设计师面试技巧有哪些?
  18. 【练习记录】C语言实现正则表达式匹配
  19. scratch编程密室逃脱
  20. boost::python::detail::destroy_referent相关的测试程序

热门文章

  1. (5)惯性推算失控保护
  2. 推荐7个助你面试通关的公众号
  3. mininet下建立拓扑时关于远程控制器的一个小问题
  4. U260686 树的直径
  5. xp系统如何添加wifi连接服务器地址,XP系统下如何连接隐藏的WiFi信号|XP系统电脑连接隐藏WiFi信号的方法...
  6. 传采用16nm工艺的小米澎湃S2年底商用
  7. java trunc函数_Oracle常用函数Trunc及Trunc函数用法讲解
  8. 许多学习vba excel脚本的简单例子
  9. DIV布局web网页设计实例作业 ——文化网页设计(6页)
  10. python打印文件的前几行或最后几行