k8s之ReplicaSet回收pod的优先级

news2024/10/7 17:33:28

ReplicaSet syncReplicaSet的逻辑,首先找到rs下相关的pod

    // 从informer中获取当前ns下所有的pod
    allPods, err := rsc.podLister.Pods(rs.Namespace).List(labels.Everything())
    if err != nil {
        return err
    }
    // 忽略不健康的pod
    filteredPods := controller.FilterActivePods(allPods)
​
    // 找到被该rs的labelSelector和ownerRefControllerBy选中的pod
    filteredPods, err = rsc.claimPods(ctx, rs, selector, filteredPods)
    if err != nil {
        return err
    }

计算Pod的健康状态

func IsPodActive(p *v1.Pod) bool {
   return v1.PodSucceeded != p.Status.Phase &&
      v1.PodFailed != p.Status.Phase &&
      p.DeletionTimestamp == nil
}

计算当前实例数与期望实例数的diff,进而判断扩缩实例数

func (rsc *ReplicaSetController) manageReplicas(ctx context.Context, filteredPods []*v1.Pod, rs *apps.ReplicaSet) error {
    // 计算当前实例数与期望实例数的diff
    diff := len(filteredPods) - int(*(rs.Spec.Replicas))
    rsKey, err := controller.KeyFunc(rs)
    if err != nil {
        utilruntime.HandleError(fmt.Errorf("couldn't get key for %v %#v: %v", rsc.Kind, rs, err))
        return nil
    }
    // 扩容,新建实例数
    if diff < 0 {
        diff *= -1
        // burstReplicas默认值为500
        if diff > rsc.burstReplicas {
            diff = rsc.burstReplicas
        }
        rsc.expectations.ExpectCreations(rsKey, diff)
        klog.V(2).InfoS("Too few replicas", "replicaSet", klog.KObj(rs), "need", *(rs.Spec.Replicas), "creating", diff)
        successfulCreations, err := slowStartBatch(diff, controller.SlowStartInitialBatchSize, func() error {
            err := rsc.podControl.CreatePods(ctx, rs.Namespace, &rs.Spec.Template, rs, metav1.NewControllerRef(rs, rsc.GroupVersionKind))
            if err != nil {
                if apierrors.HasStatusCause(err, v1.NamespaceTerminatingCause) {
                    return nil
                }
            }
            return err
        })
        if skippedPods := diff - successfulCreations; skippedPods > 0 {
            klog.V(2).Infof("Slow-start failure. Skipping creation of %d pods, decrementing expectations for %v %v/%v", skippedPods, rsc.Kind, rs.Namespace, rs.Name)
            for i := 0; i < skippedPods; i++ {
                // Decrement the expected number of creates because the informer won't observe this pod
                rsc.expectations.CreationObserved(rsKey)
            }
        }
        return err
    } else if diff > 0 {
        // 缩容,删除实例
        // burstReplicas默认值为500
        if diff > rsc.burstReplicas {
            diff = rsc.burstReplicas
        }
        klog.V(2).InfoS("Too many replicas", "replicaSet", klog.KObj(rs), "need", *(rs.Spec.Replicas), "deleting", diff)
        // 获取deployment ownerRefControllerBy的所有rs的所有pod
        relatedPods, err := rsc.getIndirectlyRelatedPods(rs)
        utilruntime.HandleError(err)
​
        // 找到需要被删除的pod列表
        podsToDelete := getPodsToDelete(filteredPods, relatedPods, diff)
​
        // 在cache中记录需要被删除的podkey
        rsc.expectations.ExpectDeletions(rsKey, getPodKeys(podsToDelete))
​
        errCh := make(chan error, diff)
        var wg sync.WaitGroup
        wg.Add(diff)
        for _, pod := range podsToDelete {
            go func(targetPod *v1.Pod) {
                defer wg.Done()
                // 删除pod
                if err := rsc.podControl.DeletePod(ctx, rs.Namespace, targetPod.Name, rs); err != nil {
                    // 从cache中移除已被删除的podkey
                    podKey := controller.PodKey(targetPod)
                    rsc.expectations.DeletionObserved(rsKey, podKey)
                    if !apierrors.IsNotFound(err) {
                        klog.V(2).Infof("Failed to delete %v, decremented expectations for %v %s/%s", podKey, rsc.Kind, rs.Namespace, rs.Name)
                        errCh <- err
                    }
                }
            }(pod)
        }
        wg.Wait()
​
        select {
        case err := <-errCh:
            // all errors have been reported before and they're likely to be the same, so we'll only return the first one we hit.
            if err != nil {
                return err
            }
        default:
        }
    }
​
    return nil
}

