【 K8s 源码之调度学习】Pod 间亲和性和反亲和性的源码分析

news2024/10/3 8:29:55

查看案例

字段含义
podAffinityPod 间的亲和性定义
podAntiAffinityPod 间的反亲和性定义
requiredDuringSchedulingIgnoredDuringExecution硬性要求,必须满足条件,保证分散部署的效果最好使用用此方式
preferredDuringSchedulingIgnoredDuringExecution软性要求,可以不完全满足,即有可能同一node上可以跑多个副本
requiredDuringSchedulingIgnoredDuringExecutionlabelSelector
topologyKey
preferredDuringSchedulingIgnoredDuringExecutionweight
podAffinityTermlabelSelector
topologyKey
topologyKey可以理解为 Node 的 Label,具有相同的 Label 的 Node,视为同一拓扑
如三个节点打上 Label :
- Node1 —— zone:beijing
- Node2 —— zone:shanghai
- Node3 —— zone:beijing
那么 Node1 和 Node3 为同一拓扑,Node2 为另一拓扑
topologyKey: kubernetes.io/hostname
上面为常见的配置,可以通过 kubectl get nodes --show-labels看到
节点上的 Lable,就具有此 kubernetes.io/hostname Label
因此就是将每个节点,作为一个独立的拓扑
apiVersion: v1
kind: Pod
metadata:
  name: test-pod
spec:
  affinity:
    # 首先根据 labelSelector 选择具有 service.cpaas.io/name: deployment-nginx  Label 的 所有 Pod
    # 接下来根据 podAffinity 亲和性,将此 pod 调度到与选中 Pod 中具有 topologyKey 的 Node 上
    podAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchLabels:
            service.cpaas.io/name: deployment-nginx
        topologyKey: kubernetes.io/hostname
      - labelSelector:
          matchLabels:
            service.cpaas.io/name: deployment-busybox
        topologyKey: kubernetes.io/hostname
    # 首先根据 labelSelector 选择具有 key 为 a ,value为 b 或 c 的 Label 的 Pod
    # 接下来根据 podAntiAffinity,将此 pod 调度到与选中 Pod 中都不相同的 Node 上,该节点需要具有 topologyKey label
    podAntiAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 100
        podAffinityTerm:
          labelSelector:
            matchExpressions:
            - key: a
              operator: In
              values: ["b", "c"]
          topologyKey: kubernetes.io/hostname
  containers:
  - name: test-pod
    image: nginx:1.18


代码分析

代码路径:pkg/scheduler/framework/plugins/interpodaffinity

img

首先根据调度器框架,观察源码,可以看出实现了一下四个接口:

  1. PreFilter
  2. Filter
  3. PreScore
  4. Score

首先明确几点

  • 该插件是考虑 Pod 间的亲和性和反亲和性(就是新Pod 和 现存 Pod 的关系)
  • 但最终结果是将 Pod 调度到合适的 Node 上(因此要记录 Node 的信息)

1 | PreFilter

此步骤作用:

  1. 梳理出【现存哪些 Pod】 讨厌【新 Pod】,记录【满足条件的现存 Pod】 对应 Node 信息为 existingPodAntiAffinityMap
  2. 梳理出【新 Pod】喜欢【哪些现存Pod】,记录【满足条件的现存 Pod】 对应 Node 信息为 incomingPodAffinityMap
  3. 梳理出【新 Pod】讨厌【哪些现存Pod】,记录【满足条件的现存 Pod】 对应 Node 信息为 incomingPodAntiAffinityMap

所以可以小总结一下

  • existingPodAntiAffinityMap 和 incomingPodAntiAffinityMap 这些记录的节点,新 Pod 不喜欢
  • incomingPodAffinityMap 记录的节点,Pod 喜欢

问题 —— 为什么不梳理 【现存哪些 Pod】 喜欢【新 Pod】?

  • 因为现在是调度【新 Pod】,只要不被讨厌,不影响【现存 Pod 】就行,因此只需要可能会影响的【现存 Pod】

注意上面所说的【条件】—— 指的是【硬性要求 requiredDuringSchedulingIgnoredDuringExecution 】 —— 因此才考虑这么详细

// 这里只截取了 PreFilter 部分重要函数
// pkg/scheduler/framework/plugins/interpodaffinity/filtering.go

