Skip to content

Commit 4e18956

Browse files
Implement SpinApp per-request invocation limits
- Implements SKIP 6: https://github.com/spinframework/skips/tree/main/proposals/006-spinapp-invocation-limits - Adds validation check for the knownn "memory" limit Signed-off-by: Kate Goldenring <[email protected]>
1 parent bd8d930 commit 4e18956

File tree

8 files changed

+235
-42
lines changed

8 files changed

+235
-42
lines changed

api/v1alpha1/spinapp_types.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,11 @@ type SpinAppSpec struct {
9191
// If not specified, the default service account will be used.
9292
// +optional
9393
ServiceAccountName string `json:"serviceAccountName,omitempty"`
94+
95+
// InvocationLimits define limits to be applied per invocation of the app.
96+
// The keys are the names of the limits and the values are the limit values.
97+
// SpinKube executors may define their own limits.
98+
InvocationLimits map[string]string `json:"invocationLimits,omitempty"`
9499
}
95100

96101
// SpinAppStatus defines the observed state of SpinApp

api/v1alpha1/zz_generated.deepcopy.go

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

config/crd/bases/core.spinkube.dev_spinapps.yaml

Lines changed: 54 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,14 @@ spec:
242242
type: object
243243
x-kubernetes-map-type: atomic
244244
type: array
245+
invocationLimits:
246+
additionalProperties:
247+
type: string
248+
description: |-
249+
InvocationLimits define limits to be applied per invocation of the app.
250+
The keys are the names of the limits and the values are the limit values.
251+
SpinKube executors may define their own limits.
252+
type: object
245253
podAnnotations:
246254
additionalProperties:
247255
type: string
@@ -728,6 +736,8 @@ spec:
728736
description: |-
729737
awsElasticBlockStore represents an AWS Disk resource that is attached to a
730738
kubelet's host machine and then exposed to the pod.
739+
Deprecated: AWSElasticBlockStore is deprecated. All operations for the in-tree
740+
awsElasticBlockStore type are redirected to the ebs.csi.aws.com CSI driver.
731741
More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore
732742
properties:
733743
fsType:
@@ -759,8 +769,10 @@ spec:
759769
- volumeID
760770
type: object
761771
azureDisk:
762-
description: azureDisk represents an Azure Data Disk mount on
763-
the host and bind mount to the pod.
772+
description: |-
773+
azureDisk represents an Azure Data Disk mount on the host and bind mount to the pod.
774+
Deprecated: AzureDisk is deprecated. All operations for the in-tree azureDisk type
775+
are redirected to the disk.csi.azure.com CSI driver.
764776
properties:
765777
cachingMode:
766778
description: 'cachingMode is the Host Caching mode: None,
@@ -798,8 +810,10 @@ spec:
798810
- diskURI
799811
type: object
800812
azureFile:
801-
description: azureFile represents an Azure File Service mount
802-
on the host and bind mount to the pod.
813+
description: |-
814+
azureFile represents an Azure File Service mount on the host and bind mount to the pod.
815+
Deprecated: AzureFile is deprecated. All operations for the in-tree azureFile type
816+
are redirected to the file.csi.azure.com CSI driver.
803817
properties:
804818
readOnly:
805819
description: |-
@@ -818,8 +832,9 @@ spec:
818832
- shareName
819833
type: object
820834
cephfs:
821-
description: cephFS represents a Ceph FS mount on the host that
822-
shares a pod's lifetime
835+
description: |-
836+
cephFS represents a Ceph FS mount on the host that shares a pod's lifetime.
837+
Deprecated: CephFS is deprecated and the in-tree cephfs type is no longer supported.
823838
properties:
824839
monitors:
825840
description: |-
@@ -871,6 +886,8 @@ spec:
871886
cinder:
872887
description: |-
873888
cinder represents a cinder volume attached and mounted on kubelets host machine.
889+
Deprecated: Cinder is deprecated. All operations for the in-tree cinder type
890+
are redirected to the cinder.csi.openstack.org CSI driver.
874891
More info: https://examples.k8s.io/mysql-cinder-pd/README.md
875892
properties:
876893
fsType:
@@ -980,8 +997,7 @@ spec:
980997
x-kubernetes-map-type: atomic
981998
csi:
982999
description: csi (Container Storage Interface) represents ephemeral
983-
storage that is handled by certain external CSI drivers (Beta
984-
feature).
1000+
storage that is handled by certain external CSI drivers.
9851001
properties:
9861002
driver:
9871003
description: |-
@@ -1447,6 +1463,7 @@ spec:
14471463
description: |-
14481464
flexVolume represents a generic volume resource that is
14491465
provisioned/attached using an exec based plugin.
1466+
Deprecated: FlexVolume is deprecated. Consider using a CSIDriver instead.
14501467
properties:
14511468
driver:
14521469
description: driver is the name of the driver to use for
@@ -1492,9 +1509,9 @@ spec:
14921509
- driver
14931510
type: object
14941511
flocker:
1495-
description: flocker represents a Flocker volume attached to
1496-
a kubelet's host machine. This depends on the Flocker control
1497-
service being running
1512+
description: |-
1513+
flocker represents a Flocker volume attached to a kubelet's host machine. This depends on the Flocker control service being running.
1514+
Deprecated: Flocker is deprecated and the in-tree flocker type is no longer supported.
14981515
properties:
14991516
datasetName:
15001517
description: |-
@@ -1510,6 +1527,8 @@ spec:
15101527
description: |-
15111528
gcePersistentDisk represents a GCE Disk resource that is attached to a
15121529
kubelet's host machine and then exposed to the pod.
1530+
Deprecated: GCEPersistentDisk is deprecated. All operations for the in-tree
1531+
gcePersistentDisk type are redirected to the pd.csi.storage.gke.io CSI driver.
15131532
More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk
15141533
properties:
15151534
fsType:
@@ -1545,7 +1564,7 @@ spec:
15451564
gitRepo:
15461565
description: |-
15471566
gitRepo represents a git repository at a particular revision.
1548-
DEPRECATED: GitRepo is deprecated. To provision a container with a git repo, mount an
1567+
Deprecated: GitRepo is deprecated. To provision a container with a git repo, mount an
15491568
EmptyDir into an InitContainer that clones the repo using git, then mount the EmptyDir
15501569
into the Pod's container.
15511570
properties:
@@ -1569,6 +1588,7 @@ spec:
15691588
glusterfs:
15701589
description: |-
15711590
glusterfs represents a Glusterfs mount on the host that shares a pod's lifetime.
1591+
Deprecated: Glusterfs is deprecated and the in-tree glusterfs type is no longer supported.
15721592
More info: https://examples.k8s.io/volumes/glusterfs/README.md
15731593
properties:
15741594
endpoints:
@@ -1778,8 +1798,9 @@ spec:
17781798
- claimName
17791799
type: object
17801800
photonPersistentDisk:
1781-
description: photonPersistentDisk represents a PhotonController
1782-
persistent disk attached and mounted on kubelets host machine
1801+
description: |-
1802+
photonPersistentDisk represents a PhotonController persistent disk attached and mounted on kubelets host machine.
1803+
Deprecated: PhotonPersistentDisk is deprecated and the in-tree photonPersistentDisk type is no longer supported.
17831804
properties:
17841805
fsType:
17851806
description: |-
@@ -1795,8 +1816,11 @@ spec:
17951816
- pdID
17961817
type: object
17971818
portworxVolume:
1798-
description: portworxVolume represents a portworx volume attached
1799-
and mounted on kubelets host machine
1819+
description: |-
1820+
portworxVolume represents a portworx volume attached and mounted on kubelets host machine.
1821+
Deprecated: PortworxVolume is deprecated. All operations for the in-tree portworxVolume type
1822+
are redirected to the pxd.portworx.com CSI driver when the CSIMigrationPortworx feature-gate
1823+
is on.
18001824
properties:
18011825
fsType:
18021826
description: |-
@@ -2161,8 +2185,9 @@ spec:
21612185
x-kubernetes-list-type: atomic
21622186
type: object
21632187
quobyte:
2164-
description: quobyte represents a Quobyte mount on the host
2165-
that shares a pod's lifetime
2188+
description: |-
2189+
quobyte represents a Quobyte mount on the host that shares a pod's lifetime.
2190+
Deprecated: Quobyte is deprecated and the in-tree quobyte type is no longer supported.
21662191
properties:
21672192
group:
21682193
description: |-
@@ -2201,6 +2226,7 @@ spec:
22012226
rbd:
22022227
description: |-
22032228
rbd represents a Rados Block Device mount on the host that shares a pod's lifetime.
2229+
Deprecated: RBD is deprecated and the in-tree rbd type is no longer supported.
22042230
More info: https://examples.k8s.io/volumes/rbd/README.md
22052231
properties:
22062232
fsType:
@@ -2273,8 +2299,9 @@ spec:
22732299
- monitors
22742300
type: object
22752301
scaleIO:
2276-
description: scaleIO represents a ScaleIO persistent volume
2277-
attached and mounted on Kubernetes nodes.
2302+
description: |-
2303+
scaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes.
2304+
Deprecated: ScaleIO is deprecated and the in-tree scaleIO type is no longer supported.
22782305
properties:
22792306
fsType:
22802307
default: xfs
@@ -2406,8 +2433,9 @@ spec:
24062433
type: string
24072434
type: object
24082435
storageos:
2409-
description: storageOS represents a StorageOS volume attached
2410-
and mounted on Kubernetes nodes.
2436+
description: |-
2437+
storageOS represents a StorageOS volume attached and mounted on Kubernetes nodes.
2438+
Deprecated: StorageOS is deprecated and the in-tree storageos type is no longer supported.
24112439
properties:
24122440
fsType:
24132441
description: |-
@@ -2452,8 +2480,10 @@ spec:
24522480
type: string
24532481
type: object
24542482
vsphereVolume:
2455-
description: vsphereVolume represents a vSphere volume attached
2456-
and mounted on kubelets host machine
2483+
description: |-
2484+
vsphereVolume represents a vSphere volume attached and mounted on kubelets host machine.
2485+
Deprecated: VsphereVolume is deprecated. All operations for the in-tree vsphereVolume type
2486+
are redirected to the csi.vsphere.vmware.com CSI driver.
24572487
properties:
24582488
fsType:
24592489
description: |-