着重看一下getPodsToDelete方法

func getPodsToDelete(filteredPods, relatedPods []*v1.Pod, diff int) []*v1.Pod {
   // 如果需要删除的实例数 小于 当前rs的活跃实例数,则对实例数删除优先级进行排序
   // 如果需要删除的实例数 大于或等于 当前rs的活跃实例数,则不需要排序,直接返回所有实例数
   if diff < len(filteredPods) {
      // 对该deployment下所有rs的所有pod所在node进行计数,并将node对应的计数值作为到当前rs下pod的rank值
      podsWithRanks := getPodsRankedByRelatedPodsOnSameNode(filteredPods, relatedPods)
      // 参考rank值进行排序
      sort.Sort(podsWithRanks)
      reportSortingDeletionAgeRatioMetric(filteredPods, diff)
   }
   return filteredPods[:diff]
}

getPodsRankedByRelatedPodsOnSameNode按照在同一node上的pod数量设置对应的rank值,并返回ActivePodsWithRanks方便进行排序

func getPodsRankedByRelatedPodsOnSameNode(podsToRank, relatedPods []*v1.Pod) controller.ActivePodsWithRanks {
   // 对该deployment下所有rs的所有pod所在node进行计数
   podsOnNode := make(map[string]int)
   for _, pod := range relatedPods {
      if controller.IsPodActive(pod) {
         podsOnNode[pod.Spec.NodeName]++
      }
   }
   // 并将node对应的计数值作为到当前rs下pod的rank值
   ranks := make([]int, len(podsToRank))
   for i, pod := range podsToRank {
      ranks[i] = podsOnNode[pod.Spec.NodeName]
   }
   // 以ActivePodsWithRanks格式返回,方便进行排序
   return controller.ActivePodsWithRanks{Pods: podsToRank, Rank: ranks, Now: metav1.Now()}
}

看一下ActivePodsWithRanks结构体

type ActivePodsWithRanks struct {
   // Pods is a list of pods.
   Pods []*v1.Pod
​
   // Rank is a ranking of pods.  This ranking is used during sorting when
   // comparing two pods that are both scheduled, in the same phase, and
   // having the same ready status.
   Rank []int
​
   // Now is a reference timestamp for doing logarithmic timestamp comparisons.
   // If zero, comparison happens without scaling.
   Now metav1.Time
}

排序算法