// 考虑现存 Pod 的 反亲和性 anti-affinity
// 简单理解:就是用现存 Pod 的 anti-affinity Terms 配置,要求 NewPod,记录下满足的 Node,说明这些节点不能调度(因为现存 Pod 排斥新 Pod)
// 这里的 anti-affinity Terms 是指 requiredDuringSchedulingIgnoredDuringExecution 定义的硬性要求
// 问题:为什么不考虑现存 Pod 的亲和性? —— 因为现存 Pod 的亲和性(是亲和他之前 Pod),在其调度的时候早已考虑,现在只需要考虑其反感的
// 代码级理解:
// 1. 遍历所有具有 anti-affinity 现存 Pod
// 2. 若即将调度的 NewPod 满足该 Pod 的 anti-affnity Terms,
// 3. 就记录到 existingPodAntiAffinityMap 中,key 为该 Pod 所在的 node 信息(topologyKey、topologyValue),value 为满足的 Terms 次数
// 例如 map{(hostname:node01):1}
// existingPodAntiAffinityMap will be used later for efficient check on existing pods' anti-affinity
existingPodAntiAffinityMap := getTPMapMatchingExistingAntiAffinity(pod, nodesWithRequiredAntiAffinityPods)

// 考虑新 NewPod 的亲和性和反亲和性
// 简单理解: 就是用 NewPod 的 anti-affinity 和 affinity Terms 配置,要求现存的 Pod,记录下满足的 Node
// incomingPodAffinityMap will be used later for efficient check on incoming pod's affinity
// incomingPodAntiAffinityMap will be used later for efficient check on incoming pod's anti-affinity
incomingPodAffinityMap, incomingPodAntiAffinityMap := getTPMapMatchingIncomingAffinityAntiAffinity(podInfo, allNodes)

2 | Filter

  • *framework.CycleState 将上面统计的信息传递过来
  • 现在的工作就是:
    • 传来了一个 Node 信息
    • 判断该 Node 与上面的 existingPodAntiAffinityMap、incomingPodAntiAffinityMap 、incomingPodAffinityMap 的关系
    • 若该 Node 满足条件,那么可以进入到下面的【打分阶段】
// pkg/scheduler/framework/plugins/interpodaffinity/filtering.go
func (pl *InterPodAffinity) Filter(ctx context.Context, cycleState *framework.CycleState, pod *v1.Pod, nodeInfo *framework.NodeInfo) *framework.Status {
	if nodeInfo.Node() == nil {
		return framework.NewStatus(framework.Error, "node not found")
	}

	state, err := getPreFilterState(cycleState)
	if err != nil {
		return framework.NewStatus(framework.Error, err.Error())
	}

	if !satisfyPodAffinity(state, nodeInfo) {
		return framework.NewStatus(framework.UnschedulableAndUnresolvable, ErrReasonAffinityNotMatch, ErrReasonAffinityRulesNotMatch)
	}

	if !satisfyPodAntiAffinity(state, nodeInfo) {
		return framework.NewStatus(framework.Unschedulable, ErrReasonAffinityNotMatch, ErrReasonAntiAffinityRulesNotMatch)
	}

	if !satisfyExistingPodsAntiAffinity(state, nodeInfo) {
		return framework.NewStatus(framework.Unschedulable, ErrReasonAffinityNotMatch, ErrReasonExistingAntiAffinityRulesNotMatch)
	}

	return nil
}

3 | PreScore

这部分主要看 processExistingPod 函数

  • 可以看出根据【现存 Pod】 和【新 Pod】的【软性要求preferredDuringSchedulingIgnoredDuringExecution】,对节点进行打分
