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 }