func (s ActivePodsWithRanks) Less(i, j int) bool {
   // 1. 未被调度到节点的pod优先被删除,Unassigned < assigned
   // If only one of the pods is unassigned, the unassigned one is smaller
   if s.Pods[i].Spec.NodeName != s.Pods[j].Spec.NodeName && (len(s.Pods[i].Spec.NodeName) == 0 || len(s.Pods[j].Spec.NodeName) == 0) {
      return len(s.Pods[i].Spec.NodeName) == 0
   }
   // 2. pending优先于unknown优先于running被删除, PodPending < PodUnknown < PodRunning
   if podPhaseToOrdinal[s.Pods[i].Status.Phase] != podPhaseToOrdinal[s.Pods[j].Status.Phase] {
      return podPhaseToOrdinal[s.Pods[i].Status.Phase] < podPhaseToOrdinal[s.Pods[j].Status.Phase]
   }
   // 3. condition ready为false优先被删除,Not ready < ready
   // If only one of the pods is not ready, the not ready one is smaller
   if podutil.IsPodReady(s.Pods[i]) != podutil.IsPodReady(s.Pods[j]) {
      return !podutil.IsPodReady(s.Pods[i])
   }
​
   // 4. k8s v1.21版本提供了controller.kubernetes.io/pod-deletion-cost注解作为featuregate,较低的值优先被删除,默认开启,lower pod-deletion-cost < higher pod-deletion cost
   if utilfeature.DefaultFeatureGate.Enabled(features.PodDeletionCost) {
      pi, _ := helper.GetDeletionCostFromPodAnnotations(s.Pods[i].Annotations)
      pj, _ := helper.GetDeletionCostFromPodAnnotations(s.Pods[j].Annotations)
      if pi != pj {
         return pi < pj
      }
   }
​
   // 5. 在同一个节点上的较多的pod,优先被删除,Doubled up < not doubled up
   if s.Rank[i] != s.Rank[j] {
      return s.Rank[i] > s.Rank[j]
   }
​
   // 6. 如果pod都是ready,则ready时间较短的实例优先被删除 Been ready for empty time < less time < more time
   if podutil.IsPodReady(s.Pods[i]) && podutil.IsPodReady(s.Pods[j]) {
      readyTime1 := podReadyTime(s.Pods[i])
      readyTime2 := podReadyTime(s.Pods[j])
      if !readyTime1.Equal(readyTime2) {
         // k8s v1.21版本提供了LogarithmicScaleDown的featuregate,默认开启,计算时间戳计算以2为底的对数即log2
         if !utilfeature.DefaultFeatureGate.Enabled(features.LogarithmicScaleDown) {
            return afterOrZero(readyTime1, readyTime2)
         } else {
            if s.Now.IsZero() || readyTime1.IsZero() || readyTime2.IsZero() {
               return afterOrZero(readyTime1, readyTime2)
            }
            rankDiff := logarithmicRankDiff(*readyTime1, *readyTime2, s.Now)
            // 如果使用LogarithmicScaleDown的计算结果相同,则会比较pod的UID,UID的ASCII码较小的pod优先被删除,可以理解为随机
            if rankDiff == 0 {
               return s.Pods[i].UID < s.Pods[j].UID
            }
            return rankDiff < 0
         }
      }
   }
   // 7. container重启次数较多的pod优先被删除,多个container取最大值
   if maxContainerRestarts(s.Pods[i]) != maxContainerRestarts(s.Pods[j]) {
      return maxContainerRestarts(s.Pods[i]) > maxContainerRestarts(s.Pods[j])
   }
   // 8. 创建时间短的pod优先被删除
    Empty creation time pods < newer pods < older pods
   if !s.Pods[i].CreationTimestamp.Equal(&s.Pods[j].CreationTimestamp) {
      // k8s v1.21版本提供了LogarithmicScaleDown的featuregate,默认开启,计算时间戳计算以2为底的对数即log2
      if !utilfeature.DefaultFeatureGate.Enabled(features.LogarithmicScaleDown) {
         return afterOrZero(&s.Pods[i].CreationTimestamp, &s.Pods[j].CreationTimestamp)
      } else {
         if s.Now.IsZero() || s.Pods[i].CreationTimestamp.IsZero() || s.Pods[j].CreationTimestamp.IsZero() {
            return afterOrZero(&s.Pods[i].CreationTimestamp, &s.Pods[j].CreationTimestamp)
         }
         rankDiff := logarithmicRankDiff(s.Pods[i].CreationTimestamp, s.Pods[j].CreationTimestamp, s.Now)
         // 如果使用LogarithmicScaleDown的计算结果相同,则会比较pod的UID,UID的ASCII码较小的pod优先被删除,可以理解为随机
         if rankDiff == 0 {
            return s.Pods[i].UID < s.Pods[j].UID
         }
         return rankDiff < 0
      }
   }
   return false
}

在6和8中使用LogarithmicScaleDown来计算时间戳,该算法不是直接比较时间戳,而是比较自创建以来的经过时间和到当前时间为止的时间差,再以2为底计算对数,进行四舍五入。这样做的效果是,当两个时间差差距较小时,将其视为相等的时间。也就是说,create或ready了几纳秒或几毫秒的pod的优先级是相等的,但它们与运行了几秒钟或几天的pod优先级不同。

同时在6和8中如果使用LogarithmicScaleDown的计算结果相同,则会比较pod的UID,UID的ASCII码较小的pod优先被删除,可以理解为随机

可以参考以10为底计算对数的例子,但实际k8s最终采用的是以2为底计算对数,参考enhancements/keps/sig-apps/2185-random-pod-select-on-replicaset-downscale at master · kubernetes/enhancements · GitHub

总而言之

