Skip to content

Commit 6c3db52

Browse files
committed
fix: replaced hard enforced local limits with kueue
1 parent 9227150 commit 6c3db52

9 files changed

Lines changed: 97 additions & 54 deletions

File tree

.github/actions/e2e-ready/action.yml

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,44 @@ runs:
2626
/home/runner/.kube/config > backend/kubeconfig.yaml
2727
chmod 644 backend/kubeconfig.yaml
2828
29+
- name: Install Kueue
30+
shell: bash
31+
run: |
32+
KUEUE_VERSION="${KUEUE_VERSION:-v0.16.1}"
33+
kubectl apply --server-side -f "https://github.com/kubernetes-sigs/kueue/releases/download/${KUEUE_VERSION}/manifests.yaml"
34+
kubectl wait --for=condition=Available --timeout=120s \
35+
deployment/kueue-controller-manager -n kueue-system
36+
kubectl apply --server-side -f - <<'EOF'
37+
apiVersion: kueue.x-k8s.io/v1beta1
38+
kind: ResourceFlavor
39+
metadata:
40+
name: default-flavor
41+
---
42+
apiVersion: kueue.x-k8s.io/v1beta1
43+
kind: ClusterQueue
44+
metadata:
45+
name: executor-queue
46+
spec:
47+
namespaceSelector: {}
48+
resourceGroups:
49+
- coveredResources: ["cpu", "memory"]
50+
flavors:
51+
- name: default-flavor
52+
resources:
53+
- name: cpu
54+
nominalQuota: "32"
55+
- name: memory
56+
nominalQuota: "4Gi"
57+
---
58+
apiVersion: kueue.x-k8s.io/v1beta1
59+
kind: LocalQueue
60+
metadata:
61+
name: executor-queue
62+
namespace: integr8scode
63+
spec:
64+
clusterQueue: executor-queue
65+
EOF
66+
2967
- name: Use test environment config
3068
shell: bash
3169
run: |

.github/workflows/stack-tests.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ env:
3131
KAFKA_IMAGE: confluentinc/cp-kafka:7.8.2
3232
K3S_VERSION: v1.32.11+k3s1
3333
K3S_INSTALL_SHA256: d75e014f2d2ab5d30a318efa5c326f3b0b7596f194afcff90fa7a7a91166d5f7
34+
KUEUE_VERSION: v0.16.1
3435

3536
jobs:
3637
# Fast unit tests (no infrastructure needed)

backend/app/services/k8s_worker/pod_builder.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ def _build_pod_metadata(
157157
) -> k8s_client.V1ObjectMeta:
158158
"""Build pod metadata with saga tracking"""
159159
labels = {"app": "integr8s", "component": "executor", "execution-id": execution_id, "language": language}
160+
labels["kueue.x-k8s.io/queue-name"] = "executor-queue"
160161

161162
labels["user-id"] = user_id[:63] # K8s label value limit
162163

backend/app/services/k8s_worker/worker.py

