github.com/gocrane/crane@v0.11.0/docs/proposals/Pod-Sorting-And-Precise-Execution-For-Crane-Agent.zh.md (about)

     1  # Pod Sorting And Precise Execution For Crane Agent
     2  该proposal丰富了crane-agent的排序策略,完善了通用排序。并且实现了一套精准操作(压制/驱逐)的框架,在执行压制/驱逐等操作时,操作到用户指定的水位线即停止的精确操作逻辑,避免了对于低优pod的过度操作;
     3  
     4  具体来说:
     5  
     6  - 丰富了crane-agent的排序策略,完善了通用排序和cpu usage为主要参考的cpu维度排序;
     7  
     8  - 针对cpu usage,实现了执行压制/驱逐等操作时,操作到用户指定的水位线即停止的精确操作逻辑,避免了对于低优pod的过度操作;
     9  
    10  - 实现了一套精确操作(压制/驱逐)的框架,通过完善自定义指标的一些列属性和实现,即可在无需关心具体细节的情况下,同样具有同cpu usage一样的精确操作能力,具有一定的普适性和扩展性。
    11  
    12  ## Table of Contents
    13  
    14  <!-- TOC -->
    15  
    16  - [Pod Sorting And Precise Execution For Crane Agent](#Pod Sorting And Precise Execution For Crane Agent)
    17      - [Table of Contents](#table-of-contents)
    18      - [Motivation](#motivation)
    19          - [Goals](#goals)
    20      - [Proposal](#proposal)
    21          - [丰富pod的排序策略](#丰富pod的排序策略)
    22          - [metric属性的定义](#metric属性的定义)
    23          - [如何根据水位线进行精准控制](#如何根据水位线进行精准控制)
    24          - [以水位线为基准进行pod的精确操作](#以水位线为基准进行pod的精确操作)
    25              - [analyzer阶段](#analyzer阶段)
    26              - [executor阶段](#executor阶段)
    27          - [Non-Goals/Future Work](#non-goalsfuture-work)
    28          - [User Stories](#user-stories)
    29  
    30  <!-- /TOC -->
    31  ## Motivation
    32  当前在crane-agent中,当超过NodeQOS中指定的水位线后,执行evict,throttle等操作时先对低优先级的pod进行排序,当前排序的依据是pod的ProrityClass,然后在排序的pod进行throttle或者evict操作;
    33  
    34  目前存在的问题有:
    35  
    36  1. 排序只参考ProrityClass,无法满足基于其他特性的排序;同时也无法满足按照水位线精确操作对灵活排序的需求,无法满足尽快让节点达到指定的水位线的要求。例如我们希望尽快降低低优先级业务的cpu使用量时,应该选出cpu使用量较多的pod,这样能够更快地降低cpu用量,保障高优业务不受影响。
    37  
    38  2. 在触发NodeQOS中指定的水位线后,会对于节点上的所有低于指定ProrityClass的pod进行操作;例如,当前节点上有10个pod低于指定ProrityClass,在触发水位线后,会对这10个pod都进行操作,但是实际上可能在操作完成对第一个pod的操作后就可以低于NodeQOS中的指标值了,对剩下的pod的操作,属于过度操作,是可以避免的。如果能以NodeQOS中的指标值作为水位线对pod进行精确的操作,操作到刚好低于水位线是更为合适的,就能避免对低优先级服务的过度影响。
    39  
    40  ### Goals
    41  
    42  - 丰富了crane-agent的排序策略,包括以pod cpu用量为主要参照的排序,以pod内存用量为主要参照的排序,基于运行时间的排序,基于扩展资源使用率的排序。
    43  - 实现一套包含排序和精确操作的框架,支持对不同的指标丰富排序规则,并且实现精确操作。
    44  - 实现针对cpu usage和memmory usage的精确操作,当整机负载超过NodeQOS中指定的水位线后,会先对低优先级的pod进行排序,然后按照顺序操作到刚好低于水位线为止。
    45  
    46  ## Proposal
    47  
    48  ### 丰富pod的排序策略
    49  
    50  - 该proposal实现了一些通用的排序方法(之后会更多地完善):
    51    
    52    classAndPriority: 比较两个pod的QOSClass和class value,优先比较QOSClass,再比较class value;priority高的排在后面优先级更高
    53  
    54    runningTime:比较两个pod的运行时间,运行时间长的排在后面优先级更高
    55  
    56    如果仅需使用这两个排序策略,使用默认的排序方法即可:会首先比较pod的优先级,之后比较pod对应指标的用量,之后比较pod的运行时长,有一个维度可以比较出结果即为pod的排序结果
    57      ```go
    58      func GeneralSorter(pods []podinfo.PodContext) {
    59          orderedBy(classAndPriority, runningTime).Sort(pods)
    60      }
    61      ```
    62  
    63  - cpu usage 使用量的排序
    64  
    65      会依次比较两个pod的优先级,如果优先级相同的情况下,再比较cpu用量,如果cpu用量也相同的情况下继续比较ext cpu资源用量(这个是cpu属性较为特殊的一点), 最后比较pod的运行时长,当某一个指标存在差异时即可返回比较结果
    66  
    67      ```go
    68      func CpuUsageSorter(pods []podinfo.PodContext) {
    69          orderedBy(classAndPriority, cpuUsage, extCpuUsage, runningTime).Sort(pods)
    70      }
    71      ```
    72  
    73  - ext cpu usage 使用量的排序
    74  
    75    会首先比较两个pod是否使用了扩展的cpu资源,在都使用了的情况下,比较 扩展cpu资源使用量/ 扩展cpu资源limit的比值
    76  
    77  
    78  - 针对需要自定义的指标,可以通过实现如下的方法,并且随意搭配通用的排序方法即可方便地实现pod的灵活自定义排序,以<metric>代表自定义metric指标,<metric-sort-func>代表自定义的针对<metric>的排序策略
    79      ```go
    80      func <metric>Sorter(pods []podinfo.PodContext) {
    81          orderedBy(classAndPriority, <metric-sort-func>, runningTime).Sort(pods)
    82      }
    83      ```
    84      其中<metric-sort-func>只需要实现如下的排序方法即可
    85      ```go
    86      func (p1, p2 podinfo.PodContext) int32 
    87      ```
    88  
    89  
    90  ### metric属性的定义
    91  
    92  为了更好的基于NodeQOS配置的metric进行排序和精准控制,对metric引入属性的概念。
    93  
    94  metric的属性包含如下几个:
    95  
    96  1. Name 表明了metric的名称,需要同collector模块中收集到的指标名称一致
    97  2. ActionPriority 表示指标的优先级,0为最低,10为最高
    98  3. SortAble 表明该指标是否可以排序
    99  4. SortFunc 对应的排序方法,排序方法可以排列组合一些通用方法,再结合指标自身的排序,将在下文详细介绍
   100  5. ThrottleAble 表明针对该指标,是否可以对pod进行压制,例如针对cpu使用量这个metric,就有相对应的压制手段,但是对于memory使用量这种指标,就只能进行pod的驱逐,无法进行有效的压制
   101  6. ThrottleQuantified 表明压制(restore)一个pod后,能否准确计算出经过压制后释放出的对应metric的资源量,我们将可以准确量化的指标称为可Quantified,否则为不可Quantified;
   102     比如cpu用量,可以通过限制cgroup用量进行压制,同时可以通过当前运行值和压制后的值计算压制后释放的cpu使用量;而比如memory usage就不属于压制可量化metric,因为memory没有对应的throttle实现,也就无法准确衡量压制一个pod后释放出来的memory资源具体用量;
   103  7. ThrottleFunc,执行Throttle动作的具体方法,如果不可Throttle,返回的released为空
   104  8. RestoreFunc,被Throttle后,执行恢复动作的具体方法,如果不可Restore,返回的released为空
   105  9. EvictAble,EvictQuantified,EvictFunc 对evict动作的相关定义,具体内容和Throttle动作类似
   106  
   107  
   108  ```go
   109  type metric struct {
   110  	Name WaterLineMetric
   111  
   112  	ActionPriority int
   113  
   114  	SortAble bool
   115  	SortFunc func(pods []podinfo.PodContext)
   116  
   117  	ThrottleAble      bool
   118  	ThrottleQuantified bool
   119  	ThrottleFunc      func(ctx *ExecuteContext, index int, ThrottleDownPods ThrottlePods, totalReleasedResource *ReleaseResource) (errPodKeys []string, released ReleaseResource)
   120  	RestoreFunc       func(ctx *ExecuteContext, index int, ThrottleUpPods ThrottlePods, totalReleasedResource *ReleaseResource) (errPodKeys []string, released ReleaseResource)
   121  
   122  	EvictAble      bool
   123  	EvictQuantified bool
   124  	EvictFunc      func(wg *sync.WaitGroup, ctx *ExecuteContext, index int, totalReleasedResource *ReleaseResource, EvictPods EvictPods) (errPodKeys []string, released ReleaseResource)
   125  }
   126  ```
   127  
   128  用户可以自行定义自己的metric,在构造完成后,通过registerMetricMap()进行注册即可
   129  
   130  ### 如何根据水位线进行精准控制
   131  
   132  - 根据多个NodeQOS及其中的objectiveEnsurances构建多条水位线:  
   133    1. 按照objectiveEnsurances对应的action进行分类,目前crane-agent有3个针对节点Qos进行保障的操作,分别是Evict,ThtottleDown(当前用量高于objectiveEnsurances中的值时对pod进行用量压制)和ThrottleUp(当前用量低于objectiveEnsurances中的值时对pod的用量进行放宽恢复),因此会有三个水位线集合,分别是
   134       ThrottleDownWaterLine,ThrottleUpWaterLine和EvictWaterLine
   135  
   136    2. 再对同一操作种类中的水位线按照其metric rule(图中以metric A,metric Z作为示意)进行分类,并记录每个objectiveEnsurances水位线的值,记为waterLine; 
   137  
   138        ThrottleDownWaterLine,ThrottleUpWaterLine和EvictWaterLine的结构是这样的:
   139        `type WaterLines map[WaterLineMetric]*WaterLine`
   140      
   141        其中WaterLineMetric就是上面的metric的Name字段,value的WaterLine就是资源数值
   142        `type WaterLine resource.Quantity`  
   143    
   144      最终形成一个类似下图的数据存储:  
   145      ![](waterline-construct.png)
   146  
   147  - 构造实时用量到水位线的差值:   
   148  结合当前节点的指标实时用量与WaterLines中该指标对应的水位线中最小值的差值构造如下的数据结构,代表到当前用量到水位线的差值  
   149     `type GapToWaterLines map[WaterLineMetric]float64`
   150  
   151      其中key值为metric的Name字段,value为用量到水位线的差值; 
   152  
   153      需要注意对于ThrottleUp,需要用水位线最小值-当前用量作为gap值,对于其他两者,使用当前用量-水位线最小值作为gap值,即始终保持gap值为正
   154      
   155      下面三个数据分别代表了需要执行evict,ThtottleDown和ThrottleUp操作的指标及其对应的到最低水位线的差值
   156      ```go
   157      EvictGapToWaterLines[metrics]     
   158      ThrottoleDownGapToWaterLines[metrics]
   159      ThrottleUpGapWaterLine[metrics]
   160      ```
   161      
   162  - 以CpuUsage这个metric为例,构造节点cpu用量相关的waterline的流程和相关数据结构如下:  
   163      ![](cpu-usage-water-line.png)
   164  
   165  ### 以水位线为基准进行pod的精确操作
   166  该proposal为了实现以水位线为基准进行pod的精确操作,将对analyzer部分和executor部分做一定的修改,大体流程是:
   167  
   168  在analyzer阶段构造针对不同操作(驱逐,压制等)和不同metric的水位线,将原先的排序逻辑删除,后移到需要进行正式操作的executor阶段,并且可能会需要进行多轮排序;
   169  
   170  在executor阶段,根据水位线中的涉及的指标进行其相应的排序,获取最新用量,构造GapToWaterLines,并进行精确操作
   171  
   172  #### analyzer阶段
   173  在该阶段进行NodeQOS到WaterLines的转换,并对相同actionName和metricrule的规则进行合并,具体内容上文已经介绍过了
   174  
   175  #### executor阶段
   176  压制过程:
   177  
   178  1. 首先分析ThrottoleDownGapToWaterLines中涉及的metrics,将这些metrics根据其Quantified属性区分为两部分,如果存在不可Quantified的metric,则通过GetHighestPriorityThrottleAbleMetric获取具有最高ActionPriority的一个throttleAble(具有throttleFunc)的metric对所选择的所有pod进行压制操作,因为但凡存在一个不可Quantified的metric,就无法进行精确的操作
   179  
   180  2. 通过getStateFunc()获取当前节点和workload的最新用量,依据ThrottoleDownGapToWaterLines和实时用量构造GapToWaterLine(需要注意的是,在构造GapToWaterLine时,会以注册过的metric进行遍历,所以最终构造出来的GapToWaterLine中的metrics,会是ThrottoleDownGapToWaterLines
   181     中注册过的metric,避免了在NodeQOS中配置错误不存在或未注册metric的情况)
   182  
   183  3. 如果GapToWaterLine中有metric的实时用量无法获取(HasUsageMissedMetric),则通过GetHighestPriorityThrottleAbleMetric获取具有最高ActionPriority的一个throttleAble(具有throttleFunc)的metric对所选择的所有pod进行压制操作,因为如果存在metric实时用量无法获取,就无法获知和水位线的gap,也就无法进行精确的操作
   184  
   185  4. 如果不存在3中的情况,则遍历ThrottoleDownGapToWaterLines中可以量化的metric:如果metric具有排序方法则直接使用其SortFunc对pod进行排序,如果没有就使用GeneralSorter进行排序,之后使用其对应的ThrottleFunc对pod进行压制,并计算释放出来的对应metric的资源量,直到ThrottoleDownGapToWaterLines中该metric对应的gap已不存在
   186  ```go
   187  //将所有触发水位线的metrics根据其Quantified属性区分为两部分
   188  metricsQuantified, MetricsNotQuantified := ThrottleDownWaterLine.DivideMetricsByQuantified()
   189  // 如果存在不可Quantified的metric,获取具有最高ActionPriority的一个throttleAble的metric对所选择的所有pod进行操作
   190  if len(MetricsNotThrottleQuantified) != 0 {
   191      highestPrioriyMetric := GetHighestPriorityThrottleAbleMetric()
   192      if highestPrioriyMetric != "" {
   193          t.throttlePods(ctx, &totalReleased, highestPrioriyMetric)
   194      }
   195  } else {
   196      //获取节点和workload的最新用量,构造和水位线差距
   197      ThrottoleDownGapToWaterLines = buildGapToWaterLine(ctx.getStateFunc())
   198      //如果触发水位线中存在metric的实时用量无法获取,则获取具有最高ActionPriority的一个throttleAble的metric对所选择的所有pod进行压制操作
   199      if ThrottoleDownGapToWaterLines.HasUsageMissedMetric() {
   200          highestPrioriyMetric := ThrottleDownWaterLine.GetHighestPriorityThrottleAbleMetric()
   201          if highestPrioriyMetric != "" {
   202              throttlePods(ctx, &totalReleased, highestPrioriyMetric)
   203          }
   204      } else {
   205          var released ReleaseResource
   206  		//遍历触发水位线的metric中可以量化的metric:如果metric具有排序方法则直接使用其SortFunc对pod进行排序,否则使用GeneralSorter排序;
   207  		//之后使用其对应的操作方法对pod执行操作,并计算释放出来的对应metric的资源量,直到对应metric到水位线的差距已不存在
   208          for _, m := range metricsQuantified {
   209              if m.SortAble {
   210                  m.SortFunc(ThrottleDownPods)
   211              } else {
   212                  GeneralSorter(ThrottleDownPods)
   213              }
   214      
   215              for !ThrottoleDownGapToWaterLines.TargetGapsRemoved(m) {
   216                  for index, _ := range ThrottleDownPods {
   217                      released = m.ThrottleFunc(ctx, index, ThrottleDownPods, &totalReleased)
   218                      ThrottoleDownGapToWaterLines[m] -= released[m]
   219                  }
   220              }
   221          }
   222      }
   223  }
   224  ```
   225  
   226  驱逐过程:
   227  
   228  驱逐和压制的流程是一样的,除了在对pod进行操作的时候需要额外判断一下pod是否已经被驱逐了;取出一个没有执行过的pod,执行驱逐操作,并计算释放出的各metric资源量,同时在对应水位线中减去释放的值,直到满足当前metric水位线要求
   229  ```go
   230  metricsEvictQuantified, MetricsNotEvcitQuantified := EvictWaterLine.DivideMetricsByEvictQuantified()
   231  
   232  if len(MetricsNotEvcitQuantified) != 0 {
   233      highestPrioriyMetric := e.EvictWaterLine.GetHighestPriorityEvictAbleMetric()
   234      if highestPrioriyMetric != "" {
   235  		e.evictPods(ctx, &totalReleased, highestPrioriyMetric)
   236      }
   237  } else {
   238      EvictGapToWaterLines = buildGapToWaterLine(ctx.getStateFunc(), ThrottleExecutor{}, *e)
   239  	if EvictGapToWaterLines.HasUsageMissedMetric() {
   240          highestPrioriyMetric := EvictWaterLine.GetHighestPriorityEvictAbleMetric()
   241          if highestPrioriyMetric != "" {
   242              e.evictPods(ctx, &totalReleased, highestPrioriyMetric)
   243          }
   244      } else {
   245  		wg := sync.WaitGroup{}
   246          var released ReleaseResource
   247          for _, m := range metricsEvictQuantified {
   248              if MetricMap[m].SortAble {
   249                  MetricMap[m].SortFunc(e.EvictPods)
   250              } else {
   251                  execsort.GeneralSorter(e.EvictPods)
   252              }
   253      
   254              for !EvictGapToWaterLines.TargetGapsRemoved(m) {
   255                  if podinfo.HasNoExecutedPod(e.EvictPods) {
   256                      index := podinfo.GetFirstNoExecutedPod(e.EvictPods)
   257                      released = MetricMap[m].EvictFunc(&wg, ctx, index, &totalReleased, e.EvictPods)
   258      
   259                      e.EvictPods[index].HasBeenActioned = true
   260                      ctx.EvictGapToWaterLines[m] -= released[m]
   261                  }
   262              }
   263          }
   264          wg.Wait()
   265          }
   266  }
   267  ```
   268  
   269  ### Non-Goals/Future Work
   270  
   271  - 当前只支持cpu usage的精确操作,但是框架可以复用,后续可以基于精准控制的框架,实现更多维度指标的精准控制。
   272  - 在做精准控制时,目前只考虑metric本身释放量,未考虑不同metric之间的相互影响。比如压制cpu usage时,memory usage也会受到影响。如果指标非常多,不同指标之间的关系会非常复杂,所以暂时不考虑不同metric直接的相互影响。
   273  
   274  ### User Stories
   275  
   276  - 用户可以使用crane-agent进行更好的QoS保障。支持更快速的降低节点负载,以保障高优先级业务不受影响。同时对低优先级业务的压制/驱逐动作,进行精确控制,避免过度操作。
   277  - 用户可以借助实现的精准操作(压制/驱逐)的框架,在无需关心细节的情况下,通过实现自定义metric相关的属性和方法,即可方便地实现以自定义metric为核心的具有精确操作和排序能力的QoS功能。