|
1 | 1 | import flyte |
2 | 2 | import flyte.app |
3 | | -from flyte.app.extras import FastAPIAppEnvironment |
4 | 3 | import pathlib |
5 | 4 |
|
6 | 5 | import kubernetes |
7 | 6 |
|
8 | | -from fastapi import FastAPI |
9 | | - |
10 | | - |
11 | | -N8N_VERSION = "2.4.8" |
12 | | -N8N_RUNNERS_AUTH_TOKEN = "your-secret-here" |
13 | | - |
14 | | -n8n_with_runner_pod_template = flyte.PodTemplate( |
15 | | - primary_container_name="app", |
16 | | - pod_spec=kubernetes.client.V1PodSpec( |
17 | | - containers=[ |
18 | | - # Primary container: n8n main server |
19 | | - kubernetes.client.V1Container( |
20 | | - name="app", |
21 | | - image=f"n8nio/n8n:{N8N_VERSION}", |
22 | | - ports=[ |
23 | | - kubernetes.client.V1ContainerPort(container_port=5678), |
24 | | - ], |
25 | | - env=[ |
26 | | - kubernetes.client.V1EnvVar(name="N8N_RUNNERS_ENABLED", value="true"), |
27 | | - kubernetes.client.V1EnvVar(name="N8N_RUNNERS_MODE", value="external"), |
28 | | - kubernetes.client.V1EnvVar(name="N8N_RUNNERS_BROKER_LISTEN_ADDRESS", value="0.0.0.0"), |
29 | | - kubernetes.client.V1EnvVar(name="N8N_RUNNERS_AUTH_TOKEN", value=N8N_RUNNERS_AUTH_TOKEN), |
30 | | - kubernetes.client.V1EnvVar(name="N8N_NATIVE_PYTHON_RUNNER", value="true"), |
31 | | - ], |
32 | | - # volume_mounts=[ |
33 | | - # kubernetes.client.V1VolumeMount( |
34 | | - # name="n8n-data", |
35 | | - # mount_path="/home/node/.n8n", |
36 | | - # ), |
37 | | - # ], |
38 | | - ), |
39 | | - # Sidecar container: task runners |
40 | | - kubernetes.client.V1Container( |
41 | | - name="task-runners", |
42 | | - # image=f"n8nio/runners:{N8N_VERSION}", |
43 | | - image="ghcr.io/flyteorg/n8n-task-runner-image:896c4478822858a314074b1a3caf882a", |
44 | | - env=[ |
45 | | - # Connect to n8n broker via localhost since they're in the same pod |
46 | | - kubernetes.client.V1EnvVar(name="N8N_RUNNERS_TASK_BROKER_URI", value="http://localhost:5679"), |
47 | | - kubernetes.client.V1EnvVar(name="N8N_RUNNERS_AUTH_TOKEN", value=N8N_RUNNERS_AUTH_TOKEN), |
48 | | - ], |
49 | | - ), |
50 | | - ], |
51 | | - # volumes=[ |
52 | | - # kubernetes.client.V1Volume( |
53 | | - # name="n8n-data", |
54 | | - # empty_dir=kubernetes.client.V1EmptyDirVolumeSource(), |
55 | | - # ), |
56 | | - # ], |
| 7 | + |
| 8 | +def n8n_pod_template(version: str, runner_auth_token: str, runner_image_uri: str | None = None) -> flyte.PodTemplate: |
| 9 | + return flyte.PodTemplate( |
| 10 | + primary_container_name="app", |
| 11 | + pod_spec=kubernetes.client.V1PodSpec( |
| 12 | + containers=[ |
| 13 | + # Primary container: n8n main server |
| 14 | + kubernetes.client.V1Container(name="app", image=f"n8nio/n8n:{version}"), |
| 15 | + # Sidecar container: task runners |
| 16 | + kubernetes.client.V1Container( |
| 17 | + name="task-runners", |
| 18 | + image=runner_image_uri or f"n8nio/runners:{version}", |
| 19 | + env=[ |
| 20 | + # Connect to n8n broker via localhost since they're in the same pod |
| 21 | + kubernetes.client.V1EnvVar(name="N8N_RUNNERS_TASK_BROKER_URI", value="http://127.0.0.1:5679"), |
| 22 | + kubernetes.client.V1EnvVar(name="N8N_RUNNERS_AUTH_TOKEN", value=runner_auth_token), |
| 23 | + ], |
| 24 | + ), |
| 25 | + ], |
| 26 | + ) |
57 | 27 | ) |
58 | | -) |
59 | 28 |
|
60 | 29 |
|
61 | | -# TODO: |
62 | | -# - ✅ Add postgres database for user data persistence |
63 | | -# - Set up external runners here: https://docs.n8n.io/hosting/configuration/task-runners/#setting-up-external-mode |
64 | | -# - Support python nodes: https://docs.n8n.io/code/code-node/#python-native |
65 | | -# - Add support for Flyte nodes: https://docs.n8n.io/hosting/configuration/task-runners/#adding-extra-dependencies |
66 | | -n8n_app_image = ( |
67 | | - flyte.Image.from_base("node:24-slim") |
68 | | - .clone(name="n8n-app-image") |
69 | | - .with_pip_packages("flyte==2.0.0b54", "fastapi", "uvicorn") |
70 | | - .with_apt_packages("ca-certificates", "curl", "gnupg", "npm") |
71 | | - .with_commands(["npm install -g n8n@2.4.8"]) |
72 | | -) |
73 | | - |
74 | | -# launcher_url = "https://github.com/n8n-io/task-runner-launcher/releases/download/1.4.2/task-runner-launcher-1.4.2-linux-amd64.tar.gz" |
75 | | - |
76 | | -# n8n_task_runner_image = ( |
77 | | -# flyte.Image.from_base("node:24-slim") |
78 | | -# .clone(name="n8n-task-runner-image") |
79 | | -# .with_pip_packages("flyte==2.0.0b54", "kubernetes") |
80 | | -# .with_apt_packages("ca-certificates", "curl", "gnupg", "npm") |
81 | | -# # install the task-runner-launcher: https://github.com/n8n-io/task-runner-launcher |
82 | | -# .with_commands([ |
83 | | -# f"curl -L -o /tmp/task-runner-launcher.tar.gz {launcher_url}", |
84 | | -# "tar -xzf /tmp/task-runner-launcher.tar.gz -C /usr/local/bin", |
85 | | -# "chmod +x /usr/local/bin/task-runner-launcher", |
86 | | -# "rm /tmp/task-runner-launcher.tar.gz", |
87 | | -# ]) |
88 | | -# .with_source_file(pathlib.Path(__file__).parent / "n8n-task-runners.json", "/etc/n8n-task-runners.json") |
89 | | -# ) |
90 | | - |
91 | | -bump = "4" |
92 | 30 | n8n_app = flyte.app.AppEnvironment( |
93 | 31 | name="n8n-app", |
94 | | - image=n8n_app_image, |
95 | | - # pod_template=n8n_with_runner_pod_template, |
96 | | - resources=flyte.Resources(cpu=2, memory="2Gi"), |
| 32 | + resources=flyte.Resources(cpu=4, memory="8Gi"), |
| 33 | + scaling=flyte.app.Scaling(replicas=(0, 1)), |
97 | 34 | port=5678, |
98 | 35 | command=["n8n", "start"], |
99 | 36 | secrets=[ |
100 | 37 | flyte.Secret("n8n_postgres_password", as_env_var="DB_POSTGRESDB_PASSWORD"), |
| 38 | + flyte.Secret("n8n_encryption_key", as_env_var="N8N_ENCRYPTION_KEY"), |
101 | 39 | ], |
102 | 40 | requires_auth=False, |
103 | 41 | env_vars={ |
104 | | - "N8N_ENCRYPTION_KEY": "abc123", |
105 | | - |
106 | | - # db config |
107 | | - # https://docs.n8n.io/hosting/installation/docker/#using-with-postgresql |
| 42 | + "N8N_RUNNERS_ENABLED": "true", |
| 43 | + "N8N_RUNNERS_MODE": "external", |
| 44 | + "N8N_RUNNERS_BROKER_LISTEN_ADDRESS": "0.0.0.0", |
| 45 | + "N8N_NATIVE_PYTHON_RUNNER": "true", |
| 46 | + |
| 47 | + # db config: https://docs.n8n.io/hosting/installation/docker/#using-with-postgresql |
108 | 48 | "DB_TYPE": "postgresdb", |
109 | 49 | "DB_POSTGRESDB_HOST": "aws-0-us-west-2.pooler.supabase.com", |
110 | 50 | "DB_POSTGRESDB_DATABASE": "postgres", |
111 | 51 | "DB_POSTGRESDB_USER": "postgres.qcfcidgymclxvslgphyb", |
112 | 52 | "DB_POSTGRESDB_PORT": "6543", |
113 | 53 | "DB_POSTGRESDB_SCHEMA": "public", |
114 | | - |
115 | | - "BUMP": bump, |
116 | 54 | } |
117 | 55 | ) |
118 | 56 |
|
119 | 57 |
|
120 | | -app = FastAPI() |
| 58 | +def build_runner_image() -> flyte.Image: |
| 59 | + flyte.init_from_config(image_builder="local") |
121 | 60 |
|
122 | | -@app.get("/") |
123 | | -async def root(): |
124 | | - import os |
| 61 | + image = flyte.Image.from_dockerfile( |
| 62 | + pathlib.Path(__file__).parent / "task_runner.dockerfile", |
| 63 | + registry="ghcr.io/flyteorg", |
| 64 | + name="n8n-task-runner-image", |
| 65 | + ) |
| 66 | + return flyte.build(image, wait=True) |
125 | 67 |
|
126 | | - return {"message": "Hello World", "n8n_app_endpoint": os.getenv("N8N_APP_URL")} |
127 | 68 |
|
128 | | -n8n_debugger = FastAPIAppEnvironment( |
129 | | - app=app, |
130 | | - name="n8n-debugger", |
131 | | - image=flyte.Image.from_debian_base().with_pip_packages("fastapi", "uvicorn", "kubernetes"), |
132 | | - resources=flyte.Resources(cpu=2, memory="2Gi"), |
133 | | - port=8080, |
134 | | - requires_auth=False, |
135 | | - depends_on=[n8n_app], |
136 | | - parameters=[ |
137 | | - flyte.app.Parameter( |
138 | | - name="n8n_app_endpoint", |
139 | | - value=flyte.app.AppEndpoint(app_name="n8n-app", public=False), |
140 | | - env_var="N8N_APP_URL", |
141 | | - ), |
142 | | - ], |
143 | | -) |
| 69 | +def get_webhook_url(subdomain: str) -> str: |
| 70 | + cfg = get_init_config() |
| 71 | + return f"https://{subdomain}.apps.{cfg.client.endpoint.replace('dns:///', '').rstrip('/')}/" |
144 | 72 |
|
145 | | -# n8n_task_runner = flyte.app.AppEnvironment( |
146 | | -# name="n8n-task-runner", |
147 | | -# image="ghcr.io/flyteorg/n8n-task-runner-image:896c4478822858a314074b1a3caf882a", |
148 | | -# resources=flyte.Resources(cpu=2, memory="2Gi"), |
149 | | -# port=5678, |
150 | | -# command=["/usr/local/bin/task-runner-launcher", "javascript", "python"], |
151 | | -# requires_auth=False, |
152 | | -# env_vars={ |
153 | | -# "N8N_RUNNERS_LAUNCHER_LOG_LEVEL": "debug", |
154 | | -# "N8N_RUNNERS_TASK_BROKER_URI": "http://n8n-app.flytesnacks-development.svc.cluster.local:5679", |
155 | | -# "N8N_RUNNERS_AUTH_TOKEN": "test-token", |
156 | | - |
157 | | -# "BUMP": bump, |
158 | | -# }, |
159 | | -# depends_on=[n8n_app, n8n_debugger], |
160 | | -# ) |
161 | 73 |
|
162 | 74 | if __name__ == "__main__": |
163 | | - flyte.init_from_config() |
164 | | - # app = flyte.serve(n8n_debugger) |
165 | | - app = flyte.serve(n8n_app) |
| 75 | + import random |
| 76 | + import string |
| 77 | + |
| 78 | + from flyte._initialize import get_init_config |
| 79 | + |
| 80 | + n8n_version = "2.6.3" |
| 81 | + # Create a random 32-character alphanumeric string for the n8n runners auth token. it's okay |
| 82 | + # to regenerate this every time the app is deployed, since only the main n8n app and the runner |
| 83 | + # sidecar container use this token. |
| 84 | + n8n_runners_auth_token = ''.join(random.choices(string.ascii_letters + string.digits, k=32)) |
| 85 | + |
| 86 | + image = build_runner_image() |
| 87 | + |
| 88 | + flyte.init_from_config(image_builder="remote") |
| 89 | + pod_template = n8n_pod_template( |
| 90 | + version=n8n_version, |
| 91 | + runner_auth_token=n8n_runners_auth_token, |
| 92 | + runner_image_uri=image.uri, |
| 93 | + ) |
| 94 | + |
| 95 | + subdomain = "n8n-app" |
| 96 | + webhook_url = get_webhook_url(subdomain) |
| 97 | + |
| 98 | + app = flyte.serve( |
| 99 | + n8n_app.clone_with( |
| 100 | + name="n8n-app-with-runners", |
| 101 | + pod_template=pod_template, |
| 102 | + domain=flyte.app.Domain(subdomain=subdomain), |
| 103 | + env_vars=n8n_app.env_vars | { |
| 104 | + "WEBHOOK_URL": webhook_url, |
| 105 | + "N8N_RUNNERS_AUTH_TOKEN": n8n_runners_auth_token, |
| 106 | + }, |
| 107 | + ) |
| 108 | + ) |
166 | 109 | print(app.url) |
0 commit comments