Lines changed: 0 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -255,12 +255,10 @@ async def ensure_namespace_security(self) -> None:
255255
256256
Creates:
257257
- Default-deny NetworkPolicy for executor pods (blocks lateral movement and exfiltration)
258-
- ResourceQuota to cap aggregate CPU/memory consumption (no pod count limit)
259258
- Pod Security Admission labels (Restricted profile)
260259
"""
261260
namespace = self._settings.K8S_NAMESPACE
262261
await self._ensure_executor_network_policy(namespace)
263-
await self._ensure_executor_resource_quota(namespace)
264262
await self._apply_psa_labels(namespace)
265263

266264
async def _ensure_executor_network_policy(self, namespace: str) -> None:
@@ -290,35 +288,6 @@ async def _ensure_executor_network_policy(self, namespace: str) -> None:
290288
)
291289
self.logger.info(f"NetworkPolicy '{policy_name}' applied in namespace {namespace}")
292290

293-
async def _ensure_executor_resource_quota(self, namespace: str) -> None:
294-
"""Create or update ResourceQuota to cap aggregate CPU/memory in the executor namespace."""
295-
quota_name = "executor-quota"
296-
297-
quota = k8s_client.V1ResourceQuota(
298-
api_version="v1",
299-
kind="ResourceQuota",
300-
metadata=k8s_client.V1ObjectMeta(
301-
name=quota_name,
302-
namespace=namespace,
303-
labels={"app": "integr8s", "component": "security"},
304-
),
305-
spec=k8s_client.V1ResourceQuotaSpec(
306-
hard={
307-
"requests.cpu": self._settings.K8S_QUOTA_CPU,
308-
"requests.memory": self._settings.K8S_QUOTA_MEMORY,
309-
"limits.cpu": self._settings.K8S_QUOTA_CPU,
310-
"limits.memory": self._settings.K8S_QUOTA_MEMORY,
311-
},
312-
),
313-
)
314-
315-
await self.v1.patch_namespaced_resource_quota( # type: ignore[call-arg]
316-
name=quota_name, namespace=namespace, body=quota,
317-
field_manager="integr8s", force=True,
318-
_content_type="application/apply-patch+yaml",
319-
)
320-
self.logger.info(f"ResourceQuota '{quota_name}' applied in namespace {namespace}")
321-
322291
async def _apply_psa_labels(self, namespace: str) -> None:
323292
"""Apply Pod Security Admission labels to the executor namespace."""
324293
psa_labels = {

backend/app/settings.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,6 @@ def __init__(
7777
K8S_POD_PRIORITY_CLASS_NAME: str | None = None
7878
K8S_POD_RUNTIME_CLASS_NAME: str | None = None # e.g. "gvisor" for sandboxed execution
7979

80-
# Namespace-level ResourceQuota caps (total budget, not per-pod)
81-
K8S_QUOTA_CPU: str = "32000m"
82-
K8S_QUOTA_MEMORY: str = "4096Mi"
83-
8480
SUPPORTED_RUNTIMES: dict[str, LanguageInfoDomain] = Field(default_factory=lambda: RUNTIME_MATRIX)
8581

8682
EXAMPLE_SCRIPTS: dict[str, str] = Field(default_factory=lambda: EXEC_EXAMPLE_SCRIPTS)

backend/config.test.toml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@ K8S_POD_CPU_REQUEST = "200m"
1616
K8S_POD_MEMORY_REQUEST = "128Mi"
1717
K8S_POD_EXECUTION_TIMEOUT = 5
1818
K8S_NAMESPACE = "integr8scode"
19-
K8S_QUOTA_CPU = "32000m"
20-
K8S_QUOTA_MEMORY = "4096Mi"
2119

2220
RATE_LIMITS = "99999/second"
2321
RATE_LIMIT_ENABLED = false

backend/config.toml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,6 @@ K8S_POD_CPU_REQUEST = "200m"
2121
K8S_POD_MEMORY_REQUEST = "128Mi"
2222
K8S_POD_EXECUTION_TIMEOUT = 5
2323
K8S_NAMESPACE = "integr8scode"
24-
K8S_QUOTA_CPU = "32000m"
25-
K8S_QUOTA_MEMORY = "4096Mi"
2624

2725
RATE_LIMITS = "100/minute"
2826

cert-generator/setup-k8s.sh

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,45 @@ echo "Connected to Kubernetes"
8989
# Create namespace
9090
kubectl create namespace integr8scode --dry-run=client -o yaml | kubectl apply -f -
9191

92-
# Clean up stale executor pods from previous runs so they don't consume ResourceQuota
93-
kubectl delete pods -n integr8scode -l component=executor --field-selector=status.phase!=Running --ignore-not-found 2>/dev/null || true
92+
# Install Kueue (scheduling-gate based quota management)
93+
KUEUE_VERSION="${KUEUE_VERSION:-v0.16.1}"
94+
echo "Installing Kueue ${KUEUE_VERSION}..."
95+
kubectl apply --server-side -f "https://github.com/kubernetes-sigs/kueue/releases/download/${KUEUE_VERSION}/manifests.yaml"
96+
kubectl wait --for=condition=Available --timeout=120s \
97+
deployment/kueue-controller-manager -n kueue-system
98+
99+
# Kueue resources: ResourceFlavor + ClusterQueue + LocalQueue
100+
kubectl apply --server-side -f - <<'KUEUE_EOF'
101+
apiVersion: kueue.x-k8s.io/v1beta1
102+
kind: ResourceFlavor
103+
metadata:
104+
name: default-flavor
105+
---
106+
apiVersion: kueue.x-k8s.io/v1beta1
107+
kind: ClusterQueue
108+
metadata:
109+
name: executor-queue
110+
spec:
111+
namespaceSelector: {}
112+
resourceGroups:
113+
- coveredResources: ["cpu", "memory"]
114+
flavors:
115+
- name: default-flavor
116+
resources:
117+
- name: cpu
118+
nominalQuota: "32"
119+
- name: memory
120+
nominalQuota: "4Gi"
121+
---
122+
apiVersion: kueue.x-k8s.io/v1beta1
123+
kind: LocalQueue
124+
metadata:
125+
name: executor-queue
126+
namespace: integr8scode
127+
spec:
128+
clusterQueue: executor-queue
129+
KUEUE_EOF
130+
echo "Kueue installed and configured"
94131

95132
# Create ServiceAccount
96133
kubectl apply -f - <<EOF
@@ -146,9 +183,6 @@ rules:
146183
- apiGroups: ["networking.k8s.io"]
147184
resources: ["networkpolicies"]
148185
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
149-
- apiGroups: [""]
150-
resources: ["resourcequotas"]
151-
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
152186
EOF
153187

154188
# Create RoleBinding

docs/security/policies.md

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -60,19 +60,27 @@ spec:
6060
6161
This policy matches pods with the `component: executor` label, which the pod builder applies to all executor pods.
6262

63-
### Resource Quota
63+
### Kueue (Resource Quota with Queuing)
6464

65-
A ResourceQuota caps aggregate CPU and memory in the executor namespace. If the execution queue allows more pods than
66-
the namespace has resources for, Kubernetes keeps excess pods in Pending state rather than failing.
65+
[Kueue](https://kueue.sigs.k8s.io/) manages CPU/memory quota for executor pods. Unlike ResourceQuota (which rejects pod
66+
creation with 403 Forbidden when quota is full), Kueue adds a **scheduling gate** to pods — they exist in the API server
67+
but the scheduler ignores them (status: `SchedulingGated`). When quota frees up, the gate is removed and the pod is
68+
scheduled normally.
6769

68-
| Resource | Limit | Source setting |
69-
|-------------------|--------------------|--------------------|
70-
| `requests.cpu` | `K8S_QUOTA_CPU` | Namespace CPU cap |
71-
| `requests.memory` | `K8S_QUOTA_MEMORY` | Namespace memory cap |
72-
| `limits.cpu` | `K8S_QUOTA_CPU` | Same as requests |
73-
| `limits.memory` | `K8S_QUOTA_MEMORY` | Same as requests |
70+
Kueue resources (created by `setup-k8s.sh`):
7471

75-
No `pods` count in the quota — concurrency is controlled by the execution queue (`max_concurrent_executions`).
72+
| Resource | Name | Purpose |
73+
|-----------------|------------------|--------------------------------------------------------------|
74+
| ResourceFlavor | `default-flavor` | Represents the cluster's default resource pool |
75+
| ClusterQueue | `executor-queue` | Defines CPU/memory quota (32 CPU, 4Gi memory) |
76+
| LocalQueue | `executor-queue` | Namespace-scoped queue in `integr8scode`, bound to ClusterQueue |
77+
78+
Executor pods carry the label `kueue.x-k8s.io/queue-name: executor-queue` (set by the pod builder). Kueue only manages
79+
pods with this label — DaemonSet pods (e.g., the image pre-puller) are invisible to Kueue.
80+
81+
Key behavior:
82+
- `active_deadline_seconds` only counts from when the pod is scheduled, so gated time does not consume execution timeout
83+
- Concurrency is still controlled by the execution queue; Kueue acts as a safety net for aggregate resource consumption
7684

7785
### Pod Security Admission (PSA)
7886

0 commit comments

Comments
 (0)