Skip to content

Commit 4e7eda3

Browse files
committed
Do not mutate resource limits below namespace minimums
1 parent a96fe1e commit 4e7eda3

File tree

1 file changed

+88
-8
lines changed

1 file changed

+88
-8
lines changed

pkg/quota/admission/clusterresourceoverride/admission.go

Lines changed: 88 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,12 @@ func Register(plugins *admission.Plugins) {
4444
glog.Infof("Admission plugin %q is not configured so it will be disabled.", api.PluginName)
4545
return nil, nil
4646
}
47-
return newClusterResourceOverride(pluginConfig)
47+
return newClusterResourceOverride(pluginConfig, defaultGetNamespaceLimitRanges)
4848
})
4949
}
5050

51+
type namespaceLimitsFunc func(a *clusterResourceOverridePlugin, attr admission.Attributes) ([]*kapi.LimitRange, error)
52+
5153
type internalConfig struct {
5254
limitCPUToMemoryRatio float64
5355
cpuRequestToLimitRatio float64
@@ -58,6 +60,7 @@ type clusterResourceOverridePlugin struct {
5860
config *internalConfig
5961
ProjectCache *cache.ProjectCache
6062
LimitRanger admission.Interface
63+
namespaceLimitsFunc
6164
}
6265

6366
var _ = oadmission.WantsProjectCache(&clusterResourceOverridePlugin{})
@@ -66,7 +69,7 @@ var _ = kadmission.WantsInternalKubeClientSet(&clusterResourceOverridePlugin{})
6669

6770
// newClusterResourceOverride returns an admission controller for containers that
6871
// configurably overrides container resource request/limits
69-
func newClusterResourceOverride(config *api.ClusterResourceOverrideConfig) (admission.Interface, error) {
72+
func newClusterResourceOverride(config *api.ClusterResourceOverrideConfig, f namespaceLimitsFunc) (admission.Interface, error) {
7073
glog.V(2).Infof("%s admission controller loaded with config: %v", api.PluginName, config)
7174
var internal *internalConfig
7275
if config != nil {
@@ -83,9 +86,10 @@ func newClusterResourceOverride(config *api.ClusterResourceOverrideConfig) (admi
8386
}
8487

8588
return &clusterResourceOverridePlugin{
86-
Handler: admission.NewHandler(admission.Create),
87-
config: internal,
88-
LimitRanger: limitRanger,
89+
Handler: admission.NewHandler(admission.Create),
90+
config: internal,
91+
LimitRanger: limitRanger,
92+
namespaceLimitsFunc: f,
8993
}, nil
9094
}
9195

@@ -177,6 +181,20 @@ func (a *clusterResourceOverridePlugin) Admit(attr admission.Attributes) error {
177181
return nil // project is exempted, do nothing
178182
}
179183

184+
namespaceLimits := []*kapi.LimitRange{}
185+
186+
if a.namespaceLimitsFunc != nil {
187+
limits, err := a.namespaceLimitsFunc(a, attr)
188+
if err != nil {
189+
return err
190+
}
191+
namespaceLimits = limits
192+
}
193+
194+
// Don't mutate resource requirements below the namespace
195+
// limit minimums.
196+
nsCPUFloor, nsMemFloor := minResourceLimits(namespaceLimits)
197+
180198
// Reuse LimitRanger logic to apply limit/req defaults from the project. Ignore validation
181199
// errors, assume that LimitRanger will run after this plugin to validate.
182200
glog.V(5).Infof("%s: initial pod limits are: %#v", api.PluginName, pod.Spec)
@@ -185,16 +203,16 @@ func (a *clusterResourceOverridePlugin) Admit(attr admission.Attributes) error {
185203
}
186204
glog.V(5).Infof("%s: pod limits after LimitRanger: %#v", api.PluginName, pod.Spec)
187205
for i := range pod.Spec.InitContainers {
188-
updateContainerResources(a.config, &pod.Spec.InitContainers[i])
206+
updateContainerResources(a.config, &pod.Spec.InitContainers[i], nsCPUFloor, nsMemFloor)
189207
}
190208
for i := range pod.Spec.Containers {
191-
updateContainerResources(a.config, &pod.Spec.Containers[i])
209+
updateContainerResources(a.config, &pod.Spec.Containers[i], nsCPUFloor, nsMemFloor)
192210
}
193211
glog.V(5).Infof("%s: pod limits after overrides are: %#v", api.PluginName, pod.Spec)
194212
return nil
195213
}
196214

197-
func updateContainerResources(config *internalConfig, container *kapi.Container) {
215+
func updateContainerResources(config *internalConfig, container *kapi.Container, nsCPUFloor, nsMemFloor *resource.Quantity) {
198216
resources := container.Resources
199217
memLimit, memFound := resources.Limits[kapi.ResourceMemory]
200218
if memFound && config.memoryRequestToLimitRatio != 0 {
@@ -216,6 +234,10 @@ func updateContainerResources(config *internalConfig, container *kapi.Container)
216234
if memFloor.Cmp(*q) > 0 {
217235
q = memFloor.Copy()
218236
}
237+
if nsMemFloor != nil && q.Cmp(*nsMemFloor) < 0 {
238+
glog.V(5).Infof("%s: %s pod limit %q below namespace limit; setting limit to %q", api.PluginName, kapi.ResourceMemory, q.String(), nsMemFloor.String())
239+
q = nsMemFloor.Copy()
240+
}
219241
resources.Requests[kapi.ResourceMemory] = *q
220242
}
221243
if memFound && config.limitCPUToMemoryRatio != 0 {
@@ -224,6 +246,10 @@ func updateContainerResources(config *internalConfig, container *kapi.Container)
224246
if cpuFloor.Cmp(*q) > 0 {
225247
q = cpuFloor.Copy()
226248
}
249+
if nsCPUFloor != nil && q.Cmp(*nsCPUFloor) < 0 {
250+
glog.V(5).Infof("%s: %s pod limit %q below namespace limit; setting limit to %q", api.PluginName, kapi.ResourceCPU, q.String(), nsCPUFloor.String())
251+
q = nsCPUFloor.Copy()
252+
}
227253
resources.Limits[kapi.ResourceCPU] = *q
228254
}
229255

@@ -234,7 +260,61 @@ func updateContainerResources(config *internalConfig, container *kapi.Container)
234260
if cpuFloor.Cmp(*q) > 0 {
235261
q = cpuFloor.Copy()
236262
}
263+
if nsCPUFloor != nil && q.Cmp(*nsCPUFloor) < 0 {
264+
glog.V(5).Infof("%s: %s pod limit %q below namespace limit; setting limit to %q", api.PluginName, kapi.ResourceCPU, q.String(), nsCPUFloor.String())
265+
q = nsCPUFloor.Copy()
266+
}
237267
resources.Requests[kapi.ResourceCPU] = *q
238268
}
269+
}
270+
271+
// minResourceLimits finds the minimum CPU and minimum Memory resource
272+
// values across all the limits in limitRanges. Nil is returned if
273+
// there is CPU or Memory resource respectively.
274+
func minResourceLimits(limitRanges []*kapi.LimitRange) (*resource.Quantity, *resource.Quantity) {
275+
cpuLimits := []*resource.Quantity{}
276+
memLimits := []*resource.Quantity{}
277+
278+
for _, limitRange := range limitRanges {
279+
for _, limit := range limitRange.Spec.Limits {
280+
if limit.Type != kapi.LimitTypeContainer {
281+
continue
282+
}
283+
if cpuLimit, cpuFound := limit.Min[kapi.ResourceCPU]; cpuFound {
284+
cpuLimits = append(cpuLimits, cpuLimit.Copy())
285+
}
286+
if memLimit, memFound := limit.Min[kapi.ResourceMemory]; memFound {
287+
memLimits = append(memLimits, memLimit.Copy())
288+
}
289+
}
290+
}
291+
292+
var cpuMin *resource.Quantity
293+
var memMin *resource.Quantity
294+
295+
if len(cpuLimits) > 0 {
296+
cpuMin = minQuantity(cpuLimits)
297+
}
298+
299+
if len(memLimits) > 0 {
300+
memMin = minQuantity(memLimits)
301+
}
302+
303+
return cpuMin, memMin
304+
}
305+
306+
func minQuantity(quantities []*resource.Quantity) *resource.Quantity {
307+
min := quantities[0].Copy()
308+
309+
for i := range quantities {
310+
if quantities[i].Cmp(*min) < 0 {
311+
min = quantities[i].Copy()
312+
}
313+
}
314+
315+
return min
316+
}
239317

318+
func defaultGetNamespaceLimitRanges(a *clusterResourceOverridePlugin, attr admission.Attributes) ([]*kapi.LimitRange, error) {
319+
return a.LimitRanger.(*limitranger.LimitRanger).GetLimitRanges(attr)
240320
}

0 commit comments

Comments
 (0)