@@ -44,10 +44,12 @@ func Register(plugins *admission.Plugins) {
44
44
glog .Infof ("Admission plugin %q is not configured so it will be disabled." , api .PluginName )
45
45
return nil , nil
46
46
}
47
- return newClusterResourceOverride (pluginConfig )
47
+ return newClusterResourceOverride (pluginConfig , defaultGetNamespaceLimitRanges )
48
48
})
49
49
}
50
50
51
+ type namespaceLimitsFunc func (a * clusterResourceOverridePlugin , attr admission.Attributes ) ([]* kapi.LimitRange , error )
52
+
51
53
type internalConfig struct {
52
54
limitCPUToMemoryRatio float64
53
55
cpuRequestToLimitRatio float64
@@ -58,6 +60,7 @@ type clusterResourceOverridePlugin struct {
58
60
config * internalConfig
59
61
ProjectCache * cache.ProjectCache
60
62
LimitRanger admission.Interface
63
+ namespaceLimitsFunc
61
64
}
62
65
63
66
var _ = oadmission .WantsProjectCache (& clusterResourceOverridePlugin {})
@@ -66,7 +69,7 @@ var _ = kadmission.WantsInternalKubeClientSet(&clusterResourceOverridePlugin{})
66
69
67
70
// newClusterResourceOverride returns an admission controller for containers that
68
71
// 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 ) {
70
73
glog .V (2 ).Infof ("%s admission controller loaded with config: %v" , api .PluginName , config )
71
74
var internal * internalConfig
72
75
if config != nil {
@@ -83,9 +86,10 @@ func newClusterResourceOverride(config *api.ClusterResourceOverrideConfig) (admi
83
86
}
84
87
85
88
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 ,
89
93
}, nil
90
94
}
91
95
@@ -177,6 +181,20 @@ func (a *clusterResourceOverridePlugin) Admit(attr admission.Attributes) error {
177
181
return nil // project is exempted, do nothing
178
182
}
179
183
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
+
180
198
// Reuse LimitRanger logic to apply limit/req defaults from the project. Ignore validation
181
199
// errors, assume that LimitRanger will run after this plugin to validate.
182
200
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 {
185
203
}
186
204
glog .V (5 ).Infof ("%s: pod limits after LimitRanger: %#v" , api .PluginName , pod .Spec )
187
205
for i := range pod .Spec .InitContainers {
188
- updateContainerResources (a .config , & pod .Spec .InitContainers [i ])
206
+ updateContainerResources (a .config , & pod .Spec .InitContainers [i ], nsCPUFloor , nsMemFloor )
189
207
}
190
208
for i := range pod .Spec .Containers {
191
- updateContainerResources (a .config , & pod .Spec .Containers [i ])
209
+ updateContainerResources (a .config , & pod .Spec .Containers [i ], nsCPUFloor , nsMemFloor )
192
210
}
193
211
glog .V (5 ).Infof ("%s: pod limits after overrides are: %#v" , api .PluginName , pod .Spec )
194
212
return nil
195
213
}
196
214
197
- func updateContainerResources (config * internalConfig , container * kapi.Container ) {
215
+ func updateContainerResources (config * internalConfig , container * kapi.Container , nsCPUFloor , nsMemFloor * resource. Quantity ) {
198
216
resources := container .Resources
199
217
memLimit , memFound := resources .Limits [kapi .ResourceMemory ]
200
218
if memFound && config .memoryRequestToLimitRatio != 0 {
@@ -216,6 +234,10 @@ func updateContainerResources(config *internalConfig, container *kapi.Container)
216
234
if memFloor .Cmp (* q ) > 0 {
217
235
q = memFloor .Copy ()
218
236
}
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
+ }
219
241
resources .Requests [kapi .ResourceMemory ] = * q
220
242
}
221
243
if memFound && config .limitCPUToMemoryRatio != 0 {
@@ -224,6 +246,10 @@ func updateContainerResources(config *internalConfig, container *kapi.Container)
224
246
if cpuFloor .Cmp (* q ) > 0 {
225
247
q = cpuFloor .Copy ()
226
248
}
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
+ }
227
253
resources .Limits [kapi .ResourceCPU ] = * q
228
254
}
229
255
@@ -234,7 +260,61 @@ func updateContainerResources(config *internalConfig, container *kapi.Container)
234
260
if cpuFloor .Cmp (* q ) > 0 {
235
261
q = cpuFloor .Copy ()
236
262
}
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
+ }
237
267
resources .Requests [kapi .ResourceCPU ] = * q
238
268
}
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
+ }
239
317
318
+ func defaultGetNamespaceLimitRanges (a * clusterResourceOverridePlugin , attr admission.Attributes ) ([]* kapi.LimitRange , error ) {
319
+ return a .LimitRanger .(* limitranger.LimitRanger ).GetLimitRanges (attr )
240
320
}
0 commit comments