feat(runtime): add experimental Node.js 24 and 26 task runtimes (#4085)
⚒️ Publish Worker (v4) / build (supervisor) (push) Has been cancelled
⚒️ Publish Worker (v4) / build (supervisor) (push) Has been cancelled
## Summary Adds experimental Node.js 24 and 26 task runtimes through the `experimental-node-24` and `experimental-node-26` config values. Existing runtime defaults and the `node`, `node-22`, and `bun` behavior remain unchanged. The unprefixed `node-24` and `node-26` config values remain unavailable until the runtimes are ready for general use. ## Design Experimental config values normalize to canonical runtime identifiers before build manifests are created, keeping deployment metadata and execution behavior consistent. Kubernetes task pods also use the runtime-default seccomp profile so modern Node.js versions fall back from io_uring to checkpoint-compatible system calls.
This commit is contained in:
@@ -120,6 +120,7 @@ class ManagedSupervisor {
|
||||
snapshotPollIntervalSeconds: env.RUNNER_SNAPSHOT_POLL_INTERVAL_SECONDS,
|
||||
additionalEnvVars: env.RUNNER_ADDITIONAL_ENV_VARS,
|
||||
dockerAutoremove: env.DOCKER_AUTOREMOVE_EXITED_CONTAINERS,
|
||||
checkpointsEnabled: !!env.TRIGGER_CHECKPOINT_URL,
|
||||
} satisfies WorkloadManagerOptions;
|
||||
|
||||
this.resourceMonitor = env.RESOURCE_MONITOR_ENABLED
|
||||
@@ -615,6 +616,7 @@ class ManagedSupervisor {
|
||||
projectId: message.project.id,
|
||||
deploymentFriendlyId: message.deployment.friendlyId,
|
||||
deploymentVersion: message.backgroundWorker.version,
|
||||
runtime: message.backgroundWorker.runtime,
|
||||
runId: message.run.id,
|
||||
runFriendlyId: message.run.friendlyId,
|
||||
version: message.version,
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
BLOCK_IO_URING_SECCOMP_PROFILE,
|
||||
withBlockIoUringSeccompProfile,
|
||||
} from "./kubernetesPodSpec.js";
|
||||
|
||||
const basePodSpec = {
|
||||
restartPolicy: "Never" as const,
|
||||
automountServiceAccountToken: false,
|
||||
securityContext: {
|
||||
runAsNonRoot: true,
|
||||
runAsUser: 1000,
|
||||
fsGroup: 1000,
|
||||
},
|
||||
};
|
||||
|
||||
describe("withBlockIoUringSeccompProfile", () => {
|
||||
it("adds the Localhost io_uring profile for node-24 and above, preserving pod security defaults", () => {
|
||||
for (const runtime of ["node-24", "node-26", "node-30", "experimental-node-24"]) {
|
||||
const podSpec = withBlockIoUringSeccompProfile(basePodSpec, runtime);
|
||||
|
||||
expect(podSpec).toMatchObject({
|
||||
...basePodSpec,
|
||||
securityContext: {
|
||||
...basePodSpec.securityContext,
|
||||
seccompProfile: {
|
||||
type: "Localhost",
|
||||
localhostProfile: BLOCK_IO_URING_SECCOMP_PROFILE,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it("leaves the pod spec unchanged for runtimes that do not create io_uring fds", () => {
|
||||
for (const runtime of ["node", "node-22", "bun", undefined, null, ""]) {
|
||||
expect(withBlockIoUringSeccompProfile(basePodSpec, runtime)).toEqual(basePodSpec);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -14,6 +14,7 @@ import { PlacementTagProcessor } from "@trigger.dev/core/v3/serverOnly";
|
||||
import { env } from "../env.js";
|
||||
import { type K8sApi, createK8sApi, type k8s } from "../clients/kubernetes.js";
|
||||
import { getRunnerId } from "../util.js";
|
||||
import { withBlockIoUringSeccompProfile } from "./kubernetesPodSpec.js";
|
||||
|
||||
type ResourceQuantities = {
|
||||
[K in "cpu" | "memory" | "ephemeral-storage"]?: string;
|
||||
@@ -105,6 +106,11 @@ export class KubernetesWorkloadManager implements WorkloadManager {
|
||||
const runnerId = getRunnerId(opts.runFriendlyId, opts.nextAttemptNumber);
|
||||
|
||||
try {
|
||||
const basePodSpec = this.addPlacementTags(this.#defaultPodSpec, opts.placementTags);
|
||||
const podSpec = this.opts.checkpointsEnabled
|
||||
? withBlockIoUringSeccompProfile(basePodSpec, opts.runtime)
|
||||
: basePodSpec;
|
||||
|
||||
await this.k8s.core.createNamespacedPod({
|
||||
namespace: this.namespace,
|
||||
body: {
|
||||
@@ -119,7 +125,7 @@ export class KubernetesWorkloadManager implements WorkloadManager {
|
||||
},
|
||||
},
|
||||
spec: {
|
||||
...this.addPlacementTags(this.#defaultPodSpec, opts.placementTags),
|
||||
...podSpec,
|
||||
affinity: this.#getAffinity(opts),
|
||||
tolerations: this.#getScheduleTolerations(this.#isScheduledRun(opts)),
|
||||
terminationGracePeriodSeconds: 60 * 60,
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import type { k8s } from "../clients/kubernetes.js";
|
||||
|
||||
/**
|
||||
* Relative path (kubelet seccomp root) of the profile blocking only io_uring
|
||||
* syscalls. Must match the profile deployed to worker nodes.
|
||||
*/
|
||||
export const BLOCK_IO_URING_SECCOMP_PROFILE = "profiles/block-io-uring.json";
|
||||
|
||||
/**
|
||||
* Node >= 24 always creates io_uring fds, which can't be checkpointed. Blocking
|
||||
* io_uring_setup makes libuv fall back to epoll. Other runtimes don't need this,
|
||||
* so the profile is only applied for node-24+. Tolerates an "experimental-" prefix.
|
||||
*/
|
||||
export function withBlockIoUringSeccompProfile(
|
||||
podSpec: Omit<k8s.V1PodSpec, "containers">,
|
||||
runtime: string | null | undefined
|
||||
): Omit<k8s.V1PodSpec, "containers"> {
|
||||
const match = runtime ? /^(?:experimental-)?node-(\d+)$/.exec(runtime) : null;
|
||||
if (!match || Number(match[1]) < 24) {
|
||||
return podSpec;
|
||||
}
|
||||
|
||||
return {
|
||||
...podSpec,
|
||||
securityContext: {
|
||||
...podSpec.securityContext,
|
||||
seccompProfile: {
|
||||
type: "Localhost",
|
||||
localhostProfile: BLOCK_IO_URING_SECCOMP_PROFILE,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -16,6 +16,8 @@ export interface WorkloadManagerOptions {
|
||||
snapshotPollIntervalSeconds?: number;
|
||||
additionalEnvVars?: Record<string, string>;
|
||||
dockerAutoremove?: boolean;
|
||||
// Whether CRIU checkpoint/restore is enabled for this deployment
|
||||
checkpointsEnabled?: boolean;
|
||||
}
|
||||
|
||||
export interface WorkloadManager {
|
||||
@@ -40,6 +42,8 @@ export interface WorkloadManagerCreateOptions {
|
||||
projectId: string;
|
||||
deploymentFriendlyId: string;
|
||||
deploymentVersion: string;
|
||||
// Canonical runtime identifier (e.g. "node", "node-22", "node-24")
|
||||
runtime?: string;
|
||||
runId: string;
|
||||
runFriendlyId: string;
|
||||
snapshotId: string;
|
||||
|
||||
Reference in New Issue
Block a user