Skip to content

Commit 451a438

Browse files
committed
Port conflict management
1 parent 6b8b072 commit 451a438

File tree

4 files changed

+68
-7
lines changed

4 files changed

+68
-7
lines changed

docs/setup.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ To set up the launcher:
2020
- The launcher exposes a health check endpoint at `/healthz` on port `5680`, configurable via `N8N_RUNNERS_LAUNCHER_HEALTH_CHECK_PORT`.
2121
- The task broker exposes a health check endpoint at `/healthz` on port `5679`, configurable via `N8N_RUNNERS_BROKER_PORT`.
2222

23+
<br>
24+
2325
```mermaid
2426
sequenceDiagram
2527
participant k8s
@@ -56,6 +58,7 @@ Example config file at `/etc/n8n-task-runners.json`:
5658
"--disable-proto=delete",
5759
"/usr/local/lib/node_modules/n8n/node_modules/@n8n/task-runner/dist/start.js"
5860
],
61+
"health-check-server-port": "5681",
5962
"allowed-env": ["PATH", "GENERIC_TIMEZONE"],
6063
"env-overrides": {
6164
"N8N_RUNNERS_TASK_TIMEOUT": "80",
@@ -73,6 +76,7 @@ Example config file at `/etc/n8n-task-runners.json`:
7376
"args": [
7477
"/usr/local/lib/python3.13/site-packages/n8n/task-runner-python/main.py"
7578
],
79+
"health-check-server-port": "5682",
7680
"allowed-env": ["PATH", "GENERIC_TIMEZONE"],
7781
"env-overrides": {
7882
"N8N_RUNNERS_TASK_TIMEOUT": "30",
@@ -90,6 +94,7 @@ Example config file at `/etc/n8n-task-runners.json`:
9094
| `workdir` | Path where the task runner's `command` will run. |
9195
| `command` | Command to start the task runner. |
9296
| `args` | Args and flags to use with `command`. |
97+
| `health-check-server-port` | Port for the runner's health check server. When a single runner is configured, this is optional and defaults to `5681`. When multiple runners are configured, this is required and must be unique per runner.
9398
| `allowed-env` | Env vars that the launcher will pass through from its own environment to the runner. See [environment](environment.md). |
9499
| `env-overrides` | Env vars that the launcher will set directly on the runner. See [environment](environment.md). |
95100

@@ -104,8 +109,9 @@ The launcher can pass env vars to task runners in two ways, as specified in the
104109
| `allowed-env` | Env vars filtered from the launcher's own environment | Passing env vars common to all runner types |
105110
| `env-overrides` | Env vars set by the launcher directly on the runner, with precedence over `allowed-env` | Passing env vars specific to a single runner type |
106111

107-
Exceptionally, these three env vars cannot be disallowed or overridden:
112+
Exceptionally, these four env vars cannot be disallowed or overridden:
108113

109114
- `N8N_RUNNERS_TASK_BROKER_URI`
110115
- `N8N_RUNNERS_GRANT_TOKEN`
111-
- `N8N_RUNNERS_HEALTH_CHECK_SERVER_ENABLED=true`
116+
- `N8N_RUNNERS_HEALTH_CHECK_SERVER_ENABLED=true`
117+
- `N8N_RUNNERS_HEALTH_CHECK_SERVER_PORT`

internal/commands/launch.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ func (c *LaunchCommand) Execute(launcherConfig *config.LauncherConfig, runnerTyp
4545
// 2. prepare env vars to pass to runner
4646

4747
runnerEnv := env.PrepareRunnerEnv(baseConfig, runnerConfig, c.logger)
48-
runnerServerURI := fmt.Sprintf("http://%s:%s", baseConfig.RunnerHealthCheckServerHost, baseConfig.RunnerHealthCheckServerPort)
48+
runnerServerURI := fmt.Sprintf("http://%s:%s", baseConfig.RunnerHealthCheckServerHost, runnerConfig.HealthCheckServerPort)
4949

5050
for {
5151
// 3. check until task broker is ready

internal/config/config.go

Lines changed: 54 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,9 @@ type BaseConfig struct {
4949
// HealthCheckServerPort is the port for the launcher's health check server.
5050
HealthCheckServerPort string `env:"N8N_RUNNERS_LAUNCHER_HEALTH_CHECK_PORT, default=5680"`
5151

52-
// RunnerHealthCheckServerHost is the host for the runner's health check server.
52+
// RunnerHealthCheckServerHost is the host for all runners' health check servers.
5353
RunnerHealthCheckServerHost string `env:"N8N_RUNNERS_HEALTH_CHECK_SERVER_HOST, default=127.0.0.1"`
5454

55-
// RunnerHealthCheckServerPort is the port for the runner's health check server.
56-
RunnerHealthCheckServerPort string `env:"N8N_RUNNERS_HEALTH_CHECK_SERVER_PORT, default=5681"`
57-
5855
// Sentry is the Sentry config for the launcher, a subset of what is defined in:
5956
// https://docs.sentry.io/platforms/go/configuration/options/
6057
Sentry *SentryConfig
@@ -82,6 +79,11 @@ type RunnerConfig struct {
8279
// Arguments for command, currently path to runner entrypoint.
8380
Args []string `json:"args"`
8481

82+
// Port for the runner's health check server.
83+
// When a single runner is configured, this is optional and defaults to 5681.
84+
// When multiple runners are configured, this is required and must be unique per runner.
85+
HealthCheckServerPort string `json:"health-check-server-port,omitempty"`
86+
8587
// Env vars for the launcher to pass from its own environment to the runner.
8688
AllowedEnv []string `json:"allowed-env"`
8789

@@ -180,6 +182,24 @@ func readLauncherConfigFile(runnerTypes []string) (map[string]*RunnerConfig, err
180182
}
181183
}
182184

185+
if len(runnerConfigs) == 1 {
186+
for _, config := range runnerConfigs {
187+
if config.HealthCheckServerPort == "" {
188+
config.HealthCheckServerPort = "5681"
189+
}
190+
}
191+
} else {
192+
for runnerType, config := range runnerConfigs {
193+
if config.HealthCheckServerPort == "" {
194+
return nil, fmt.Errorf("runner %s: health-check-server-port is required with multiple runners", runnerType)
195+
}
196+
}
197+
}
198+
199+
if err := validateRunnerPorts(runnerConfigs); err != nil {
200+
return nil, err
201+
}
202+
183203
if taskRunnersNum == 1 {
184204
logs.Debug("Loaded config file with a single runner config")
185205
} else {
@@ -188,3 +208,33 @@ func readLauncherConfigFile(runnerTypes []string) (map[string]*RunnerConfig, err
188208

189209
return runnerConfigs, nil
190210
}
211+
212+
func validateRunnerPorts(runnerConfigs map[string]*RunnerConfig) error {
213+
reservedPorts := map[string]string{
214+
"5678": "n8n main server",
215+
"5679": "n8n broker server",
216+
"5680": "launcher health check server",
217+
}
218+
219+
usedPorts := make(map[string]string)
220+
221+
for runnerType, config := range runnerConfigs {
222+
port := config.HealthCheckServerPort
223+
224+
if port, err := strconv.Atoi(port); err != nil || port <= 0 || port >= 65536 {
225+
return fmt.Errorf("runner %s: health-check-server-port must be a valid port number", runnerType)
226+
}
227+
228+
if service, exists := reservedPorts[port]; exists {
229+
return fmt.Errorf("runner %s: health-check-server-port %s conflicts with %s", runnerType, port, service)
230+
}
231+
232+
if existingRunner, exists := usedPorts[port]; exists {
233+
return fmt.Errorf("runners %s and %s cannot use the same health-check-server-port %s", existingRunner, runnerType, port)
234+
}
235+
236+
usedPorts[port] = runnerType
237+
}
238+
239+
return nil
240+
}

internal/env/env.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ const (
2222
// EnvVarHealthCheckServerEnabled is the env var to enable the runner's health check server.
2323
EnvVarHealthCheckServerEnabled = "N8N_RUNNERS_HEALTH_CHECK_SERVER_ENABLED"
2424

25+
// EnvVarHealthCheckServerPort is the env var for the runner's health check server port.
26+
EnvVarHealthCheckServerPort = "N8N_RUNNERS_HEALTH_CHECK_SERVER_PORT"
27+
2528
// EnvVarAutoShutdownTimeout is the env var for how long (in seconds) a runner
2629
// may be idle for before exit.
2730
EnvVarAutoShutdownTimeout = "N8N_RUNNERS_AUTO_SHUTDOWN_TIMEOUT"
@@ -103,6 +106,7 @@ var requiredRuntimeEnvVars = []string{
103106
EnvVarTaskBrokerURI,
104107
EnvVarHealthCheckServerEnabled,
105108
EnvVarGrantToken,
109+
EnvVarHealthCheckServerPort,
106110
}
107111

108112
// PrepareRunnerEnv prepares the environment variables to pass to the runner.
@@ -120,6 +124,7 @@ func PrepareRunnerEnv(baseConfig *config.BaseConfig, runnerConfig *config.Runner
120124
}
121125
runnerEnv = append(runnerEnv, fmt.Sprintf("%s=%s", EnvVarTaskBrokerURI, baseConfig.TaskBrokerURI))
122126
runnerEnv = append(runnerEnv, fmt.Sprintf("%s=true", EnvVarHealthCheckServerEnabled))
127+
runnerEnv = append(runnerEnv, fmt.Sprintf("%s=%s", EnvVarHealthCheckServerPort, runnerConfig.HealthCheckServerPort))
123128

124129
// TODO: The next two lines are legacy behavior to remove after deprecation period.
125130
runnerEnv = append(runnerEnv, fmt.Sprintf("%s=%s", EnvVarAutoShutdownTimeout, baseConfig.AutoShutdownTimeout))

0 commit comments

Comments
 (0)