Skip to content
This repository was archived by the owner on Mar 16, 2024. It is now read-only.

Commit 33ee3ee

Browse files
author
Oscar Ward
authored
ComputeClass request downscaling for overprovisioning (#2381) (#2394)
1 parent 57a5112 commit 33ee3ee

File tree

14 files changed

+380
-11
lines changed

14 files changed

+380
-11
lines changed

docs/docs/40-admin/03-computeclasses.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ memory:
2121
min: 1Gi
2222
max: 2Gi
2323
default: 1Gi # This default overrides the install-wide memory default
24+
requestScaler: .5 # A percentage of memory to request in relation to the limit, will not go below a configured min value
2425
values: # Specific values that are only allowed to be used. Default must be included in these values and max/min cannot be set.
2526
- 1.5Gi
2627
cpuScaler: 1 # This is used as a ratio of how many VCPUs to schedule per Gibibyte of memory. In this case it is 1 to 1.

integration/client/computeclass/computeclass_test.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,41 @@ func TestCreatingComputeClasses(t *testing.T) {
180180
},
181181
fail: true,
182182
},
183+
{
184+
name: "valid-values-with-scaler",
185+
cpuScaler: 0.25,
186+
memory: adminv1.ComputeClassMemory{
187+
RequestScaler: 0.1,
188+
Default: "1Gi",
189+
Values: []string{"1Gi", "2Gi"},
190+
},
191+
},
192+
{
193+
name: "valid-scaler-upper-bound",
194+
memory: adminv1.ComputeClassMemory{
195+
RequestScaler: 1.0,
196+
},
197+
},
198+
{
199+
name: "valid-scaler-lower-bound",
200+
memory: adminv1.ComputeClassMemory{
201+
RequestScaler: 0,
202+
},
203+
},
204+
{
205+
name: "invalid-scaler-negative",
206+
memory: adminv1.ComputeClassMemory{
207+
RequestScaler: -0.1,
208+
},
209+
fail: true,
210+
},
211+
{
212+
name: "invalid-scaler-too-large",
213+
memory: adminv1.ComputeClassMemory{
214+
RequestScaler: 1.1,
215+
},
216+
fail: true,
217+
},
183218
}
184219

185220
for _, tt := range checks {

pkg/apis/internal.acorn.io/v1/memory.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ func ValidateMemory(memSpec MemoryMap, containerName string, container Container
4646
memMaximum = *specMemMaximum
4747
}
4848

49-
// Determine which memory should be used to set the resource limit/requests. Gets set
49+
// Determine which memory should be used to set the resource limit. Gets set
5050
// 4 ways: User setting a specific workload, user setting all workloads, Acornfile, or
5151
// from the apiv1.Config default.
5252
memBytes, errType := memDefault, ErrInvalidDefaultMemory
@@ -80,7 +80,7 @@ func ValidateMemory(memSpec MemoryMap, containerName string, container Container
8080
errType, defaultQuantity, maxQuantity)
8181
}
8282
} else if memBytes == 0 {
83-
// For bytes, 0 is viewed as the maximum allowed memory". As such,
83+
// For bytes, 0 is viewed as the maximum allowed memory. As such,
8484
// update to the current maximum.
8585
memBytes = memMaximum
8686
}

pkg/apis/internal.admin.acorn.io/v1/computeclasses.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,9 @@ type ProjectComputeClassInstanceList struct {
6565
}
6666

6767
type ComputeClassMemory struct {
68-
Min string `json:"min,omitempty"`
69-
Max string `json:"max,omitempty"`
70-
Default string `json:"default,omitempty"`
71-
Values []string `json:"values,omitempty"`
68+
Min string `json:"min,omitempty"`
69+
Max string `json:"max,omitempty"`
70+
Default string `json:"default,omitempty"`
71+
RequestScaler float64 `json:"requestScaler,omitempty"`
72+
Values []string `json:"values,omitempty"`
7273
}

pkg/computeclasses/computeclasses.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@ func ParseComputeClassMemory(memory internaladminv1.ComputeClassMemory) (memoryQ
5959
}
6060
quantities.Def = &defInt
6161

