Run scheduled jobs, webhooks and APIs on Kubernetes from one config. Scale with kubectl: cron coordination, health probes and pod discovery are built in.
Three replicas, one cron line. Each pod holds its own copy of the config, so each pod's scheduler sees that line and wants to run it. Without coordination, all three do.
One manifest: a namespace, a throwaway Postgres, the RBAC for pod discovery, a headless Service for the mesh, a config with two routes, and the Deployment at three replicas. Replace the API key and apply it.
Every replica health-checks every other replica, itself included:
One declaration on every pod, meaning "this agent is one of several serving the same configs". The nodes then race for a durable claim on (config, interface, minute) in Postgres, and exactly one wins. The other two skip the tick.
/livez answers as soon as the process is up. /readyz answers once configs are loaded, and returns 503 with "starting" until then — which is what stops the Service sending traffic to a pod that has nothing to serve yet.
Worth knowing what Kubernetes actually mounts here, because it bites more tools than it should: a ConfigMap volume is not a directory of files. It's a symlink farm — ..data pointing at a timestamped directory, and one symlink per key.
The manifest already runs three, so go further:
The one thing that genuinely needs wiring: a WebSocket publish arrives at whichever pod the load balancer picked, but its subscribers are spread across every pod.
This is the part with no equivalent elsewhere. A discover action returns the live members of a service as an array, and lookup: fans actions out across all of them:
From the same config, no extra deployment: an OpenAPI document generated from the assert tests, Prometheus metrics per route, and an OpenTelemetry trace per request showing each action and how long it took. When a nightly job gets slow, the trace says whether it was your database or the API you called, before you start guessing.
Everything in step 6 ships as one pack — Kubernetes Service Discovery — with four routes: list pods by label with node, image and readiness, find peers by DNS with no RBAC at all, health-check every replica, and broadcast one call to the whole Deployment. Install it, apply the RoleBinding above, and the routes work against your own cluster.
sessionAffinity: ClientIP will pin you to one pod. It's a reasonable-looking default that quietly turns three replicas into one — a load test looked beautifully stable until we noticed 12 of 12 requests hit the same pod. Leave it None unless you specifically need stickiness.