k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/plugin/pkg/admission/limitranger/admission.go (about) 1 /* 2 Copyright 2014 The Kubernetes 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 limitranger 18 19 import ( 20 "context" 21 "fmt" 22 "io" 23 "sort" 24 "strings" 25 "time" 26 27 "golang.org/x/sync/singleflight" 28 corev1 "k8s.io/api/core/v1" 29 "k8s.io/apimachinery/pkg/api/meta" 30 "k8s.io/apimachinery/pkg/api/resource" 31 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 32 "k8s.io/apimachinery/pkg/labels" 33 "k8s.io/apimachinery/pkg/runtime" 34 utilerrors "k8s.io/apimachinery/pkg/util/errors" 35 "k8s.io/apiserver/pkg/admission" 36 genericadmissioninitailizer "k8s.io/apiserver/pkg/admission/initializer" 37 "k8s.io/apiserver/pkg/util/feature" 38 "k8s.io/client-go/informers" 39 "k8s.io/client-go/kubernetes" 40 corev1listers "k8s.io/client-go/listers/core/v1" 41 "k8s.io/utils/lru" 42 43 api "k8s.io/kubernetes/pkg/apis/core" 44 "k8s.io/kubernetes/pkg/features" 45 ) 46 47 const ( 48 limitRangerAnnotation = "kubernetes.io/limit-ranger" 49 // PluginName indicates name of admission plugin. 50 PluginName = "LimitRanger" 51 ) 52 53 // Register registers a plugin 54 func Register(plugins *admission.Plugins) { 55 plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) { 56 return NewLimitRanger(&DefaultLimitRangerActions{}) 57 }) 58 } 59 60 // LimitRanger enforces usage limits on a per resource basis in the namespace 61 type LimitRanger struct { 62 *admission.Handler 63 client kubernetes.Interface 64 actions LimitRangerActions 65 lister corev1listers.LimitRangeLister 66 67 // liveLookups holds the last few live lookups we've done to help ammortize cost on repeated lookup failures. 68 // This let's us handle the case of latent caches, by looking up actual results for a namespace on cache miss/no results. 69 // We track the lookup result here so that for repeated requests, we don't look it up very often. 70 liveLookupCache *lru.Cache 71 group singleflight.Group 72 liveTTL time.Duration 73 } 74 75 var _ admission.MutationInterface = &LimitRanger{} 76 var _ admission.ValidationInterface = &LimitRanger{} 77 78 var _ genericadmissioninitailizer.WantsExternalKubeInformerFactory = &LimitRanger{} 79 var _ genericadmissioninitailizer.WantsExternalKubeClientSet = &LimitRanger{} 80 81 type liveLookupEntry struct { 82 expiry time.Time 83 items []*corev1.LimitRange 84 } 85 86 // SetExternalKubeInformerFactory registers an informer factory into the LimitRanger 87 func (l *LimitRanger) SetExternalKubeInformerFactory(f informers.SharedInformerFactory) { 88 limitRangeInformer := f.Core().V1().LimitRanges() 89 l.SetReadyFunc(limitRangeInformer.Informer().HasSynced) 90 l.lister = limitRangeInformer.Lister() 91 } 92 93 // SetExternalKubeClientSet registers the client into LimitRanger 94 func (l *LimitRanger) SetExternalKubeClientSet(client kubernetes.Interface) { 95 l.client = client 96 } 97 98 // ValidateInitialization verifies the LimitRanger object has been properly initialized 99 func (l *LimitRanger) ValidateInitialization() error { 100 if l.lister == nil { 101 return fmt.Errorf("missing limitRange lister") 102 } 103 if l.client == nil { 104 return fmt.Errorf("missing client") 105 } 106 return nil 107 } 108 109 // Admit admits resources into cluster that do not violate any defined LimitRange in the namespace 110 func (l *LimitRanger) Admit(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) (err error) { 111 return l.runLimitFunc(a, l.actions.MutateLimit) 112 } 113 114 // Validate admits resources into cluster that do not violate any defined LimitRange in the namespace 115 func (l *LimitRanger) Validate(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) (err error) { 116 return l.runLimitFunc(a, l.actions.ValidateLimit) 117 } 118 119 func (l *LimitRanger) runLimitFunc(a admission.Attributes, limitFn func(limitRange *corev1.LimitRange, kind string, obj runtime.Object) error) (err error) { 120 if !l.actions.SupportsAttributes(a) { 121 return nil 122 } 123 124 // ignore all objects marked for deletion 125 oldObj := a.GetOldObject() 126 if oldObj != nil { 127 oldAccessor, err := meta.Accessor(oldObj) 128 if err != nil { 129 return admission.NewForbidden(a, err) 130 } 131 if oldAccessor.GetDeletionTimestamp() != nil { 132 return nil 133 } 134 } 135 136 items, err := l.GetLimitRanges(a) 137 if err != nil { 138 return err 139 } 140 141 // ensure it meets each prescribed min/max 142 for i := range items { 143 limitRange := items[i] 144 145 if !l.actions.SupportsLimit(limitRange) { 146 continue 147 } 148 149 err = limitFn(limitRange, a.GetResource().Resource, a.GetObject()) 150 if err != nil { 151 return admission.NewForbidden(a, err) 152 } 153 } 154 return nil 155 } 156 157 // GetLimitRanges returns a LimitRange object with the items held in 158 // the indexer if available, or do alive lookup of the value. 159 func (l *LimitRanger) GetLimitRanges(a admission.Attributes) ([]*corev1.LimitRange, error) { 160 items, err := l.lister.LimitRanges(a.GetNamespace()).List(labels.Everything()) 161 if err != nil { 162 return nil, admission.NewForbidden(a, fmt.Errorf("unable to %s %v at this time because there was an error enforcing limit ranges", a.GetOperation(), a.GetResource())) 163 } 164 165 // if there are no items held in our indexer, check our live-lookup LRU, if that misses, do the live lookup to prime it. 166 if len(items) == 0 { 167 lruItemObj, ok := l.liveLookupCache.Get(a.GetNamespace()) 168 if !ok || lruItemObj.(liveLookupEntry).expiry.Before(time.Now()) { 169 // Fixed: #22422 170 // use singleflight to alleviate simultaneous calls to 171 lruItemObj, err, _ = l.group.Do(a.GetNamespace(), func() (interface{}, error) { 172 liveList, err := l.client.CoreV1().LimitRanges(a.GetNamespace()).List(context.TODO(), metav1.ListOptions{}) 173 if err != nil { 174 return nil, admission.NewForbidden(a, err) 175 } 176 newEntry := liveLookupEntry{expiry: time.Now().Add(l.liveTTL)} 177 for i := range liveList.Items { 178 newEntry.items = append(newEntry.items, &liveList.Items[i]) 179 } 180 l.liveLookupCache.Add(a.GetNamespace(), newEntry) 181 return newEntry, nil 182 }) 183 if err != nil { 184 return nil, err 185 } 186 } 187 lruEntry := lruItemObj.(liveLookupEntry) 188 189 items = append(items, lruEntry.items...) 190 191 } 192 193 return items, nil 194 } 195 196 // NewLimitRanger returns an object that enforces limits based on the supplied limit function 197 func NewLimitRanger(actions LimitRangerActions) (*LimitRanger, error) { 198 liveLookupCache := lru.New(10000) 199 200 if actions == nil { 201 actions = &DefaultLimitRangerActions{} 202 } 203 204 return &LimitRanger{ 205 Handler: admission.NewHandler(admission.Create, admission.Update), 206 actions: actions, 207 liveLookupCache: liveLookupCache, 208 liveTTL: time.Duration(30 * time.Second), 209 }, nil 210 } 211 212 // defaultContainerResourceRequirements returns the default requirements for a container 213 // the requirement.Limits are taken from the LimitRange defaults (if specified) 214 // the requirement.Requests are taken from the LimitRange default request (if specified) 215 func defaultContainerResourceRequirements(limitRange *corev1.LimitRange) api.ResourceRequirements { 216 requirements := api.ResourceRequirements{} 217 requirements.Requests = api.ResourceList{} 218 requirements.Limits = api.ResourceList{} 219 220 for i := range limitRange.Spec.Limits { 221 limit := limitRange.Spec.Limits[i] 222 if limit.Type == corev1.LimitTypeContainer { 223 for k, v := range limit.DefaultRequest { 224 requirements.Requests[api.ResourceName(k)] = v.DeepCopy() 225 } 226 for k, v := range limit.Default { 227 requirements.Limits[api.ResourceName(k)] = v.DeepCopy() 228 } 229 } 230 } 231 return requirements 232 } 233 234 // mergeContainerResources handles defaulting all of the resources on a container. 235 func mergeContainerResources(container *api.Container, defaultRequirements *api.ResourceRequirements, annotationPrefix string, annotations []string) []string { 236 setRequests := []string{} 237 setLimits := []string{} 238 if container.Resources.Limits == nil { 239 container.Resources.Limits = api.ResourceList{} 240 } 241 if container.Resources.Requests == nil { 242 container.Resources.Requests = api.ResourceList{} 243 } 244 for k, v := range defaultRequirements.Limits { 245 _, found := container.Resources.Limits[k] 246 if !found { 247 container.Resources.Limits[k] = v.DeepCopy() 248 setLimits = append(setLimits, string(k)) 249 } 250 } 251 for k, v := range defaultRequirements.Requests { 252 _, found := container.Resources.Requests[k] 253 if !found { 254 container.Resources.Requests[k] = v.DeepCopy() 255 setRequests = append(setRequests, string(k)) 256 } 257 } 258 if len(setRequests) > 0 { 259 sort.Strings(setRequests) 260 a := strings.Join(setRequests, ", ") + fmt.Sprintf(" request for %s %s", annotationPrefix, container.Name) 261 annotations = append(annotations, a) 262 } 263 if len(setLimits) > 0 { 264 sort.Strings(setLimits) 265 a := strings.Join(setLimits, ", ") + fmt.Sprintf(" limit for %s %s", annotationPrefix, container.Name) 266 annotations = append(annotations, a) 267 } 268 return annotations 269 } 270 271 // mergePodResourceRequirements merges enumerated requirements with default requirements 272 // it annotates the pod with information about what requirements were modified 273 func mergePodResourceRequirements(pod *api.Pod, defaultRequirements *api.ResourceRequirements) { 274 annotations := []string{} 275 276 for i := range pod.Spec.Containers { 277 annotations = mergeContainerResources(&pod.Spec.Containers[i], defaultRequirements, "container", annotations) 278 } 279 280 for i := range pod.Spec.InitContainers { 281 annotations = mergeContainerResources(&pod.Spec.InitContainers[i], defaultRequirements, "init container", annotations) 282 } 283 284 if len(annotations) > 0 { 285 if pod.ObjectMeta.Annotations == nil { 286 pod.ObjectMeta.Annotations = make(map[string]string) 287 } 288 val := "LimitRanger plugin set: " + strings.Join(annotations, "; ") 289 pod.ObjectMeta.Annotations[limitRangerAnnotation] = val 290 } 291 } 292 293 // requestLimitEnforcedValues returns the specified values at a common precision to support comparability 294 func requestLimitEnforcedValues(requestQuantity, limitQuantity, enforcedQuantity resource.Quantity) (request, limit, enforced int64) { 295 request = requestQuantity.Value() 296 limit = limitQuantity.Value() 297 enforced = enforcedQuantity.Value() 298 // do a more precise comparison if possible (if the value won't overflow) 299 if request <= resource.MaxMilliValue && limit <= resource.MaxMilliValue && enforced <= resource.MaxMilliValue { 300 request = requestQuantity.MilliValue() 301 limit = limitQuantity.MilliValue() 302 enforced = enforcedQuantity.MilliValue() 303 } 304 return 305 } 306 307 // minConstraint enforces the min constraint over the specified resource 308 func minConstraint(limitType string, resourceName string, enforced resource.Quantity, request api.ResourceList, limit api.ResourceList) error { 309 req, reqExists := request[api.ResourceName(resourceName)] 310 lim, limExists := limit[api.ResourceName(resourceName)] 311 observedReqValue, observedLimValue, enforcedValue := requestLimitEnforcedValues(req, lim, enforced) 312 313 if !reqExists { 314 return fmt.Errorf("minimum %s usage per %s is %s. No request is specified", resourceName, limitType, enforced.String()) 315 } 316 if observedReqValue < enforcedValue { 317 return fmt.Errorf("minimum %s usage per %s is %s, but request is %s", resourceName, limitType, enforced.String(), req.String()) 318 } 319 if limExists && (observedLimValue < enforcedValue) { 320 return fmt.Errorf("minimum %s usage per %s is %s, but limit is %s", resourceName, limitType, enforced.String(), lim.String()) 321 } 322 return nil 323 } 324 325 // maxRequestConstraint enforces the max constraint over the specified resource 326 // use when specify LimitType resource doesn't recognize limit values 327 func maxRequestConstraint(limitType string, resourceName string, enforced resource.Quantity, request api.ResourceList) error { 328 req, reqExists := request[api.ResourceName(resourceName)] 329 observedReqValue, _, enforcedValue := requestLimitEnforcedValues(req, resource.Quantity{}, enforced) 330 331 if !reqExists { 332 return fmt.Errorf("maximum %s usage per %s is %s. No request is specified", resourceName, limitType, enforced.String()) 333 } 334 if observedReqValue > enforcedValue { 335 return fmt.Errorf("maximum %s usage per %s is %s, but request is %s", resourceName, limitType, enforced.String(), req.String()) 336 } 337 return nil 338 } 339 340 // maxConstraint enforces the max constraint over the specified resource 341 func maxConstraint(limitType string, resourceName string, enforced resource.Quantity, request api.ResourceList, limit api.ResourceList) error { 342 req, reqExists := request[api.ResourceName(resourceName)] 343 lim, limExists := limit[api.ResourceName(resourceName)] 344 observedReqValue, observedLimValue, enforcedValue := requestLimitEnforcedValues(req, lim, enforced) 345 346 if !limExists { 347 return fmt.Errorf("maximum %s usage per %s is %s. No limit is specified", resourceName, limitType, enforced.String()) 348 } 349 if observedLimValue > enforcedValue { 350 return fmt.Errorf("maximum %s usage per %s is %s, but limit is %s", resourceName, limitType, enforced.String(), lim.String()) 351 } 352 if reqExists && (observedReqValue > enforcedValue) { 353 return fmt.Errorf("maximum %s usage per %s is %s, but request is %s", resourceName, limitType, enforced.String(), req.String()) 354 } 355 return nil 356 } 357 358 // limitRequestRatioConstraint enforces the limit to request ratio over the specified resource 359 func limitRequestRatioConstraint(limitType string, resourceName string, enforced resource.Quantity, request api.ResourceList, limit api.ResourceList) error { 360 req, reqExists := request[api.ResourceName(resourceName)] 361 lim, limExists := limit[api.ResourceName(resourceName)] 362 observedReqValue, observedLimValue, _ := requestLimitEnforcedValues(req, lim, enforced) 363 364 if !reqExists || (observedReqValue == int64(0)) { 365 return fmt.Errorf("%s max limit to request ratio per %s is %s, but no request is specified or request is 0", resourceName, limitType, enforced.String()) 366 } 367 if !limExists || (observedLimValue == int64(0)) { 368 return fmt.Errorf("%s max limit to request ratio per %s is %s, but no limit is specified or limit is 0", resourceName, limitType, enforced.String()) 369 } 370 371 observedRatio := float64(observedLimValue) / float64(observedReqValue) 372 displayObservedRatio := observedRatio 373 maxLimitRequestRatio := float64(enforced.Value()) 374 if enforced.Value() <= resource.MaxMilliValue { 375 observedRatio = observedRatio * 1000 376 maxLimitRequestRatio = float64(enforced.MilliValue()) 377 } 378 379 if observedRatio > maxLimitRequestRatio { 380 return fmt.Errorf("%s max limit to request ratio per %s is %s, but provided ratio is %f", resourceName, limitType, enforced.String(), displayObservedRatio) 381 } 382 383 return nil 384 } 385 386 // DefaultLimitRangerActions is the default implementation of LimitRangerActions. 387 type DefaultLimitRangerActions struct{} 388 389 // ensure DefaultLimitRangerActions implements the LimitRangerActions interface. 390 var _ LimitRangerActions = &DefaultLimitRangerActions{} 391 392 // MutateLimit enforces resource requirements of incoming resources 393 // against enumerated constraints on the LimitRange. It may modify 394 // the incoming object to apply default resource requirements if not 395 // specified, and enumerated on the LimitRange 396 func (d *DefaultLimitRangerActions) MutateLimit(limitRange *corev1.LimitRange, resourceName string, obj runtime.Object) error { 397 switch resourceName { 398 case "pods": 399 return PodMutateLimitFunc(limitRange, obj.(*api.Pod)) 400 } 401 return nil 402 } 403 404 // ValidateLimit verifies the resource requirements of incoming 405 // resources against enumerated constraints on the LimitRange are 406 // valid 407 func (d *DefaultLimitRangerActions) ValidateLimit(limitRange *corev1.LimitRange, resourceName string, obj runtime.Object) error { 408 switch resourceName { 409 case "pods": 410 return PodValidateLimitFunc(limitRange, obj.(*api.Pod)) 411 case "persistentvolumeclaims": 412 return PersistentVolumeClaimValidateLimitFunc(limitRange, obj.(*api.PersistentVolumeClaim)) 413 } 414 return nil 415 } 416 417 // SupportsAttributes ignores all calls that do not deal with pod resources or storage requests (PVCs). 418 // Also ignores any call that has a subresource defined. 419 func (d *DefaultLimitRangerActions) SupportsAttributes(a admission.Attributes) bool { 420 if a.GetSubresource() != "" { 421 return false 422 } 423 424 // Since containers and initContainers cannot currently be added, removed, or updated, it is unnecessary 425 // to mutate and validate limitrange on pod updates. Trying to mutate containers or initContainers on a pod 426 // update request will always fail pod validation because those fields are immutable once the object is created. 427 if a.GetKind().GroupKind() == api.Kind("Pod") && a.GetOperation() == admission.Update { 428 return false 429 } 430 431 return a.GetKind().GroupKind() == api.Kind("Pod") || a.GetKind().GroupKind() == api.Kind("PersistentVolumeClaim") 432 } 433 434 // SupportsLimit always returns true. 435 func (d *DefaultLimitRangerActions) SupportsLimit(limitRange *corev1.LimitRange) bool { 436 return true 437 } 438 439 // PersistentVolumeClaimValidateLimitFunc enforces storage limits for PVCs. 440 // Users request storage via pvc.Spec.Resources.Requests. Min/Max is enforced by an admin with LimitRange. 441 // Claims will not be modified with default values because storage is a required part of pvc.Spec. 442 // All storage enforced values *only* apply to pvc.Spec.Resources.Requests. 443 func PersistentVolumeClaimValidateLimitFunc(limitRange *corev1.LimitRange, pvc *api.PersistentVolumeClaim) error { 444 var errs []error 445 for i := range limitRange.Spec.Limits { 446 limit := limitRange.Spec.Limits[i] 447 limitType := limit.Type 448 if limitType == corev1.LimitTypePersistentVolumeClaim { 449 for k, v := range limit.Min { 450 // normal usage of minConstraint. pvc.Spec.Resources.Limits is not recognized as user input 451 if err := minConstraint(string(limitType), string(k), v, pvc.Spec.Resources.Requests, api.ResourceList{}); err != nil { 452 errs = append(errs, err) 453 } 454 } 455 for k, v := range limit.Max { 456 // We want to enforce the max of the LimitRange against what 457 // the user requested. 458 if err := maxRequestConstraint(string(limitType), string(k), v, pvc.Spec.Resources.Requests); err != nil { 459 errs = append(errs, err) 460 } 461 } 462 } 463 } 464 return utilerrors.NewAggregate(errs) 465 } 466 467 // PodMutateLimitFunc sets resource requirements enumerated by the pod against 468 // the specified LimitRange. The pod may be modified to apply default resource 469 // requirements if not specified, and enumerated on the LimitRange 470 func PodMutateLimitFunc(limitRange *corev1.LimitRange, pod *api.Pod) error { 471 defaultResources := defaultContainerResourceRequirements(limitRange) 472 mergePodResourceRequirements(pod, &defaultResources) 473 return nil 474 } 475 476 // PodValidateLimitFunc enforces resource requirements enumerated by the pod against 477 // the specified LimitRange. 478 func PodValidateLimitFunc(limitRange *corev1.LimitRange, pod *api.Pod) error { 479 var errs []error 480 481 for i := range limitRange.Spec.Limits { 482 limit := limitRange.Spec.Limits[i] 483 limitType := limit.Type 484 // enforce container limits 485 if limitType == corev1.LimitTypeContainer { 486 for j := range pod.Spec.Containers { 487 container := &pod.Spec.Containers[j] 488 for k, v := range limit.Min { 489 if err := minConstraint(string(limitType), string(k), v, container.Resources.Requests, container.Resources.Limits); err != nil { 490 errs = append(errs, err) 491 } 492 } 493 for k, v := range limit.Max { 494 if err := maxConstraint(string(limitType), string(k), v, container.Resources.Requests, container.Resources.Limits); err != nil { 495 errs = append(errs, err) 496 } 497 } 498 for k, v := range limit.MaxLimitRequestRatio { 499 if err := limitRequestRatioConstraint(string(limitType), string(k), v, container.Resources.Requests, container.Resources.Limits); err != nil { 500 errs = append(errs, err) 501 } 502 } 503 } 504 for j := range pod.Spec.InitContainers { 505 container := &pod.Spec.InitContainers[j] 506 for k, v := range limit.Min { 507 if err := minConstraint(string(limitType), string(k), v, container.Resources.Requests, container.Resources.Limits); err != nil { 508 errs = append(errs, err) 509 } 510 } 511 for k, v := range limit.Max { 512 if err := maxConstraint(string(limitType), string(k), v, container.Resources.Requests, container.Resources.Limits); err != nil { 513 errs = append(errs, err) 514 } 515 } 516 for k, v := range limit.MaxLimitRequestRatio { 517 if err := limitRequestRatioConstraint(string(limitType), string(k), v, container.Resources.Requests, container.Resources.Limits); err != nil { 518 errs = append(errs, err) 519 } 520 } 521 } 522 } 523 524 // enforce pod limits on init containers 525 if limitType == corev1.LimitTypePod { 526 opts := podResourcesOptions{ 527 InPlacePodVerticalScalingEnabled: feature.DefaultFeatureGate.Enabled(features.InPlacePodVerticalScaling), 528 } 529 podRequests := podRequests(pod, opts) 530 podLimits := podLimits(pod, opts) 531 for k, v := range limit.Min { 532 if err := minConstraint(string(limitType), string(k), v, podRequests, podLimits); err != nil { 533 errs = append(errs, err) 534 } 535 } 536 for k, v := range limit.Max { 537 if err := maxConstraint(string(limitType), string(k), v, podRequests, podLimits); err != nil { 538 errs = append(errs, err) 539 } 540 } 541 for k, v := range limit.MaxLimitRequestRatio { 542 if err := limitRequestRatioConstraint(string(limitType), string(k), v, podRequests, podLimits); err != nil { 543 errs = append(errs, err) 544 } 545 } 546 } 547 } 548 return utilerrors.NewAggregate(errs) 549 } 550 551 type podResourcesOptions struct { 552 // InPlacePodVerticalScalingEnabled indicates that the in-place pod vertical scaling feature gate is enabled. 553 InPlacePodVerticalScalingEnabled bool 554 } 555 556 // podRequests is a simplified version of pkg/api/v1/resource/PodRequests that operates against the core version of 557 // pod. Any changes to that calculation should be reflected here. 558 // TODO: Maybe we can consider doing a partial conversion of the pod to a v1 559 // type and then using the pkg/api/v1/resource/PodRequests. 560 func podRequests(pod *api.Pod, opts podResourcesOptions) api.ResourceList { 561 reqs := api.ResourceList{} 562 563 var containerStatuses map[string]*api.ContainerStatus 564 if opts.InPlacePodVerticalScalingEnabled { 565 containerStatuses = map[string]*api.ContainerStatus{} 566 for i := range pod.Status.ContainerStatuses { 567 containerStatuses[pod.Status.ContainerStatuses[i].Name] = &pod.Status.ContainerStatuses[i] 568 } 569 } 570 571 for _, container := range pod.Spec.Containers { 572 containerReqs := container.Resources.Requests 573 if opts.InPlacePodVerticalScalingEnabled { 574 cs, found := containerStatuses[container.Name] 575 if found { 576 if pod.Status.Resize == api.PodResizeStatusInfeasible { 577 containerReqs = cs.AllocatedResources 578 } else { 579 containerReqs = max(container.Resources.Requests, cs.AllocatedResources) 580 } 581 } 582 } 583 584 addResourceList(reqs, containerReqs) 585 } 586 587 restartableInitCotnainerReqs := api.ResourceList{} 588 initContainerReqs := api.ResourceList{} 589 // init containers define the minimum of any resource 590 // Note: In-place resize is not allowed for InitContainers, so no need to check for ResizeStatus value 591 for _, container := range pod.Spec.InitContainers { 592 containerReqs := container.Resources.Requests 593 594 if container.RestartPolicy != nil && *container.RestartPolicy == api.ContainerRestartPolicyAlways { 595 // and add them to the resulting cumulative container requests 596 addResourceList(reqs, containerReqs) 597 598 // track our cumulative restartable init container resources 599 addResourceList(restartableInitCotnainerReqs, containerReqs) 600 containerReqs = restartableInitCotnainerReqs 601 } else { 602 tmp := api.ResourceList{} 603 addResourceList(tmp, containerReqs) 604 addResourceList(tmp, restartableInitCotnainerReqs) 605 containerReqs = tmp 606 } 607 608 maxResourceList(initContainerReqs, containerReqs) 609 } 610 611 maxResourceList(reqs, initContainerReqs) 612 return reqs 613 } 614 615 // podLimits is a simplified version of pkg/api/v1/resource/PodLimits that operates against the core version of 616 // pod. Any changes to that calculation should be reflected here. 617 // TODO: Maybe we can consider doing a partial conversion of the pod to a v1 618 // type and then using the pkg/api/v1/resource/PodLimits. 619 func podLimits(pod *api.Pod, opts podResourcesOptions) api.ResourceList { 620 limits := api.ResourceList{} 621 622 for _, container := range pod.Spec.Containers { 623 addResourceList(limits, container.Resources.Limits) 624 } 625 626 restartableInitContainerLimits := api.ResourceList{} 627 initContainerLimits := api.ResourceList{} 628 // init containers define the minimum of any resource 629 for _, container := range pod.Spec.InitContainers { 630 containerLimits := container.Resources.Limits 631 // Is the init container marked as a sidecar? 632 if container.RestartPolicy != nil && *container.RestartPolicy == api.ContainerRestartPolicyAlways { 633 addResourceList(limits, containerLimits) 634 635 // track our cumulative restartable init container resources 636 addResourceList(restartableInitContainerLimits, containerLimits) 637 containerLimits = restartableInitContainerLimits 638 } else { 639 tmp := api.ResourceList{} 640 addResourceList(tmp, containerLimits) 641 addResourceList(tmp, restartableInitContainerLimits) 642 containerLimits = tmp 643 } 644 maxResourceList(initContainerLimits, containerLimits) 645 } 646 647 maxResourceList(limits, initContainerLimits) 648 649 return limits 650 } 651 652 // addResourceList adds the resources in newList to list. 653 func addResourceList(list, newList api.ResourceList) { 654 for name, quantity := range newList { 655 if value, ok := list[name]; !ok { 656 list[name] = quantity.DeepCopy() 657 } else { 658 value.Add(quantity) 659 list[name] = value 660 } 661 } 662 } 663 664 // maxResourceList sets list to the greater of list/newList for every resource in newList 665 func maxResourceList(list, newList api.ResourceList) { 666 for name, quantity := range newList { 667 if value, ok := list[name]; !ok || quantity.Cmp(value) > 0 { 668 list[name] = quantity.DeepCopy() 669 } 670 } 671 } 672 673 // max returns the result of max(a, b) for each named resource and is only used if we can't 674 // accumulate into an existing resource list 675 func max(a api.ResourceList, b api.ResourceList) api.ResourceList { 676 result := api.ResourceList{} 677 for key, value := range a { 678 if other, found := b[key]; found { 679 if value.Cmp(other) <= 0 { 680 result[key] = other.DeepCopy() 681 continue 682 } 683 } 684 result[key] = value.DeepCopy() 685 } 686 for key, value := range b { 687 if _, found := result[key]; !found { 688 result[key] = value.DeepCopy() 689 } 690 } 691 return result 692 }