1、未被调度到节点的pod优先被删除

2、pod状态pending优先于unknown优先于running被删除

3、pod condition ready为false优先被删除

4、pod注解pod-deletion-cost值较低的优先被删除

5、同一个节点上的较多的pod,优先被删除

6、如果pod都是ready,则ready时间较短的实例优先被删除,但ready时间差距较小也会认为是ready时间相同,若相同则此时会比较UID,UID小的优先被删除,可以理解为随机删除

7、pod都是非ready,则container重启次数较多的pod优先被删除,多个container取最大值

8、创建时间短的pod优先被删除,但创建时间差距较小也会认为是创建时间相同,若相同则此时会比较UID,UID小的优先被删除,可以理解为随机删除

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

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

相关文章

CentOS桥接模式下设置静态IP并解决java.net.ConnectException: Connection timed out: connect

一、前言 最近在配置服务器&#xff0c;DHCP模式下IP地址不固定&#xff0c;每次SSH远程登录连接不上&#xff0c;都要查看新动态分配的IP地址重新配置&#xff0c;感觉些许麻烦&#xff0c;于是给机器配置了静态固定IP。 动态主机配置协议DHCP&#xff08;Dynamic Host Confi…

Shell脚本练习题(附详细解题过程)

目录 一、利用for循环打印99乘法表 二、十进制转二进制 三、将十进制ip地址转换为二进制格式 四、检测某个网段中存活的主机并输出其ip地址 五、检查文件中用户名是否存在并提示创建用户和设置密码 六、检查httpd服务并开启 七、根据百米赛跑成绩判断结果 八、随机数…

栈和队列(数据结构刷题)[一]-python

文章目录 前言一、原理介绍二、用栈实现队列1.操作2.思路 三、关于面试考察栈里面的元素在内存中是连续分布的么&#xff1f; 前言 提到栈和队列&#xff0c;大家可能对它们的了解只停留在表面&#xff0c;再深入一点&#xff0c;好像知道又好像不知道的感觉。本文我将从底层实…

Django----------模板、静态文件、案例(城市天气预报)、请求和响应

目录 1.templates模板 2.静态文件 1.static目录 2.引用静态文件 1.方式一&#xff1a;直接引用 2.方式二&#xff1a;头部及内部引用 3. 模板语法 1.取内容 2.取下标 3.for循环 4.利用字典 5.列表里套字典 6.if条件语句 7.总结 4.案例&#xff08;城市天气预…

CH573-01-GPIO-LED——RISC-V内核BLE MCU快速开发教程

1. 新建工程 1) NEW Project 点击“File->New->MounRiver Project”&#xff1a; 2) finish 选择CH573F的裸机开发工程模板&#xff0c;如下图&#xff0c;然后点击“finish” 3) 编译检查 4) 精简代码 打开工程目录下的./src/main.c文件&#xff0c;修改删掉生成的串口测…

动态规划III (买股票-121、122、123、188)

CP121 买股票的最佳时机 题目描述&#xff1a; 给定一个数组 prices &#xff0c;它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。你只能选择 某一天 买入这只股票&#xff0c;并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利…

Vue 中的几种动画效果

Vue 中的动画效果 在 Vue 中&#xff0c;动画效果是非常常见的交互方式。它可以为用户提供更加生动的交互体验&#xff0c;增强用户的参与感和满意度。在本文中&#xff0c;我们将探讨 Vue 中的动画效果的基本原理和用法&#xff0c;并给出一些实例代码来帮助读者更好地理解。…

『手撕 Mybatis 源码』06 - Mapper 代理方式初始化

Mapper 代理方式初始化 首先修改一下 SqlSession 获取代理对象方式&#xff0c;即通过 getMapper() 来拿到动态代理对象 public class MybatisTest {/*** 问题1&#xff1a;<package name"com.itheima.mapper"/> 是如何进行解析的&#xff1f;* 解答&#xf…

算法刷题-数组-有序数组的平方

977.有序数组的平方 力扣题目链接 给你一个按 非递减顺序 排序的整数数组 nums&#xff0c;返回 每个数字的平方 组成的新数组&#xff0c;要求也按 非递减顺序 排序。 示例 1&#xff1a; 输入&#xff1a;nums [-4,-1,0,3,10] 输出&#xff1a;[0,1,9,16,100] 解释&#…