internal/controller/deployment.go

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@ import (
44
"context"
55
"errors"
66
"fmt"
7+
"strconv"
78
"strings"
89

910
corev1 "k8s.io/api/core/v1"
11+
"k8s.io/apimachinery/pkg/api/resource"
1012
"k8s.io/apimachinery/pkg/util/intstr"
1113

1214
spinv1alpha1 "github.com/spinkube/spin-operator/api/v1alpha1"
@@ -103,7 +105,7 @@ func ConstructVolumeMountsForApp(ctx context.Context, app *spinv1alpha1.SpinApp,
103105

104106
// ConstructEnvForApp constructs the env for a spin app that runs as a k8s pod.
105107
// Variables are not guaranteed to stay backed by ENV.
106-
func ConstructEnvForApp(ctx context.Context, app *spinv1alpha1.SpinApp, listenPort int, otel *spinv1alpha1.OtelConfig) []corev1.EnvVar {
108+
func ConstructEnvForApp(ctx context.Context, app *spinv1alpha1.SpinApp, listenPort int, otel *spinv1alpha1.OtelConfig) ([]corev1.EnvVar, error) {
107109
envs := make([]corev1.EnvVar, len(app.Spec.Variables))
108110
// Adding the Spin Variables
109111
for idx, variable := range app.Spec.Variables {
@@ -141,7 +143,21 @@ func ConstructEnvForApp(ctx context.Context, app *spinv1alpha1.SpinApp, listenPo
141143
}
142144
}
143145

144-
return envs
146+
// Set known invocation limits as environment variables.
147+
if limit, exists := app.Spec.InvocationLimits["memory"]; exists {
148+
// Convert limit to bytes
149+
quantity, err := resource.ParseQuantity(limit)
150+
if err != nil {
151+
return nil, fmt.Errorf("failed to parse memory limit %q: %w", limit, err)
152+
}
153+
bytes := quantity.ToDec().Value()
154+
envs = append(envs, corev1.EnvVar{
155+
Name: "SPIN_MAX_INSTANCE_MEMORY",
156+
Value: strconv.Itoa(int(bytes)),
157+
})
158+
}
159+
160+
return envs, nil
145161
}
146162

147163
func SpinHealthCheckToCoreProbe(probe *spinv1alpha1.HealthProbe) (*corev1.Probe, error) {

internal/controller/deployment_test.go

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -110,8 +110,10 @@ func TestConstructEnvForApp(t *testing.T) {
110110
value string
111111
valueFrom *corev1.EnvVarSource
112112

113-
expectedOtelVars map[string]string
114-
otelVars spinv1alpha1.OtelConfig
113+
expectedOtelVars map[string]string
114+
otelVars spinv1alpha1.OtelConfig
115+
invocationLimits map[string]string
116+
expectedInvocationLimitsVars map[string]string
115117
}{
116118
{
117119
name: "simple_secret_with_static_value",
@@ -165,6 +167,18 @@ func TestConstructEnvForApp(t *testing.T) {
165167
"OTEL_EXPORTER_OTLP_LOGS_ENDPOINT": "http://logs",
166168
},
167169
},
170+
{
171+
name: "memory_invocation_limit",
172+
varName: "simple_secret",
173+
expectedEnvName: "SPIN_VARIABLE_SIMPLE_SECRET",
174+
value: "f00",
175+
invocationLimits: map[string]string{
176+
"memory": "40M",
177+
},
178+
expectedInvocationLimitsVars: map[string]string{
179+
"SPIN_MAX_INSTANCE_MEMORY": "40000000", // 40M in bytes
180+
},
181+
},
168182
}
169183

170184
for _, test := range tests {
@@ -177,29 +191,33 @@ func TestConstructEnvForApp(t *testing.T) {
177191
ValueFrom: test.valueFrom,
178192
},
179193
}
194+
app.Spec.InvocationLimits = test.invocationLimits
180195

181-
envs := ConstructEnvForApp(context.Background(), app, 0, &test.otelVars)
196+
envs, err := ConstructEnvForApp(context.Background(), app, 0, &test.otelVars)
197+
require.NoError(t, err)
182198

183199
require.Equal(t, test.expectedEnvName, envs[0].Name)
184200
require.Equal(t, test.value, envs[0].Value)
185201
require.Equal(t, test.valueFrom, envs[0].ValueFrom)
186202

187-
for key, value := range test.expectedOtelVars {
188-
varNotFound := true
189-
for _, envVar := range envs {
190-
if envVar.Name == key {
191-
varNotFound = false
192-
if envVar.Value != value {
193-
require.Equal(t, test.value, envVar.Value)
203+
inMap := func(expected map[string]string, envs []corev1.EnvVar) {
204+
for key, value := range expected {
205+
varNotFound := true
206+
for _, envVar := range envs {
207+
if envVar.Name == key {
208+
varNotFound = false
209+
require.Equal(t, value, envVar.Value)
194210
break
195211
}
196212
}
197-
}
198-
199-
if varNotFound {
200-
require.NotContains(t, test.expectedOtelVars, key)
213+
if varNotFound {
214+
require.NotContains(t, expected, key)
215+
}
201216
}
202217
}
218+
219+
inMap(test.expectedOtelVars, envs)
220+
inMap(test.expectedInvocationLimitsVars, envs)
203221
})
204222
}
205223
}

internal/controller/spinapp_controller.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -429,7 +429,11 @@ func constructDeployment(ctx context.Context, app *spinv1alpha1.SpinApp, config
429429
Requests: app.Spec.Resources.Requests,
430430
}
431431

432-
env := ConstructEnvForApp(ctx, app, spinapp.DefaultHTTPPort, config.Otel)
432+
env, err := ConstructEnvForApp(ctx, app, spinapp.DefaultHTTPPort, config.Otel)
433+
if err != nil {
434+
return nil, err
435+
}
436+
433437
if app.Spec.Components != nil {
434438
env = append(env, corev1.EnvVar{
435439
Name: "SPIN_COMPONENTS_TO_RETAIN",

0 commit comments

Comments
 (0)