github.com/kubewharf/katalyst-core@v0.5.3/pkg/agent/evictionmanager/plugin/resource/resources.go (about)

     1  /*
     2  Copyright 2022 The Katalyst Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package resource
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"sort"
    23  
    24  	v1 "k8s.io/api/core/v1"
    25  	"k8s.io/apimachinery/pkg/util/sets"
    26  	"k8s.io/klog/v2"
    27  
    28  	pluginapi "github.com/kubewharf/katalyst-api/pkg/protocol/evictionplugin/v1alpha1"
    29  	"github.com/kubewharf/katalyst-core/pkg/metaserver"
    30  	"github.com/kubewharf/katalyst-core/pkg/metrics"
    31  	"github.com/kubewharf/katalyst-core/pkg/util/general"
    32  	"github.com/kubewharf/katalyst-core/pkg/util/native"
    33  )
    34  
    35  const (
    36  	MetricsNamePodCount         = "pod_count"
    37  	MetricsNamePodResource      = "pod_resource"
    38  	MetricsNameGetResourceEmpty = "get_resource_empty"
    39  )
    40  
    41  type ResourcesGetter func(ctx context.Context) (v1.ResourceList, error)
    42  
    43  type ThresholdGetter func(resourceName v1.ResourceName) *float64
    44  
    45  type GracePeriodGetter func() int64
    46  
    47  // ResourcesEvictionPlugin implements EvictPlugin interface it trigger
    48  // pod eviction logic based on the tolerance of resources.
    49  type ResourcesEvictionPlugin struct {
    50  	// emitter is used to emit metrics.
    51  	emitter metrics.MetricEmitter
    52  
    53  	// thresholdGetter is used to get the threshold of resources.
    54  	thresholdGetter                     ThresholdGetter
    55  	resourcesGetter                     ResourcesGetter
    56  	deletionGracePeriodGetter           GracePeriodGetter
    57  	thresholdMetToleranceDurationGetter GracePeriodGetter
    58  
    59  	skipZeroQuantityResourceNames sets.String
    60  	podFilter                     func(pod *v1.Pod) (bool, error)
    61  
    62  	pluginName string
    63  
    64  	metaServer *metaserver.MetaServer
    65  }
    66  
    67  func NewResourcesEvictionPlugin(pluginName string, metaServer *metaserver.MetaServer,
    68  	emitter metrics.MetricEmitter, resourcesGetter ResourcesGetter, thresholdGetter ThresholdGetter,
    69  	deletionGracePeriodGetter GracePeriodGetter, thresholdMetToleranceDurationGetter GracePeriodGetter,
    70  	skipZeroQuantityResourceNames sets.String,
    71  	podFilter func(pod *v1.Pod) (bool, error),
    72  ) *ResourcesEvictionPlugin {
    73  	// use the given threshold to override the default configurations
    74  	plugin := &ResourcesEvictionPlugin{
    75  		pluginName:                          pluginName,
    76  		emitter:                             emitter,
    77  		metaServer:                          metaServer,
    78  		resourcesGetter:                     resourcesGetter,
    79  		thresholdGetter:                     thresholdGetter,
    80  		deletionGracePeriodGetter:           deletionGracePeriodGetter,
    81  		thresholdMetToleranceDurationGetter: thresholdMetToleranceDurationGetter,
    82  		skipZeroQuantityResourceNames:       skipZeroQuantityResourceNames,
    83  		podFilter:                           podFilter,
    84  	}
    85  	return plugin
    86  }
    87  
    88  func (b *ResourcesEvictionPlugin) Name() string {
    89  	if b == nil {
    90  		return ""
    91  	}
    92  
    93  	return b.pluginName
    94  }
    95  
    96  func (b *ResourcesEvictionPlugin) Start() {
    97  	return
    98  }
    99  
   100  // ThresholdMet evict pods when the beset effort resources usage is greater than
   101  // the supply (after considering toleration).
   102  func (b *ResourcesEvictionPlugin) ThresholdMet(ctx context.Context) (*pluginapi.ThresholdMetResponse, error) {
   103  	activePods, err := b.metaServer.GetPodList(ctx, native.PodIsActive)
   104  	if err != nil {
   105  		errMsg := fmt.Sprintf("failed to list pods from metaServer: %v", err)
   106  		klog.Errorf("[%s] %s", b.pluginName, errMsg)
   107  		return nil, fmt.Errorf(errMsg)
   108  	}
   109  
   110  	filteredPods := native.FilterPods(activePods, b.podFilter)
   111  
   112  	klog.Infof("[%s] total %d filtered pods out-of %d running pods", b.pluginName, len(filteredPods), len(activePods))
   113  
   114  	_ = b.emitter.StoreInt64(MetricsNamePodCount, int64(len(filteredPods)), metrics.MetricTypeNameRaw)
   115  	if len(filteredPods) == 0 {
   116  		return &pluginapi.ThresholdMetResponse{
   117  			MetType: pluginapi.ThresholdMetType_NOT_MET,
   118  		}, nil
   119  	}
   120  
   121  	allocatable, err := b.resourcesGetter(ctx)
   122  	if err != nil {
   123  		errMsg := fmt.Sprintf("failed to get resources: %v", err)
   124  		klog.Errorf("[%s] %s", b.pluginName, errMsg)
   125  		return nil, fmt.Errorf(errMsg)
   126  	}
   127  
   128  	native.EmitResourceMetrics(MetricsNamePodResource, allocatable, map[string]string{
   129  		"type": "allocatable",
   130  	}, b.emitter)
   131  
   132  	// allocatable resources in cnr status is empty and it's a abnormal case.
   133  	// to avoid evict mistakenly, we return threshold not met, and emit metrics.
   134  	// [TODO] we should consider refining it when actually finding cases.
   135  	if allocatable == nil || len(allocatable) == 0 {
   136  		_ = b.emitter.StoreInt64(MetricsNameGetResourceEmpty, 1, metrics.MetricTypeNameCount, metrics.MetricTag{
   137  			Key: "pluginName", Val: b.pluginName,
   138  		})
   139  		return &pluginapi.ThresholdMetResponse{
   140  			MetType: pluginapi.ThresholdMetType_NOT_MET,
   141  		}, nil
   142  	}
   143  
   144  	// use requests (rather than limits) as used resource
   145  	usedResources := make(v1.ResourceList)
   146  	for _, pod := range filteredPods {
   147  		resources := native.SumUpPodRequestResources(pod)
   148  		usedResources = native.AddResources(usedResources, resources)
   149  
   150  		native.EmitResourceMetrics(MetricsNamePodResource, resources, map[string]string{
   151  			"pluginName": b.pluginName,
   152  			"namespace":  pod.Namespace,
   153  			"name":       pod.Name,
   154  			"type":       "pod",
   155  		}, b.emitter)
   156  	}
   157  	klog.Infof("[%s] resources: allocatable %+v usedResources %+v", b.pluginName, allocatable, usedResources)
   158  
   159  	native.EmitResourceMetrics(MetricsNamePodResource, usedResources, map[string]string{
   160  		"pluginName": b.pluginName,
   161  		"type":       "used",
   162  	}, b.emitter)
   163  
   164  	for resourceName, usedQuantity := range usedResources {
   165  		totalQuantity, ok := allocatable[resourceName]
   166  		if !ok {
   167  			klog.Warningf("[%s] used resource: %s doesn't exist in allocatable", b.pluginName, resourceName)
   168  			continue
   169  		}
   170  
   171  		total := float64((&totalQuantity).Value())
   172  
   173  		if total <= 0 && b.skipZeroQuantityResourceNames.Has(string(resourceName)) {
   174  			klog.Warningf("[%s] skip resource: %s with total: %.2f", b.pluginName, total)
   175  			continue
   176  		}
   177  
   178  		used := float64((&usedQuantity).Value())
   179  
   180  		// get resource threshold (i.e. tolerance) for each resource
   181  		// if nil, eviction will not be triggered.
   182  		thresholdRate := b.thresholdGetter(resourceName)
   183  		if thresholdRate == nil {
   184  			continue
   185  		}
   186  
   187  		thresholdValue := *thresholdRate * total
   188  		klog.Infof("[%s] resources %v: total %v, used %v, thresholdRate %v, thresholdValue: %v", b.pluginName,
   189  			resourceName, total, used, *thresholdRate, thresholdValue)
   190  
   191  		exceededValue := thresholdValue - used
   192  		if exceededValue < 0 {
   193  			klog.Infof("[%s] resources %v exceeded: total %v, used %v, thresholdRate %v, thresholdValue: %v", b.pluginName,
   194  				resourceName, total, used, *thresholdRate, thresholdValue)
   195  
   196  			return &pluginapi.ThresholdMetResponse{
   197  				ThresholdValue:     thresholdValue,
   198  				ObservedValue:      used,
   199  				ThresholdOperator:  pluginapi.ThresholdOperator_GREATER_THAN,
   200  				MetType:            pluginapi.ThresholdMetType_HARD_MET,
   201  				EvictionScope:      string(resourceName),
   202  				GracePeriodSeconds: b.thresholdMetToleranceDurationGetter(),
   203  			}, nil
   204  		}
   205  	}
   206  
   207  	return &pluginapi.ThresholdMetResponse{
   208  		MetType: pluginapi.ThresholdMetType_NOT_MET,
   209  	}, nil
   210  }
   211  
   212  func (b *ResourcesEvictionPlugin) GetTopEvictionPods(_ context.Context, request *pluginapi.GetTopEvictionPodsRequest) (*pluginapi.GetTopEvictionPodsResponse, error) {
   213  	if request == nil {
   214  		return nil, fmt.Errorf("GetTopEvictionPods got nil request")
   215  	}
   216  
   217  	if len(request.ActivePods) == 0 {
   218  		klog.Warningf("[%s] GetTopEvictionPods got empty active pods list", b.pluginName)
   219  		return &pluginapi.GetTopEvictionPodsResponse{}, nil
   220  	}
   221  
   222  	activeFilteredPods := native.FilterPods(request.ActivePods, b.podFilter)
   223  
   224  	sort.Slice(activeFilteredPods, func(i, j int) bool {
   225  		valueI, valueJ := int64(0), int64(0)
   226  
   227  		resourceI, resourceJ := native.SumUpPodRequestResources(activeFilteredPods[i]), native.SumUpPodRequestResources(activeFilteredPods[j])
   228  		if quantity, ok := resourceI[v1.ResourceName(request.EvictionScope)]; ok {
   229  			valueI = (&quantity).Value()
   230  		}
   231  		if quantity, ok := resourceJ[v1.ResourceName(request.EvictionScope)]; ok {
   232  			valueJ = (&quantity).Value()
   233  		}
   234  		return valueI > valueJ
   235  	})
   236  
   237  	retLen := general.MinUInt64(request.TopN, uint64(len(activeFilteredPods)))
   238  
   239  	var deletionOptions *pluginapi.DeletionOptions
   240  	if gracePeriod := b.deletionGracePeriodGetter(); gracePeriod > 0 {
   241  		deletionOptions = &pluginapi.DeletionOptions{
   242  			GracePeriodSeconds: gracePeriod,
   243  		}
   244  	}
   245  
   246  	return &pluginapi.GetTopEvictionPodsResponse{
   247  		TargetPods:      activeFilteredPods[:retLen],
   248  		DeletionOptions: deletionOptions,
   249  	}, nil
   250  }
   251  
   252  func (b *ResourcesEvictionPlugin) GetEvictPods(_ context.Context, request *pluginapi.GetEvictPodsRequest) (*pluginapi.GetEvictPodsResponse, error) {
   253  	if request == nil {
   254  		return nil, fmt.Errorf("GetEvictPods got nil request")
   255  	}
   256  
   257  	return &pluginapi.GetEvictPodsResponse{}, nil
   258  }