1
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
50 PluginName = "LimitRanger"
51 )
52
53
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
61 type LimitRanger struct {
62 *admission.Handler
63 client kubernetes.Interface
64 actions LimitRangerActions
65 lister corev1listers.LimitRangeLister
66
67
68
69
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
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
94 func (l *LimitRanger) SetExternalKubeClientSet(client kubernetes.Interface) {
95 l.client = client
96 }
97
98
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
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
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
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
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
158
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
166 if len(items) == 0 {
167 lruItemObj, ok := l.liveLookupCache.Get(a.GetNamespace())
168 if !ok || lruItemObj.(liveLookupEntry).expiry.Before(time.Now()) {
169
170
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
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
213
214
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
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
272
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
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
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
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
326
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
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
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
387 type DefaultLimitRangerActions struct{}
388
389
390 var _ LimitRangerActions = &DefaultLimitRangerActions{}
391
392
393
394
395
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
405
406
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
418
419 func (d *DefaultLimitRangerActions) SupportsAttributes(a admission.Attributes) bool {
420 if a.GetSubresource() != "" {
421 return false
422 }
423
424
425
426
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
435 func (d *DefaultLimitRangerActions) SupportsLimit(limitRange *corev1.LimitRange) bool {
436 return true
437 }
438
439
440
441
442
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
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
457
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
468
469
470 func PodMutateLimitFunc(limitRange *corev1.LimitRange, pod *api.Pod) error {
471 defaultResources := defaultContainerResourceRequirements(limitRange)
472 mergePodResourceRequirements(pod, &defaultResources)
473 return nil
474 }
475
476
477
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
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
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
553 InPlacePodVerticalScalingEnabled bool
554 }
555
556
557
558
559
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
590
591 for _, container := range pod.Spec.InitContainers {
592 containerReqs := container.Resources.Requests
593
594 if container.RestartPolicy != nil && *container.RestartPolicy == api.ContainerRestartPolicyAlways {
595
596 addResourceList(reqs, containerReqs)
597
598
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
616
617
618
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
629 for _, container := range pod.Spec.InitContainers {
630 containerLimits := container.Resources.Limits
631
632 if container.RestartPolicy != nil && *container.RestartPolicy == api.ContainerRestartPolicyAlways {
633 addResourceList(limits, containerLimits)
634
635
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
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
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
674
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 }
693
View as plain text