Skip to content

feat(k8s): Kubernetes manifests for the three runtimes - #25

Merged
FullFran merged 6 commits into
mainfrom
feat/kubernetes-manifests
Aug 10, 2026
Merged

feat(k8s): Kubernetes manifests for the three runtimes#25
FullFran merged 6 commits into
mainfrom
feat/kubernetes-manifests

Conversation

@FullFran

@FullFran FullFran commented Aug 10, 2026

Copy link
Copy Markdown
Owner

Adds k8s/ with the manifests to run FeedPulse's three runtimes on Kubernetes, and the CI that proves they work.

What is here

  • k8s/base — Deployments for api, scheduler and worker, plus Service, PDB, Ingress, migration Job, ConfigMap and a documented (non-applied) secret example
  • k8s/overlays/ci — Postgres, Redis and CI-only secrets, so the manifests can be exercised end to end
  • k8s/keda — ScaledObject autoscaling the worker on BullMQ queue depth
  • k8s/monitoring — ServiceMonitor and PodMonitor for kube-prometheus-stack
  • .github/workflows/k8s.yml — schema validation + a real kind cluster
  • k8s/README.md — the decisions and their reasoning

Verified, not asserted

Both jobs are green. The end-to-end job builds the image, side-loads it into kind, applies the overlay, runs migrations, waits for all three runtimes, curls /health and /ready, restarts the worker to exercise graceful shutdown, then installs KEDA and waits for the ScaledObject to report Ready=True — which is what proves the HPA was actually created rather than merely accepted by the webhook.

Two bugs CI caught that review would not have

The KEDA Redis address was wrong twice. First addressFromEnv: REDIS_ADDRESS, but the worker exposes REDIS_URL — a URL, not host:port. Then the short name redis:6379, which fails with server misbehaving: the connection is opened by the KEDA operator in the keda namespace, and short service names only resolve within the caller's own namespace.

The CI overlay disabled ENABLE_AUTH. env.schema.ts rejects that under NODE_ENV=production because it resolves every request to the shared legacy tenant. The guard was right, the overlay was wrong. CI now runs with auth on, as production does.

Decisions worth reviewing

Scheduler is a Deployment, not a CronJob. It owns its own loop via SCHEDULER_TICK_MS (default 15s) and holds warm connections. strategy: Recreate so two never tick at once.

Liveness never touches a dependency. /health is process-only, /ready resolves the base schema. A dependency check in liveness turns a Postgres blip into a cluster-wide CrashLoopBackOff.

Worker declares no replicas — KEDA owns that field.

terminationGracePeriodSeconds (60s) exceeds SHUTDOWN_TIMEOUT_MS (30s) so in-flight jobs drain instead of being SIGKILLed.

Known limitations, stated rather than hidden

Refs #17, #18, #19, #20, #22

Deployments for api, scheduler and worker, plus Service, Ingress, migration
Job, ConfigMap and a documented secret example.

The worker Deployment declares no replicas: a KEDA ScaledObject scales it on
BullMQ queue depth, which is the signal that actually tracks its load. CPU
does not, because the worker blocks on network I/O against slow feeds while
the backlog grows.

Liveness probes never touch Postgres or Redis, so a dependency outage removes
pods from the Service instead of restarting them against a broken dependency.

Refs #17, #18, #19, #20
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

Two jobs: kubeconform over both overlays, then an end-to-end run that builds
the image, applies the CI overlay to a kind cluster and drives it until the
three runtimes are ready, migrations have completed and both probes answer.

Also fixes the KEDA trigger address. It resolved Redis through
addressFromEnv: REDIS_ADDRESS, but the worker exposes REDIS_URL (a full URL)
rather than a host:port pair, so the scaler would never have connected.

Refs #19, #22
The overlay disabled ENABLE_AUTH, which env.schema.ts rejects outright under
NODE_ENV=production: disabling auth resolves every request to the shared
legacy tenant. The guard was right and the overlay was wrong.

Switches AUTH_PROVIDER to api_key so no Clerk credentials are needed, and
seeds BOOTSTRAP_API_KEY for the migration to install.
The connection is opened by the KEDA operator, which runs in the keda
namespace. Short service names only resolve within the caller's own
namespace, so 'redis:6379' failed with 'server misbehaving' and the HPA was
never created.

Found by the kind end-to-end job.
Replaces the 'never applied to a cluster' caveat with the CI that now applies
them, and records the two bugs the end-to-end job caught.
@FullFran
FullFran merged commit 7cef22e into main Aug 10, 2026
8 checks passed
@FullFran
FullFran deleted the feat/kubernetes-manifests branch August 10, 2026 17:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant