GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — feat(supervisor): reserve configured source ports on run pods · triggerdotdev/trigger.dev@65d123f

File tree

Expand file treeCollapse file tree
Open diff view settings
Filter options
  • .server-changes
    • supervisor-reserved-source-ports.md
  • apps/supervisor/src
    • env.ts
    • envUtil.test.ts
    • envUtil.ts
    • workloadManager
      • kubernetes.test.ts
      • kubernetes.ts
      • kubernetesPodSpec.ts
Expand file treeCollapse file tree
Open diff view settings
Collapse file

‎.server-changes/supervisor-reserved-source-ports.md‎

Copy file name to clipboard
+6Lines changed: 6 additions & 0 deletions
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
area: supervisor
3+
type: feature
4+
---
5+
6+
Set `KUBERNETES_POD_RESERVED_SOURCE_PORTS` on the supervisor (for example `50080,50081`) to stop runs using those ports for outbound connections, which is useful to avoid port clashes.
Collapse file

‎apps/supervisor/src/env.ts‎

Copy file name to clipboardExpand all lines: apps/supervisor/src/env.ts
+2Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
BoolEnv,
88
NodeLabelValue,
99
OrgPlacementOverrides,
10+
ReservedPorts,
1011
Tolerations,
1112
} from "./envUtil.js";
1213

@@ -280,6 +281,7 @@ export const Env = z
280281
.default("node-24-plus"),
281282
KUBERNETES_RUNNER_SECURITY_CONTEXT: z.enum(["off", "baseline", "restricted"]).default("off"),
282283
KUBERNETES_RUNNER_RUN_AS_USER: z.coerce.number().int().min(1).default(1000),
284+
KUBERNETES_POD_RESERVED_SOURCE_PORTS: ReservedPorts,
283285

284286
// Pod DNS config: override the cluster default ndots to `KUBERNETES_POD_DNS_NDOTS`.
285287
// Default k8s ndots is 5: any name with fewer than 5 dots (e.g. `api.example.com`, 2 dots) is first walked
Collapse file

‎apps/supervisor/src/envUtil.test.ts‎

Copy file name to clipboardExpand all lines: apps/supervisor/src/envUtil.test.ts
+22Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
AdditionalEnvVars,
55
NodeLabelValue,
66
OrgPlacementOverrides,
7+
ReservedPorts,
78
Tolerations,
89
} from "./envUtil.js";
910

@@ -113,6 +114,27 @@ describe("NodeLabelValue", () => {
113114
});
114115
});
115116

