Skip to content

Commit 4e83d83

Browse files
1stvampTrigger.dev RepoOps
authored andcommitted
feat(compute): make the guest lane shippable and selectable
Mono-RevId: c893ea0934c69e8fe8ec6f45eaf7be8487e4886b
1 parent 7e92a02 commit 4e83d83

6 files changed

Lines changed: 82 additions & 4 deletions

File tree

apps/supervisor/src/env.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,14 @@ export const Env = z
183183
// the pod here. The two are mutually exclusive by construction: whichever
184184
// one runs is the only thing that creates a workload for a cold start.
185185
KUBERNETES_RUN_CRD_ENABLED: BoolEnv.default(false),
186+
// Which isolation lane a Runner asks for. Cell-wide rather than per run,
187+
// because a cell's node pools decide what it can serve and nothing on a
188+
// dequeued message can express the choice. Ignored unless the run-crd
189+
// backend is the one running: the pod backends have no guest lane.
190+
//
191+
// Not to be confused with the task runtime (node-24, bun), which rides on
192+
// the same Runner as taskRuntime and says which interpreter the task needs.
193+
KUBERNETES_RUNNER_RUNTIME: z.enum(["container", "microvm"]).default("container"),
186194
KUBERNETES_NAMESPACE: z.string().default("default"),
187195
KUBERNETES_WORKER_NODETYPE_LABEL: NodeLabelValue.default("v4-worker"),
188196
KUBERNETES_IMAGE_PULL_SECRETS: z.string().optional(), // csv

