app chart: soft node-affinity steering web+worker pods to app-worker nodes #7

Merged
plat merged 2 commits from feat/app-worker-node-affinity into main 2026-07-17 16:51:25 +00:00
Owner

Why

platgold2 now has a second node (pg-grand, plat/mitosis#101), but it's underused: the server sits at 241/250 pods while pg-grand holds 8. Root cause (proven live): the default scheduler scores nodes by free CPU/mem, so the 80-core server always outscores the 4-core droplet — new pods pile onto the server until its hard 250-pod IP cap forces overflow, leaving the agent idle. A plain new pod scheduled with the server at 241/250 still lands on the server (verified).

What

A weight-100 soft nodeAffinity toward node-role=app-worker on the app's web + worker Deployments (via a shared app.appWorkerAffinity helper). Steers the light, numerous app/preview/worker pods onto the agent; CNPG databases are intentionally left on the big node (the agent is 8GB — fine for many light pods, not for heavy Postgres). Proven: a weight-100 affinity pulled test pods to pg-grand 3/3 despite the server's CPU/mem advantage.

  • placement.preferAppWorker (default true), appWorkerWeight (100), appWorkerLabelKey/Value (node-role/app-worker) — all overridable per-app.
  • Soft (preferred…): pods still schedule anywhere when no app-worker has room.
  • No-op on single-node clusters (no node carries the label). Verified: disabled case emits no affinity; full render is valid YAML; default-on.

Effect / rollout

Forward-looking: existing apps pick it up on their next release (helm upgrade rolls the Deployment → pod reschedules toward pg-grand); all new apps + preview envs get it immediately. Databases and already-placed PVC-pinned pods are unaffected. This relieves the server's pod-IP churn where it's worst (light/ephemeral pods) without pressuring the small node's RAM.

Not included (deliberate)

  • CNPG database placement (would need the mcp overlay renderer + per-DB memory review) — left for a follow-up if we want to spread stateful load too.
  • Chart bumped 0.2.0 → 0.2.1.

🤖 Generated with Claude Code

## Why platgold2 now has a second node (pg-grand, plat/mitosis#101), but it's underused: the server sits at **241/250 pods** while pg-grand holds **8**. Root cause (proven live): the default scheduler scores nodes by *free CPU/mem*, so the 80-core server always outscores the 4-core droplet — new pods pile onto the server until its hard 250-pod IP cap forces overflow, leaving the agent idle. A plain new pod scheduled with the server at 241/250 still lands on the server (verified). ## What A weight-100 **soft** `nodeAffinity` toward `node-role=app-worker` on the app's **web + worker** Deployments (via a shared `app.appWorkerAffinity` helper). Steers the light, numerous app/preview/worker pods onto the agent; **CNPG databases are intentionally left on the big node** (the agent is 8GB — fine for many light pods, not for heavy Postgres). Proven: a weight-100 affinity pulled test pods to pg-grand **3/3** despite the server's CPU/mem advantage. - `placement.preferAppWorker` (default **true**), `appWorkerWeight` (100), `appWorkerLabelKey/Value` (node-role/app-worker) — all overridable per-app. - **Soft** (`preferred…`): pods still schedule anywhere when no app-worker has room. - **No-op on single-node clusters** (no node carries the label). Verified: disabled case emits no affinity; full render is valid YAML; default-on. ## Effect / rollout Forward-looking: existing apps pick it up on their next release (helm upgrade rolls the Deployment → pod reschedules toward pg-grand); all new apps + preview envs get it immediately. Databases and already-placed PVC-pinned pods are unaffected. This relieves the server's pod-IP churn where it's worst (light/ephemeral pods) without pressuring the small node's RAM. ## Not included (deliberate) - CNPG database placement (would need the mcp overlay renderer + per-DB memory review) — left for a follow-up if we want to spread stateful load too. - Chart bumped 0.2.0 → 0.2.1. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
RBAC-3: trust platform forwardAuth headers (X-Plat-*) instead of in-app OAuth (plat/mitosis#30)
Some checks failed
check / check (push) Successful in 1m40s
check / check (pull_request) Successful in 1m34s
preview / preview (pull_request) Failing after 19s
1601945102
Add a "header-trust" auth mode selected by PLAT_FORWARD_AUTH=1. When enabled the
app runs ZERO auth code of its own: RBAC-2's edge forwardAuth service has already
authenticated the caller against Forgejo, run the repo-permission probe, and
injected verified X-Plat-User / X-Plat-Perm / X-Plat-Manage headers (it strips
inbound X-Plat-* first, so they are authoritative in-process). The app reads them
via src/platform-auth.ts, gates the example /api/manage route on X-Plat-Manage=1,
and FAILS CLOSED when X-Plat-User is absent (401) so a misconfigured deploy that
bypasses the edge cannot leak.

Default (flag unset) is unchanged: the in-app better-auth Forgejo OAuth flow runs
exactly as before — verified byte-identical homepage + default /api/me. better-auth
(src/auth.ts) is now imported lazily so header-trust apps need no OAuth/DB-auth env
at all.

- src/platform-auth.ts: header-trust identity + manage gate (case-insensitive read)
- src/platform-auth.test.ts: unit tests for the decision (node:test)
- src/server.ts: two-mode wiring; lazy better-auth import; /api/manage example
- src/homepage.ts: header-trust auth box variant (no in-app sign-in)
- README.md: documents the two modes + the edge-is-the-gate / fail-closed contract

Live E2E on a germinated cluster with the forwardAuth edge is deferred; the header
decision is unit-validated and boot-smoked here.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
app chart: soft node-affinity steering web+worker pods to app-worker nodes (#101)
Some checks failed
check / check (push) Successful in 1m29s
check / check (pull_request) Successful in 1m29s
preview / preview (pull_request) Failing after 18s
f93b1bf1c5
The default scheduler scores by free CPU/mem, so on an asymmetric cluster (big
control-plane node + small remote agents) new pods pile onto the big node until
its ~250-pod IP cap forces overflow, leaving agents idle. A weight-100 soft
nodeAffinity toward node-role=app-worker steers the light web+worker pods onto
the agents; databases stay on the big node. Soft, default-on, no-op on
single-node clusters. Proven live: weight-100 affinity pulled test pods to the
agent 3/3 despite the server's CPU/mem advantage.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
plat merged commit 2115e73c0c into main 2026-07-17 16:51:25 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
open-platform/app-template!7
No description provided.