117+
describe("ReservedPorts", () => {
118+
it("is unset when empty", () => {
119+
for (const value of [undefined, "", " "]) {
120+
expect(ReservedPorts.parse(value)).toBeUndefined();
121+
}
122+
});
123+
124+
it("accepts ports and ranges, stripping whitespace the kernel would reject", () => {
125+
expect(ReservedPorts.parse("50080,50081")).toBe("50080,50081");
126+
expect(ReservedPorts.parse(" 50080 , 50000-50100 ")).toBe("50080,50000-50100");
127+
expect(ReservedPorts.parse("0,65535,8080-8080")).toBe("0,65535,8080-8080");
128+
});
129+
130+
it.each(["50080,", ",50080", "50080 50081", "65536", "50100-50000", "08080", "0x50", "-1", "a"])(
131+
"refuses %j, so a bad value stops the supervisor from starting",
132+
(value) => {
133+
expect(ReservedPorts.safeParse(value).success).toBe(false);
134+
}
135+
);
136+
});
137+
116138
describe("Tolerations", () => {
117139
it("should parse key=value entries as Equal", () => {
118140
expect(Tolerations.parse("dedicated=runs:NoSchedule")).toEqual([
Collapse file

‎apps/supervisor/src/envUtil.ts‎

Copy file name to clipboardExpand all lines: apps/supervisor/src/envUtil.ts
+30Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,36 @@ export const Tolerations = z.string().transform((val, ctx) => {
146146
});
147147
});
148148

149+
// The kernel rejects whitespace between entries and reads a leading zero as octal.
150+
const PORT_OR_RANGE = /^(0|[1-9]\d{0,4})(?:-(0|[1-9]\d{0,4}))?$/;
151+
152+
export const ReservedPorts = z
153+
.string()
154+
.optional()
155+
.transform((val, ctx) => {
156+
if (val === undefined || val.trim() === "") {
157+
return undefined;
158+
}
159+
160+
const entries = val.split(",").map((entry) => entry.trim());
161+
162+
for (const entry of entries) {
163+
const match = PORT_OR_RANGE.exec(entry);
164+
const low = Number(match?.[1]);
165+
const high = Number(match?.[2] ?? match?.[1]);
166+
167+
if (!match || high > 65535 || low > high) {
168+
ctx.addIssue({
169+
code: z.ZodIssueCode.custom,
170+
message: `Invalid reserved port "${entry}". Must be a port or an ascending low-high range, within 0-65535.`,
171+
});
172+
return z.NEVER;
173+
}
174+
}
175+
176+
return entries.join(",");
177+
});
178+
149179
/**
150180
* Scalar values are coerced: YAML/JSON easily produce `true` or `3` where a label
151181
* value is meant, and Kubernetes label values are always strings. An empty value
Collapse file

‎apps/supervisor/src/workloadManager/kubernetes.test.ts‎

Copy file name to clipboardExpand all lines: apps/supervisor/src/workloadManager/kubernetes.test.ts
+46Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
nodetypeNodeSelector,
44
runPodTolerations,
55
runnerSecurityContext,
6+
withReservedSourcePorts,
67
withRunnerSeccompProfile,
78
withNodeSelector,
89
} from "./kubernetesPodSpec.js";
@@ -155,6 +156,51 @@ describe("withRunnerSeccompProfile", () => {
155156
});
156157
});
157158

159+
describe("withReservedSourcePorts", () => {
160+
const reserved = { name: "net.ipv4.ip_local_reserved_ports", value: "50080,50081" };
161+
162+
it("leaves the spec untouched when unset", () => {
163+
for (const ports of [undefined, ""]) {
164+
expect(withReservedSourcePorts(basePodSpec, ports)).toBe(basePodSpec);
165+
}
166+
});
167+
168+
it("adds the sysctl and keeps the rest of the security context", () => {
169+
expect(withReservedSourcePorts(basePodSpec, "50080,50081")).toEqual({
170+
...basePodSpec,
171+
securityContext: { ...basePodSpec.securityContext, sysctls: [reserved] },
172+
});
173+
});
174+
175+
it("adds a security context when the spec has none", () => {
176+
expect(withReservedSourcePorts({ restartPolicy: "Never" }, "50080,50081")).toEqual({
177+
restartPolicy: "Never",
178+
securityContext: { sysctls: [reserved] },
179+
});
180+
});
181+
182+
it("preserves existing sysctls", () => {
183+
const existing = { name: "net.ipv4.ip_local_port_range", value: "32768 60999" };
184+
const podSpec = { ...basePodSpec, securityContext: { sysctls: [existing] } };
185+
186+
expect(withReservedSourcePorts(podSpec, "50080,50081").securityContext?.sysctls).toEqual([
187+
existing,
188+
reserved,
189+
]);
190+
});
191+
192+
it("replaces an existing value rather than duplicating the name", () => {
193+
const podSpec = {
194+
...basePodSpec,
195+
securityContext: { sysctls: [{ ...reserved, value: "40000" }] },
196+
};
197+
198+
expect(withReservedSourcePorts(podSpec, "50080,50081").securityContext?.sysctls).toEqual([
199+
reserved,
200+
]);
201+
});
202+
});
203+
158204
describe("runnerSecurityContext", () => {
159205
it("sets nothing when off", () => {
160206
expect(runnerSecurityContext("off", 1000, "node-24")).toBeUndefined();
Collapse file

‎apps/supervisor/src/workloadManager/kubernetes.ts‎

Copy file name to clipboardExpand all lines: apps/supervisor/src/workloadManager/kubernetes.ts
+10-6Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
nodetypeNodeSelector,
1919
runPodTolerations,
2020
runnerSecurityContext,
21+
withReservedSourcePorts,
2122
withRunnerSeccompProfile,
2223
withNodeSelector,
2324
} from "./kubernetesPodSpec.js";
@@ -137,12 +138,15 @@ export class KubernetesWorkloadManager implements WorkloadManager {
137138
);
138139
}
139140
}
140-
const podSpec = withRunnerSeccompProfile(basePodSpec, {
141-
profilePath: env.KUBERNETES_RUNNER_SECCOMP_PROFILE_PATH,
142-
runtimes: env.KUBERNETES_RUNNER_SECCOMP_PROFILE_RUNTIMES,
143-
runtime: opts.runtime,
144-
checkpointsEnabled: this.opts.checkpointsEnabled,
145-
});
141+
const podSpec = withReservedSourcePorts(
142+
withRunnerSeccompProfile(basePodSpec, {
143+
profilePath: env.KUBERNETES_RUNNER_SECCOMP_PROFILE_PATH,
144+
runtimes: env.KUBERNETES_RUNNER_SECCOMP_PROFILE_RUNTIMES,
145+
runtime: opts.runtime,
146+
checkpointsEnabled: this.opts.checkpointsEnabled,
147+
}),
148+
env.KUBERNETES_POD_RESERVED_SOURCE_PORTS
149+
);
146150

147151
await this.k8s.core.createNamespacedPod({
148152
namespace: this.namespace,
Collapse file

‎apps/supervisor/src/workloadManager/kubernetesPodSpec.ts‎

Copy file name to clipboardExpand all lines: apps/supervisor/src/workloadManager/kubernetesPodSpec.ts
+24Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,30 @@ export function withRunnerSeccompProfile(
9797
};
9898
}
9999

100+
const RESERVED_PORTS_SYSCTL = "net.ipv4.ip_local_reserved_ports";
101+
102+
export function withReservedSourcePorts(
103+
podSpec: Omit<k8s.V1PodSpec, "containers">,
104+
ports: string | undefined
105+
): Omit<k8s.V1PodSpec, "containers"> {
106+
if (!ports) {
107+
return podSpec;
108+
}
109+
110+
return {
111+
...podSpec,
112+
securityContext: {
113+
...podSpec.securityContext,
114+
sysctls: [
115+
...(podSpec.securityContext?.sysctls ?? []).filter(
116+
(sysctl) => sysctl.name !== RESERVED_PORTS_SYSCTL
117+
),
118+
{ name: RESERVED_PORTS_SYSCTL, value: ports },
119+
],
120+
},
121+
};
122+
}
123+
100124
const BUN_RUN_AS_USER = 1001;
101125

102126
/**

0 commit comments

Comments
0 (0)
You are visitor 01402520 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.