2015-01-23 01:31:31 +00:00
|
|
|
/*
|
2015-05-01 16:19:44 +00:00
|
|
|
Copyright 2014 The Kubernetes Authors All rights reserved.
|
2015-01-23 01:31:31 +00:00
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
package limitranger
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
|
|
|
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/admission"
|
|
|
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
2015-02-16 15:54:29 +00:00
|
|
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
|
2015-01-23 01:31:31 +00:00
|
|
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource"
|
|
|
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
2015-02-16 15:54:29 +00:00
|
|
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/client/cache"
|
2015-03-15 21:51:41 +00:00
|
|
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
|
2015-01-23 01:31:31 +00:00
|
|
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
|
|
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
2015-02-16 15:54:29 +00:00
|
|
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
|
2015-01-23 01:31:31 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
admission.RegisterPlugin("LimitRanger", func(client client.Interface, config io.Reader) (admission.Interface, error) {
|
2015-03-31 14:12:57 +00:00
|
|
|
return NewLimitRanger(client, Limit), nil
|
2015-01-23 01:31:31 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// limitRanger enforces usage limits on a per resource basis in the namespace
|
|
|
|
type limitRanger struct {
|
2015-05-15 14:48:33 +00:00
|
|
|
*admission.Handler
|
2015-01-23 01:31:31 +00:00
|
|
|
client client.Interface
|
|
|
|
limitFunc LimitFunc
|
2015-02-16 15:54:29 +00:00
|
|
|
indexer cache.Indexer
|
2015-01-23 01:31:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Admit admits resources into cluster that do not violate any defined LimitRange in the namespace
|
|
|
|
func (l *limitRanger) Admit(a admission.Attributes) (err error) {
|
2015-02-16 15:54:29 +00:00
|
|
|
obj := a.GetObject()
|
|
|
|
resource := a.GetResource()
|
|
|
|
name := "Unknown"
|
|
|
|
if obj != nil {
|
|
|
|
name, _ = meta.NewAccessor().Name(obj)
|
2015-04-14 22:03:26 +00:00
|
|
|
if len(name) == 0 {
|
|
|
|
name, _ = meta.NewAccessor().GenerateName(obj)
|
|
|
|
}
|
2015-02-16 15:54:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
key := &api.LimitRange{
|
|
|
|
ObjectMeta: api.ObjectMeta{
|
|
|
|
Namespace: a.GetNamespace(),
|
|
|
|
Name: "",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
items, err := l.indexer.Index("namespace", key)
|
2015-01-23 01:31:31 +00:00
|
|
|
if err != nil {
|
2015-04-14 22:03:26 +00:00
|
|
|
return admission.NewForbidden(a, fmt.Errorf("Unable to %s %s at this time because there was an error enforcing limit ranges", a.GetOperation(), resource))
|
2015-02-16 15:54:29 +00:00
|
|
|
}
|
|
|
|
if len(items) == 0 {
|
|
|
|
return nil
|
2015-01-23 01:31:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// ensure it meets each prescribed min/max
|
2015-02-16 15:54:29 +00:00
|
|
|
for i := range items {
|
|
|
|
limitRange := items[i].(*api.LimitRange)
|
2015-01-30 13:16:46 +00:00
|
|
|
err = l.limitFunc(limitRange, a.GetResource(), a.GetObject())
|
2015-01-23 01:31:31 +00:00
|
|
|
if err != nil {
|
2015-04-14 22:03:26 +00:00
|
|
|
return admission.NewForbidden(a, err)
|
2015-01-23 01:31:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewLimitRanger returns an object that enforces limits based on the supplied limit function
|
|
|
|
func NewLimitRanger(client client.Interface, limitFunc LimitFunc) admission.Interface {
|
2015-02-16 15:54:29 +00:00
|
|
|
lw := &cache.ListWatch{
|
|
|
|
ListFunc: func() (runtime.Object, error) {
|
|
|
|
return client.LimitRanges(api.NamespaceAll).List(labels.Everything())
|
|
|
|
},
|
|
|
|
WatchFunc: func(resourceVersion string) (watch.Interface, error) {
|
2015-03-15 21:51:41 +00:00
|
|
|
return client.LimitRanges(api.NamespaceAll).Watch(labels.Everything(), fields.Everything(), resourceVersion)
|
2015-02-16 15:54:29 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
indexer, reflector := cache.NewNamespaceKeyedIndexerAndReflector(lw, &api.LimitRange{}, 0)
|
|
|
|
reflector.Run()
|
2015-05-15 14:48:33 +00:00
|
|
|
return &limitRanger{
|
|
|
|
Handler: admission.NewHandler(admission.Create, admission.Update),
|
|
|
|
client: client,
|
|
|
|
limitFunc: limitFunc,
|
|
|
|
indexer: indexer,
|
|
|
|
}
|
2015-01-23 01:31:31 +00:00
|
|
|
}
|
|
|
|
|
2015-05-15 14:48:33 +00:00
|
|
|
// Min returns the lesser of its 2 arguments
|
2015-01-23 01:31:31 +00:00
|
|
|
func Min(a int64, b int64) int64 {
|
|
|
|
if a < b {
|
|
|
|
return a
|
|
|
|
}
|
|
|
|
return b
|
|
|
|
}
|
|
|
|
|
2015-05-15 14:48:33 +00:00
|
|
|
// Max returns the greater of its 2 arguments
|
2015-01-23 01:31:31 +00:00
|
|
|
func Max(a int64, b int64) int64 {
|
|
|
|
if a > b {
|
|
|
|
return a
|
|
|
|
}
|
|
|
|
return b
|
|
|
|
}
|
|
|
|
|
2015-03-31 14:12:57 +00:00
|
|
|
// Limit enforces resource requirements of incoming resources against enumerated constraints
|
|
|
|
// on the LimitRange. It may modify the incoming object to apply default resource requirements
|
|
|
|
// if not specified, and enumerated on the LimitRange
|
|
|
|
func Limit(limitRange *api.LimitRange, resourceName string, obj runtime.Object) error {
|
|
|
|
switch resourceName {
|
|
|
|
case "pods":
|
|
|
|
return PodLimitFunc(limitRange, obj.(*api.Pod))
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// defaultContainerResourceRequirements returns the default requirements for a container
|
|
|
|
// the requirement.Limits are taken from the LimitRange defaults (if specified)
|
|
|
|
// the requirement.Requests are taken from the LimitRange min (if specified)
|
|
|
|
func defaultContainerResourceRequirements(limitRange *api.LimitRange) api.ResourceRequirements {
|
|
|
|
requirements := api.ResourceRequirements{}
|
|
|
|
requirements.Limits = api.ResourceList{}
|
|
|
|
requirements.Requests = api.ResourceList{}
|
|
|
|
|
|
|
|
for i := range limitRange.Spec.Limits {
|
|
|
|
limit := limitRange.Spec.Limits[i]
|
|
|
|
if limit.Type == api.LimitTypeContainer {
|
|
|
|
for k, v := range limit.Default {
|
|
|
|
value := v.Copy()
|
|
|
|
requirements.Limits[k] = *value
|
|
|
|
}
|
|
|
|
}
|
2015-01-23 01:31:31 +00:00
|
|
|
}
|
2015-03-31 14:12:57 +00:00
|
|
|
return requirements
|
|
|
|
}
|
|
|
|
|
|
|
|
// mergePodResourceRequirements merges enumerated requirements with default requirements
|
|
|
|
func mergePodResourceRequirements(pod *api.Pod, defaultRequirements *api.ResourceRequirements) {
|
|
|
|
for i := range pod.Spec.Containers {
|
2015-04-14 20:12:24 +00:00
|
|
|
container := &pod.Spec.Containers[i]
|
|
|
|
if container.Resources.Limits == nil {
|
|
|
|
container.Resources.Limits = api.ResourceList{}
|
|
|
|
}
|
|
|
|
if container.Resources.Requests == nil {
|
|
|
|
container.Resources.Requests = api.ResourceList{}
|
|
|
|
}
|
2015-03-31 14:12:57 +00:00
|
|
|
for k, v := range defaultRequirements.Limits {
|
|
|
|
_, found := container.Resources.Limits[k]
|
|
|
|
if !found {
|
|
|
|
container.Resources.Limits[k] = *v.Copy()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for k, v := range defaultRequirements.Requests {
|
|
|
|
_, found := container.Resources.Requests[k]
|
|
|
|
if !found {
|
|
|
|
container.Resources.Requests[k] = *v.Copy()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// PodLimitFunc enforces resource requirements enumerated by the pod against
|
|
|
|
// the specified LimitRange. The pod may be modified to apply default resource
|
|
|
|
// requirements if not specified, and enumerated on the LimitRange
|
|
|
|
func PodLimitFunc(limitRange *api.LimitRange, pod *api.Pod) error {
|
2015-01-23 01:31:31 +00:00
|
|
|
|
2015-03-31 14:12:57 +00:00
|
|
|
defaultResources := defaultContainerResourceRequirements(limitRange)
|
|
|
|
mergePodResourceRequirements(pod, &defaultResources)
|
2015-01-23 01:31:31 +00:00
|
|
|
|
|
|
|
podCPU := int64(0)
|
|
|
|
podMem := int64(0)
|
|
|
|
|
|
|
|
minContainerCPU := int64(0)
|
|
|
|
minContainerMem := int64(0)
|
|
|
|
maxContainerCPU := int64(0)
|
|
|
|
maxContainerMem := int64(0)
|
|
|
|
|
|
|
|
for i := range pod.Spec.Containers {
|
2015-04-14 20:12:24 +00:00
|
|
|
container := &pod.Spec.Containers[i]
|
2015-01-25 04:19:36 +00:00
|
|
|
containerCPU := container.Resources.Limits.Cpu().MilliValue()
|
|
|
|
containerMem := container.Resources.Limits.Memory().Value()
|
2015-01-23 01:31:31 +00:00
|
|
|
|
|
|
|
if i == 0 {
|
|
|
|
minContainerCPU = containerCPU
|
|
|
|
minContainerMem = containerMem
|
|
|
|
maxContainerCPU = containerCPU
|
|
|
|
maxContainerMem = containerMem
|
|
|
|
}
|
|
|
|
|
2015-01-25 04:19:36 +00:00
|
|
|
podCPU = podCPU + container.Resources.Limits.Cpu().MilliValue()
|
|
|
|
podMem = podMem + container.Resources.Limits.Memory().Value()
|
2015-01-23 01:31:31 +00:00
|
|
|
|
|
|
|
minContainerCPU = Min(containerCPU, minContainerCPU)
|
|
|
|
minContainerMem = Min(containerMem, minContainerMem)
|
|
|
|
maxContainerCPU = Max(containerCPU, maxContainerCPU)
|
|
|
|
maxContainerMem = Max(containerMem, maxContainerMem)
|
|
|
|
}
|
|
|
|
|
|
|
|
for i := range limitRange.Spec.Limits {
|
|
|
|
limit := limitRange.Spec.Limits[i]
|
2015-01-27 21:54:50 +00:00
|
|
|
for _, minOrMax := range []string{"Min", "Max"} {
|
|
|
|
var rl api.ResourceList
|
|
|
|
switch minOrMax {
|
|
|
|
case "Min":
|
|
|
|
rl = limit.Min
|
|
|
|
case "Max":
|
|
|
|
rl = limit.Max
|
2015-01-23 01:31:31 +00:00
|
|
|
}
|
2015-01-27 21:54:50 +00:00
|
|
|
for k, v := range rl {
|
|
|
|
observed := int64(0)
|
|
|
|
enforced := int64(0)
|
|
|
|
var err error
|
|
|
|
switch k {
|
|
|
|
case api.ResourceMemory:
|
|
|
|
enforced = v.Value()
|
|
|
|
switch limit.Type {
|
|
|
|
case api.LimitTypePod:
|
|
|
|
observed = podMem
|
|
|
|
err = fmt.Errorf("%simum memory usage per pod is %s", minOrMax, v.String())
|
|
|
|
case api.LimitTypeContainer:
|
|
|
|
observed = maxContainerMem
|
|
|
|
err = fmt.Errorf("%simum memory usage per container is %s", minOrMax, v.String())
|
|
|
|
}
|
|
|
|
case api.ResourceCPU:
|
|
|
|
enforced = v.MilliValue()
|
|
|
|
switch limit.Type {
|
|
|
|
case api.LimitTypePod:
|
|
|
|
observed = podCPU
|
|
|
|
err = fmt.Errorf("%simum CPU usage per pod is %s, but requested %s", minOrMax, v.String(), resource.NewMilliQuantity(observed, resource.DecimalSI))
|
|
|
|
case api.LimitTypeContainer:
|
|
|
|
observed = maxContainerCPU
|
|
|
|
err = fmt.Errorf("%simum CPU usage per container is %s", minOrMax, v.String())
|
|
|
|
}
|
2015-01-23 01:31:31 +00:00
|
|
|
}
|
2015-01-27 21:54:50 +00:00
|
|
|
switch minOrMax {
|
|
|
|
case "Min":
|
|
|
|
if observed < enforced {
|
2015-04-14 22:03:26 +00:00
|
|
|
return err
|
2015-01-27 21:54:50 +00:00
|
|
|
}
|
|
|
|
case "Max":
|
|
|
|
if observed > enforced {
|
2015-04-14 22:03:26 +00:00
|
|
|
return err
|
2015-01-27 21:54:50 +00:00
|
|
|
}
|
2015-01-23 01:31:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|