apps/supervisor/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@ class ManagedSupervisor {
195195
this.workloadManager = new RunCrdWorkloadManager({
196196
...workloadManagerOptions,
197197
namespace: env.KUBERNETES_NAMESPACE,
198+
runtime: env.KUBERNETES_RUNNER_RUNTIME,
198199
});
199200
this.workloadManagerBackend = "run-crd";
200201
} else if (this.isKubernetes) {

apps/supervisor/src/workloadManager/runCrd.test.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { runnerBodyFor, runnerTokenSecretName } from "./runCrd.js";
44
import { getRunnerId } from "../util.js";
55
import type { WorkloadManagerCreateOptions } from "./types.js";
66

7-
const meta = { name: "runner-abc123", namespace: "v4-runs" };
7+
const meta = { name: "runner-abc123", namespace: "v4-runs", runtime: "container" } as const;
88

99
function createOptions(
1010
overrides: Partial<WorkloadManagerCreateOptions> = {}
@@ -28,6 +28,31 @@ function createOptions(
2828
};
2929
}
3030

31+
/**
32+
* The isolation lane is the one spec field a cell chooses rather than derives
33+
* from the run, so it is the one a refactor can quietly pin. Asserting both
34+
* values, rather than that the field is carried, is what catches a literal
35+
* creeping back in: a hardcoded "container" passes any test that only ever asks
36+
* for a container.
37+
*/
38+
describe("runnerBodyFor carries the isolation lane it is given", () => {
39+
it.each(["container", "microvm"] as const)("asks for %s", (runtime) => {
40+
const body = runnerBodyFor(createOptions(), { ...meta, runtime });
41+
42+
expect(body.spec.runtime).toBe(runtime);
43+
});
44+
45+
it("leaves the task runtime alone, which is a different field", () => {
46+
const body = runnerBodyFor(createOptions({ runtime: "node-24" }), {
47+
...meta,
48+
runtime: "microvm",
49+
});
50+
51+
expect(body.spec.runtime).toBe("microvm");
52+
expect(body.spec.taskRuntime).toBe("node-24");
53+
});
54+
});
55+
3156
describe("runnerBodyFor", () => {
3257
it("names the object after the runner and carries the required spec", () => {
3358
const body = runnerBodyFor(createOptions(), meta);

apps/supervisor/src/workloadManager/runCrd.ts

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,20 @@ const TOKEN_KEY = "token";
1919

2020
type OwnerReference = { apiVersion: string; kind: string; name: string; uid: string };
2121

22+
/** The isolation lane a Runner asks for, which is the CRD's own enum. */
23+
export type RunnerRuntime = "container" | "microvm";
24+
2225
export type RunCrdWorkloadManagerOptions = WorkloadManagerOptions & {
2326
/** Passed in, not read from env, so the translation below is testable alone. */
2427
namespace: string;
28+
/**
29+
* Cell-wide, because a cell's node pools decide what it can serve and nothing
30+
* on a dequeued message can express a per-run choice. A guest asked for on a
31+
* cell with no RuntimeClass fails the Runner rather than falling back, which
32+
* is the operator's decision and the right one: a run silently served by the
33+
* wrong isolation is worse than one that does not start.
34+
*/
35+
runtime: RunnerRuntime;
2536
};
2637

2738
/**
@@ -35,18 +46,25 @@ export class RunCrdWorkloadManager implements WorkloadManager {
3546
private readonly logger = new SimpleStructuredLogger("run-crd-workload-provider");
3647
private readonly k8s: K8sApi;
3748
private readonly namespace: string;
49+
private readonly runtime: RunnerRuntime;
3850

3951
constructor(opts: RunCrdWorkloadManagerOptions) {
4052
this.k8s = createK8sApi();
4153
this.namespace = opts.namespace;
54+
this.runtime = opts.runtime;
4255
}
4356

4457
async create(opts: WorkloadManagerCreateOptions) {
4558
const runnerId = getRunnerId(opts.runFriendlyId, opts.nextAttemptNumber);
4659

4760
const token = await this.ensureRunnerToken(opts, runnerId);
4861

49-
const body = runnerBodyFor(opts, { name: runnerId, namespace: this.namespace, token });
62+
const body = runnerBodyFor(opts, {
63+
name: runnerId,
64+
namespace: this.namespace,
65+
runtime: this.runtime,
66+
token,
67+
});
5068

5169
this.logger.verbose("[RunCrdWorkloadManager] Creating runner", { runnerId, body });
5270

@@ -253,7 +271,12 @@ function uidOf(created: unknown): string | undefined {
253271
*/
254272
export function runnerBodyFor(
255273
opts: WorkloadManagerCreateOptions,
256-
meta: { name: string; namespace: string; token?: { name: string; key: string } }
274+
meta: {
275+
name: string;
276+
namespace: string;
277+
runtime: RunnerRuntime;
278+
token?: { name: string; key: string };
279+
}
257280
) {
258281
return {
259282
apiVersion: `${GROUP}/${VERSION}`,
@@ -264,7 +287,7 @@ export function runnerBodyFor(
264287
namespace: meta.namespace,
265288
},
266289
spec: {
267-
runtime: "container",
290+
runtime: meta.runtime,
268291
// As built, digest and all: the operator owns stripping and rewriting, so
269292
// they cannot both apply.
270293
image: opts.image,

hosting/k8s/helm/templates/supervisor.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,10 +180,18 @@ spec:
180180
value: {{ .Values.supervisor.config.kubernetes.forceEnabled | quote }}
181181
- name: KUBERNETES_RUN_CRD_ENABLED
182182
value: {{ .Values.supervisor.config.kubernetes.runCrdEnabled | quote }}
183+
- name: KUBERNETES_RUNNER_RUNTIME
184+
value: {{ .Values.supervisor.config.kubernetes.runnerRuntime | quote }}
183185
{{- range .Values.supervisor.extraEnvVars }}
184186
{{- if eq .name "KUBERNETES_RUN_CRD_ENABLED" }}
185187
{{- fail "Set supervisor.config.kubernetes.runCrdEnabled rather than overriding KUBERNETES_RUN_CRD_ENABLED through extraEnvVars. extraEnvVars is rendered last, so the override would win and enable the backend while the Role still lacks the two writes it needs, giving a 403 on every cold start." }}
186188
{{- end }}
189+
{{- if eq .name "KUBERNETES_RUNNER_RUNTIME" }}
190+
{{- fail "Set supervisor.config.kubernetes.runnerRuntime rather than overriding KUBERNETES_RUNNER_RUNTIME through extraEnvVars. extraEnvVars is rendered last, so the override would win and ask for a lane the chart has not checked." }}
191+
{{- end }}
192+
{{- end }}
193+
{{- if not (has .Values.supervisor.config.kubernetes.runnerRuntime (list "container" "microvm")) }}
194+
{{- fail "supervisor.config.kubernetes.runnerRuntime must be container or microvm, matching the Runner CRD's own enum. Anything else is refused by the API server at create time, which is one dequeued run lost per attempt." }}
187195
{{- end }}
188196
- name: KUBERNETES_WORKER_NODETYPE_LABEL
189197
value: {{ .Values.supervisor.config.kubernetes.workerNodetypeLabel | quote }}

hosting/k8s/helm/values.yaml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,19 @@ supervisor:
307307
# the create 404s without them. Also grants the two writes the backend
308308
# needs, a Secret for the deployment token and the Runner itself.
309309
runCrdEnabled: false
310+
# Which isolation lane a Runner asks for: "container", or "microvm" for a
311+
# Firecracker guest. Only read when runCrdEnabled is on, since the pod
312+
# backends have no guest lane. Cell-wide rather than per run, because the
313+
# node pools decide what can be served.
314+
#
315+
# A guest needs a RuntimeClass and a runtime handler on the node; the
316+
# operator treats an unset microVM.runtimeClassName as an error rather
317+
# than falling back to a container, so a half-installed cluster fails the
318+
# Runner instead of quietly running the workload unisolated.
319+
#
320+
# Not to be confused with the task runtime (node-24, bun), which rides on
321+
# the same Runner and says which interpreter the task needs.
322+
runnerRuntime: container
310323
podCleaner:
311324
enabled: true
312325
batchSize: 100

0 commit comments

Comments
 (0)