62+
if memory.RequestScaler < 0 || memory.RequestScaler > 1 {
63+
return memoryQuantities{}, errors.New("request scaler value must be between 0 and 1, inclusive")
64+
}
65+
6266
quantities.Values = make([]*resource.Quantity, len(memory.Values))
6367
for i, value := range memory.Values {
6468
valueInt, err := parseQuantity(value)

pkg/controller/scheduling/computeclass_test.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,14 @@ func TestGenericResourcesComputeClass(t *testing.T) {
6161
tester.DefaultTest(t, scheme.Scheme, "testdata/computeclass/generic-resources", Calculate)
6262
}
6363

64+
func TestRequestScaler(t *testing.T) {
65+
tester.DefaultTest(t, scheme.Scheme, "testdata/computeclass/request-scaler", Calculate)
66+
}
67+
68+
func TestRequestScalerFloor(t *testing.T) {
69+
tester.DefaultTest(t, scheme.Scheme, "testdata/computeclass/request-scaler-floor", Calculate)
70+
}
71+
6472
func TestTwoCCCDefaultsShouldError(t *testing.T) {
6573
harness, input, err := tester.FromDir(scheme.Scheme, "testdata/computeclass/two-ccc-defaults-should-error")
6674
if err != nil {

pkg/controller/scheduling/scheduling.go

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -187,18 +187,36 @@ func ResourceRequirements(req router.Request, app *v1.AppInstance, containerName
187187
}
188188
}
189189

190-
memoryQuantity, err := v1.ValidateMemory(app.Spec.Memory, containerName, container, memDefault, memMax)
190+
memoryLimit, err := v1.ValidateMemory(app.Spec.Memory, containerName, container, memDefault, memMax)
191191
if err != nil {
192192
return nil, err
193193
}
194194

195-
if memoryQuantity.Value() != 0 {
196-
requirements.Requests[corev1.ResourceMemory] = memoryQuantity
197-
requirements.Limits[corev1.ResourceMemory] = memoryQuantity
195+
// Figure out the scaled value of memory to request based on the compute class
196+
memoryRequest := memoryLimit.DeepCopy()
197+
if computeClass != nil && computeClass.Memory.RequestScaler != 0 {
198+
// The following line should hold up without loss of precision up to 4 petabytes
199+
memoryRequest.Set(int64(memoryLimit.AsApproximateFloat64() * computeClass.Memory.RequestScaler))
200+
201+
// Never allocate less than the defined minimum of the compute class
202+
if computeClass.Memory.Min != "" && computeClass.Memory.Min != "0" {
203+
minValue, err := resource.ParseQuantity(computeClass.Memory.Min)
204+
if err != nil {
205+
return nil, err
206+
}
207+
if minValue.Cmp(memoryRequest) == 1 {
208+
memoryRequest = minValue
209+
}
210+
}
211+
}
212+
213+
if memoryLimit.Value() != 0 {
214+
requirements.Requests[corev1.ResourceMemory] = memoryRequest
215+
requirements.Limits[corev1.ResourceMemory] = memoryLimit
198216
}
199217

200218
if computeClass != nil {
201-
cpuQuantity, err := computeclasses.CalculateCPU(*computeClass, memDefault, memoryQuantity)
219+
cpuQuantity, err := computeclasses.CalculateCPU(*computeClass, memDefault, memoryRequest)
202220
if err != nil {
203221
return nil, err
204222
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
---
2+
kind: ClusterComputeClassInstance
3+
apiVersion: internal.admin.acorn.io/v1
4+
metadata:
5+
name: sample-compute-class
6+
description: Simple description for a simple ComputeClass
7+
cpuScaler: 0.25
8+
memory:
9+
min: 1Mi # 1Mi
10+
max: 2Mi # 2Mi
11+
default: 2Mi # 2Mi
12+
requestScaler: .1
13+
affinity:
14+
nodeAffinity:
15+
requiredDuringSchedulingIgnoredDuringExecution:
16+
nodeSelectorTerms:
17+
- matchExpressions:
18+
- key: foo
19+
operator: In
20+
values:
21+
- bar
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
`apiVersion: internal.acorn.io/v1
2+
kind: AppInstance
3+
metadata:
4+
creationTimestamp: null
5+
name: app-name
6+
namespace: app-namespace
7+
uid: 1234567890abcdef
8+
spec:
9+
computeClass:
10+
oneimage: sample-compute-class
11+
image: test
12+
status:
13+
appImage:
14+
buildContext: {}
15+
id: test
16+
imageData: {}
17+
vcs: {}
18+
appSpec:
19+
containers:
20+
oneimage:
21+
build:
22+
context: .
23+
dockerfile: Dockerfile
24+
image: image-name
25+
metrics: {}
26+
ports:
27+
- port: 80
28+
protocol: http
29+
targetPort: 81
30+
probes: null
31+
sidecars:
32+
left:
33+
image: foo
34+
metrics: {}
35+
ports:
36+
- port: 90
37+
protocol: tcp
38+
targetPort: 91
39+
probes: null
40+
appStatus: {}
41+
columns: {}
42+
conditions:
43+
reason: Success
44+
status: "True"
45+
success: true
46+
type: scheduling
47+
defaults:
48+
memory:
49+
"": 0
50+
left: 2097152
51+
oneimage: 2097152
52+
namespace: app-created-namespace
53+
observedGeneration: 1
54+
resolvedOfferings: {}
55+
scheduling:
56+
left:
57+
requirements:
58+
limits:
59+
memory: 2Mi
60+
requests:
61+
cpu: 1m
62+
memory: 1Mi
63+
oneimage:
64+
affinity:
65+
nodeAffinity:
66+
requiredDuringSchedulingIgnoredDuringExecution:
67+
nodeSelectorTerms:
68+
- matchExpressions:
69+
- key: foo
70+
operator: In
71+
values:
72+
- bar
73+
requirements:
74+
limits:
75+
memory: 2Mi
76+
requests:
77+
cpu: 1m
78+
memory: 1Mi
79+
tolerations:
80+
- key: taints.acorn.io/workload
81+
operator: Exists
82+
staged:
83+
appImage:
84+
buildContext: {}
85+
imageData: {}
86+
vcs: {}
87+
summary: {}
88+
`
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
kind: AppInstance
2+
apiVersion: internal.acorn.io/v1
3+
metadata:
4+
name: app-name
5+
namespace: app-namespace
6+
uid: 1234567890abcdef
7+
spec:
8+
image: test
9+
computeClass:
10+
oneimage: sample-compute-class
11+
status:
12+
observedGeneration: 1
13+
defaults:
14+
memory:
15+
"": 0
16+
left: 2097152 # 2Mi
17+
oneimage: 2097152 # 2Mi
18+
namespace: app-created-namespace
19+
appImage:
20+
id: test
21+
defaults:
22+
appSpec:
23+
containers:
24+
oneimage:
25+
sidecars:
26+
left:
27+
image: "foo"
28+
ports:
29+
- port: 90
30+
targetPort: 91
31+
protocol: tcp
32+
ports:
33+
- port: 80
34+
targetPort: 81
35+
protocol: http
36+
image: "image-name"
37+
build:
38+
dockerfile: "Dockerfile"
39+
context: "."

0 commit comments

Comments
 (0)