// pkg/scheduler/framework/plugins/interpodaffinity/scoring.go
// PreScore builds and writes cycle state used by Score and NormalizeScore.
func (pl *InterPodAffinity) PreScore(
	pCtx context.Context,
	cycleState *framework.CycleState,
	pod *v1.Pod,
	nodes []*v1.Node,
) *framework.Status {
  
  // ... ...
	topoScores := make([]scoreMap, len(allNodes))
	index := int32(-1)
	processNode := func(i int) {
		nodeInfo := allNodes[i]
		if nodeInfo.Node() == nil {
			return
		}
		// Unless the pod being scheduled has affinity terms, we only
		// need to process pods with affinity in the node.
		podsToProcess := nodeInfo.PodsWithAffinity
		if hasAffinityConstraints || hasAntiAffinityConstraints {
			// We need to process all the pods.
			podsToProcess = nodeInfo.Pods
		}

		topoScore := make(scoreMap)
		for _, existingPod := range podsToProcess {
			pl.processExistingPod(state, existingPod, nodeInfo, pod, topoScore)
		}
		if len(topoScore) > 0 {
			topoScores[atomic.AddInt32(&index, 1)] = topoScore
		}
	}
	parallelize.Until(context.Background(), len(allNodes), processNode)

	for i := 0; i <= int(index); i++ {
		state.topologyScore.append(topoScores[i])
	}

	cycleState.Write(preScoreStateKey, state)
	return nil
}

func (pl *InterPodAffinity) processExistingPod(
	state *preScoreState,
	existingPod *framework.PodInfo,
	existingPodNodeInfo *framework.NodeInfo,
	incomingPod *v1.Pod,
	topoScore scoreMap,
) {
	existingPodNode := existingPodNodeInfo.Node()

	// For every soft pod affinity term of <pod>, if <existingPod> matches the term,
	// increment <p.counts> for every node in the cluster with the same <term.TopologyKey>
	// value as that of <existingPods>`s node by the term`s weight.
	topoScore.processTerms(state.podInfo.PreferredAffinityTerms, existingPod.Pod, existingPodNode, 1)

	// For every soft pod anti-affinity term of <pod>, if <existingPod> matches the term,
	// decrement <p.counts> for every node in the cluster with the same <term.TopologyKey>
	// value as that of <existingPod>`s node by the term`s weight.
	topoScore.processTerms(state.podInfo.PreferredAntiAffinityTerms, existingPod.Pod, existingPodNode, -1)

	// For every hard pod affinity term of <existingPod>, if <pod> matches the term,
	// increment <p.counts> for every node in the cluster with the same <term.TopologyKey>
	// value as that of <existingPod>'s node by the constant <args.hardPodAffinityWeight>
	if pl.args.HardPodAffinityWeight > 0 {
		for _, term := range existingPod.RequiredAffinityTerms {
			t := framework.WeightedAffinityTerm{AffinityTerm: term, Weight: pl.args.HardPodAffinityWeight}
			topoScore.processTerm(&t, incomingPod, existingPodNode, 1)
		}
	}

	// For every soft pod affinity term of <existingPod>, if <pod> matches the term,
	// increment <p.counts> for every node in the cluster with the same <term.TopologyKey>
	// value as that of <existingPod>'s node by the term's weight.
	topoScore.processTerms(existingPod.PreferredAffinityTerms, incomingPod, existingPodNode, 1)

	// For every soft pod anti-affinity term of <existingPod>, if <pod> matches the term,
	// decrement <pm.counts> for every node in the cluster with the same <term.TopologyKey>
	// value as that of <existingPod>'s node by the term's weight.
	topoScore.processTerms(existingPod.PreferredAntiAffinityTerms, incomingPod, existingPodNode, -1)
}

4 | Score

这部分就是,将节点的得分进行累计计算,返回此符合条件的节点的得分数

  • 注意,所有符合条件都会调用此函数,得到自己对应的分数