clang到底是什么?gcc和clang到底有什么区别?

最近发现自己对 GNU GCC 和 Clang 的区别不太清楚&#xff0c;影响到一些实现和学习&#xff0c;所以趁这两天有空好好研究了一下。 在这个研究过程中&#xff0c;我发现很多问题其实源自于语言&#xff08;不是指编程语言&#xff0c;而是中文和英文翻译的失真&#xff09;和…

前端前端学习不断

卷吧卷吧...&#xff0c;这东西什么时候是个头啊……

智能指针(2)

智能指针&#xff08;2&#xff09; shared_ptr(共享型智能指针)基础知识特点引用计数器共享型智能指针结构理解 shared_ptr仿写删除器类计数器类shared_ptr类使用以及仿写代码的理解 循环引用_Weaks 初始化智能指针的方法 shared_ptr(共享型智能指针) 基础知识 在java中有一…

chatgpt赋能python:Python如何判断输入的字符——基础教程与实例

Python如何判断输入的字符——基础教程与实例 时至今日&#xff0c;互联网已经成为人们获取信息的重要途径&#xff0c;而搜索引擎优化&#xff08;SEO&#xff09;则是网站重要的推广手段之一。而Python作为一种高级编程语言&#xff0c;在实现SEO时也有很大的优势&#xff0…

chatgpt赋能python:Python如何进行升序和降序排列

Python如何进行升序和降序排列 Python是一种非常流行的编程语言&#xff0c;由于其在数据科学、机器学习和人工智能等领域的强大表现&#xff0c;越来越多的人开始学习和使用Python。在Python中&#xff0c;排序是一项非常常见的操作。在这篇文章中&#xff0c;我将向您介绍如…

stable diffusion webui 登录接口(login)api接口调用(使用C#)

唠嗑 本次将跟读者讲一下如何通过C#请求sd webui api【login】接口&#xff0c;如果读者觉得文章有用&#xff0c;请给【点个赞】吧&#xff0c;有问题可以评论区提问。 实战 1.配置api启用参数 启动webui时&#xff0c;需加上【–api】 、【–api-auth 账号:密码】 和【–…

chatgpt赋能python:Python字符类型判断:如何判断字符是字母或数字

Python字符类型判断&#xff1a;如何判断字符是字母或数字 在Python编程中&#xff0c;经常需要判断一个字符是字母还是数字。本文将介绍如何在Python中判断字符类型&#xff0c;并给出几个示例。 判断字符类型的方法 在Python中&#xff0c;可以使用以下方法来判断字符类型…

chatgpt赋能python:Python中如何删除变量中的字符

Python中如何删除变量中的字符 在Python编程中&#xff0c;我们有时需要清除变量中的字符。删除字符可以是去掉字符串中的某些字符&#xff0c;也可以是从列表或元组中删除某些元素。本文将介绍Python中如何删除变量中的字符。 删除字符串中的字符 Python使用字符串的切片操…

网络安全入门学习第十五课——PHP基础

文章目录 一、WEB技术1、什么是web2、B/S架构3、C/S架构 二、PHP概述1、PHP是什么2、PHP受欢迎的原因3、基于MVC模式的PHP框架4、常用编译工具5、PHP环境搭建6、开发工具 三、PHP基本语法格式1、标记2、输出语句3、注释4、标识符 四、数据与运算1、常量1.1、常量定义1.2、预定义…

前端vue实现页面加水印文字 单个页面所有页面加水印 水印颜色

前端vue实现页面加水印文字, 可以实现系统所有页面加水印,也可以单个页面加水印, 可更改水印颜色, 下载完整代码请访问uni-app插件市场地址: https://ext.dcloud.net.cn/plugin?id12889 效果图如下: #### 使用方法 使用方法 /* 给系统所有页面加水印*/ // 第一个参数:水印…

Shell脚本函数简介及运用

目录 一、函数的作用 二、定义函数 三、调用函数 1.在脚本中调用函数 2.在函数中调用函数 四、函数传参 五、函数的返回值 六、函数的递归 七、函数及其变量的作用范围 八、外部脚本调用函数 一、函数的作用 语句块定义成函数约等于别名&#xff0c;定义函数&#xf…