// pkg/scheduler/framework/plugins/interpodaffinity/scoring.go
// Score invoked at the Score extension point.
// The "score" returned in this function is the sum of weights got from cycleState which have its topologyKey matching with the node's labels.
// it is normalized later.
// Note: the returned "score" is positive for pod-affinity, and negative for pod-antiaffinity.
func (pl *InterPodAffinity) Score(ctx context.Context, cycleState *framework.CycleState, pod *v1.Pod, nodeName string) (int64, *framework.Status) {
	nodeInfo, err := pl.sharedLister.NodeInfos().Get(nodeName)
	if err != nil || nodeInfo.Node() == nil {
		return 0, framework.NewStatus(framework.Error, fmt.Sprintf("getting node %q from Snapshot: %v, node is nil: %v", nodeName, err, nodeInfo.Node() == nil))
	}
	node := nodeInfo.Node()

	s, err := getPreScoreState(cycleState)
	if err != nil {
		return 0, framework.NewStatus(framework.Error, err.Error())
	}
	var score int64
	for tpKey, tpValues := range s.topologyScore {
		if v, exist := node.Labels[tpKey]; exist {
			score += tpValues[v]
		}
	}

	return score, nil
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/369510.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

duilib.dll丢失怎么办?dll文件丢失修复方法分享

duilib.dll丢失怎么办&#xff1f;其实在使用 Windows 系统的过程中&#xff0c;有时会出现提示“duilib.dll丢失”的错误。这个错误可能会影响电脑的正常运行&#xff0c;但是不用担心&#xff0c;今天小编来给大家详细的讲解一下duilib.dll丢失都有哪些解决方法。 一.什么是…

SAFe(Scaled Agile Framework)学习笔记

1.SAFe 概述 SAFe&#xff08;Scaled Agile Framework&#xff09;是一种面向大型企业的敏捷开发框架&#xff0c;旨在协调多个团队和部门的协同工作&#xff0c;以实现高效的软件开发和交付。下面是SAFe框架的简单介绍总结&#xff1a; SAFe框架包括以下四个层次&#xff1a…

金测评 手感更细腻的游戏手柄,双模加持兼容更出色,雷柏V600S上手

很多朋友周末都喜欢玩玩游戏放松一下&#xff0c;在家玩游戏的时候&#xff0c;PC是大家常用的平台&#xff0c;当然了&#xff0c;玩游戏的时候用键鼠的话&#xff0c;手感难免差点意思&#xff0c;还是要手柄才能获得更好的体验。我现在用的是雷柏V600S&#xff0c;这是一款支…

飞鹅打印机怎么样?飞鹅打印机好用吗?飞鹅打印机怎么知道订单是否漏单?

外卖打印机怎么选?飞鹅打印机好用吗&#xff1f;飞鹅智能云打印机产品专注于云打印的解决方案和技术服务提供。2019 年飞鹅已经成为国内先进的云打印服务提供商&#xff0c;主要是服务美团、饿了么客户&#xff0c;产品主要优势&#xff1a;自动接单、自动打印&#xff0c;无需…

美好音乐不只在现场,索尼播放器NW-WM1ZM2和NW-WM1AM2满足聆听热爱

当两点一线的单调生活成了多数人的生活常态&#xff0c;那些有过程有讲究的仪式感开始变得弥足珍贵起来&#xff0c;爱乐者们不远千里奔赴音乐节、Livehouse的现场&#xff0c;除了追求当下高燃兴奋的感受&#xff0c;同样是为了获得一份全心投入的听音仪式感。而当不便出行的日…

.net core 本地环境切换网络遇到的问题 500.19 502.5 invalid_request

问题一 运行环境 IIS 部署.NET CORE 项目 出现 HTTP 错误 500.19 - Internal Server Error附上.NET CORE2.1版本的下载链接下载 .NET Core 2.1 (Linux、macOS 和 Windows) (microsoft.com)下载完成以后重启IIS&#xff0c;有的版本还需要在IIS设置.NET CLR版本为无托管代码二 H…

从0开始写Vue项目-Vue实现用户个人信息界面上传头像

从0开始写Vue项目-环境和项目搭建_慕言要努力的博客-CSDN博客从0开始写Vue项目-Vue2集成Element-ui和后台主体框架搭建_慕言要努力的博客-CSDN博客从0开始写Vue项目-Vue页面主体布局和登录、注册页面_慕言要努力的博客-CSDN博客从0开始写Vue项目-SpringBoot整合Mybatis-plus实现…

mybatis-plus深入学习篇(三)

mybatis-plus深入学习篇(三) 1 准备工作 1.1 建表sql语句(Emp表) SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS 0; -- ---------------------------- -- Table structure for emp -- ---------------------------- DROP TABLE IF EXISTS emp; CREATE TABLE emp (EMPNO int …

java原理4:java的io网络模型

文章目录1&#xff1a;基础概念1&#xff1a;同步和异步2&#xff1a;阻塞和非阻塞2.1&#xff1a;阻塞IO2.2&#xff1a;非阻塞io2.3&#xff1a;io复用3&#xff1a;同步/异步和阻塞/非阻塞3.1&#xff1a;同步非阻塞NIO4: redis为什么速度快Java 网络IO模型简介1&#xff1a…

VMware ESXi 8.0b - 领先的裸机 Hypervisor (Dell HPE Custom Image update)

本站发布 Dell 和 HPE 定制版 ESXi 8.0b 镜像 请访问原文链接&#xff1a;https://sysin.org/blog/vmware-esxi-8/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页&#xff1a;www.sysin.org 产品简介 VMware ESXi&#xff1a;专门构建的裸机 Hyper…

HTB-Photobomb

HTB-Photobomb信息收集开机提权对于问题的思考信息收集 端口扫描 目标首页 有一个http Authorization 目录扫描 在查看源码的时候发现了一个js文件。 并且发现了访问不存在的目录会出现错误提示。 通过搜索得知 Sinatra 是一个基于 Ruby 语言的 DSL&#xff08;领域…

2. Dart 开发工具环境配置

很多编辑器都可以用来开发dart&#xff0c;所以大家可以选择自己喜欢的编辑器去进行开发。我还是比较喜欢vs code如果你不用vs code来开发dart的话&#xff0c;这篇文章可以直接跳过。如果想要在vs code里有dart的语法提示&#xff0c;我们需要安装相关的插件如图点开插件输入d…

MySQL高可用 集群(MHA)

1. MHA集群概述 集群的定义&#xff1a;多台服务器一起提供相同的服务&#xff0c;如&#xff08;web集群&#xff09;等。常见集群的分类&#xff1a; LB&#xff08;负载均衡集群&#xff09;&#xff1a;服务器共同平均分摊处理客户端的多次连接请求。 HA&#xff08;高可用…

python库streamlit学习笔记

什么是streamlit&#xff1f; Streamlit是一个免费的开源框架&#xff0c;用于快速构建和共享漂亮的机器学习和数据科学Web应用程序。它是一个基于Python的库&#xff0c;专为机器学习工程师设计。数据科学家或机器学习工程师不是网络开发人员&#xff0c;他们对花几周时间学习…

Python近红外光谱分析与机器学习、深度学习方法融合实践技术

、 第一n入门基础【理论讲解与案 1、Python环境搭建&#xff08; 下载、安装与版本选择&#xff09;。 2、如何选择Python编辑器&#xff1f;&#xff08;IDLE、Notepad、PyCharm、Jupyter…&#xff09; 3、Python基础&#xff08;数据类型和变量、字符串和编码、list和tu…

教你如何搭建培训机构-教务管理系统,demo可分享

1、简介 1.1、案例简介 本文将介绍&#xff0c;如何搭建培训机构-教务管理。 1.2、应用场景 可对排课、上课点名、课堂点评、作业发布进行管理。 2、设置方法 2.1、表单搭建 1&#xff09;新建表单【学员】&#xff0c;字段设置如下&#xff1a; 名称类型学员成员单选姓…

STM32—独立看门狗(IWDG)

什么是看门狗&#xff1f; 在由单片机构成的微型计算机系统中&#xff0c;由于单片机的工作常常会收到来自外界电磁场的干扰&#xff0c;造成程序跑飞&#xff0c;而陷入死循环&#xff0c;程序的正常运行被打断&#xff0c;由单片机控制的系统无法继续工作&#xff0c;会造成…

Python Unittest框架

1、unittest简介 unittest是Python自带的单元测试框架,具备编写用例、组织用例、执行用例、输出报告等自动化框架的条件,主要适用于单元测试,可以用来作自动化测试框架的用例组织执行框架。 2、unittest框架的特性: 提供用例组织与执行:当测试用例只有几条的时候可以不考虑…

notepad++如何快速批量搜索复制,3步搜索+标记所在行+复制书签行

一。缘起 用习惯了 某edit, 突然用notepad很不习惯&#xff0c;至少3处不习惯&#xff1a;列操作&#xff0c;批量复制搜索行&#xff0c;和是txt文件比较。 另外一直坚持认为&#xff0c;不提供快捷键操作的软件不是好软件&#xff1a;&#xff09;当下屏幕对眼睛迫害至深的时…

【正点原子FPGA连载】第十七章双核AMP实验 摘自【正点原子】DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南

1&#xff09;实验平台&#xff1a;正点原子MPSoC开发板 2&#xff09;平台购买地址&#xff1a;https://detail.tmall.com/item.htm?id692450874670 3&#xff09;全套实验源码手册视频下载地址&#xff1a; http://www.openedv.com/thread-340252-1-1.html 第十七章双核AMP…