Sync the self-hosting stack

This commit is contained in:
2026-10-21 10:11:48 +02:00
parent 6db65866d2
commit e210b4db11
7 changed files with 82 additions and 25 deletions
+14 -7
View File
@@ -50,10 +50,13 @@ and an index can only do that because one server decides. Do not point pods at r
`(replicas + maxSurge + jobs) × connection_limit + reserve`: with 3 api pods at 10 connections
each, a migration Job at 5, and headroom, about **150**. The stock Postgres image ships 100, and
exhausting it presents as an api that starts and then cannot serve.
- **Redis.** [`08-redis-sentinel.yaml`](08-redis-sentinel.yaml) deploys one, or point `REDIS_URL` at
a managed Redis that fails over behind a single address and skip that file. Persistence is not
required: everything Jarvis keeps in Redis is ephemeral by design, which is exactly why the run
lock is in Postgres instead.
- **Redis.** [`08-redis-sentinel.yaml`](08-redis-sentinel.yaml) deploys one — three Redis, one of
them the primary, and three sentinels — and `02-config.yaml`'s two `REDIS_SENTINEL*` lines point
the api at it. Or use a managed Redis that fails over behind a single address: skip that file,
delete those two lines, and set `REDIS_URL` to it. Never `REDIS_URL` alone at the file's `redis`
Service: it is headless, two of the three pods it answers with are read-only replicas, and an api
pod that draws one fails every write. Persistence is not required: everything Jarvis keeps in
Redis is ephemeral by design, which is exactly why the run lock is in Postgres instead.
- **An Ingress controller and a TLS terminator.** Jarvis speaks plain HTTP.
## Order
@@ -67,7 +70,7 @@ kubectl apply -f 01-secret-job.yaml # the half that is random bytes (optio
kubectl wait --for=condition=complete job/jarvis-mint-secrets -n jarvis --timeout=2m
kubectl apply -f 02-config.yaml
kubectl apply -f 08-redis-sentinel.yaml # only if you are running your own Redis
kubectl apply -f 08-redis-sentinel.yaml # unless you use a managed Redis (then edit 02-config.yaml)
# The schema, to completion, before any api pod starts.
kubectl apply -f 03-migrate-job.yaml
@@ -130,10 +133,14 @@ thing being ordered:
| `-1` | `jarvis-mint-secrets` **(hook)** | the migration reads `DATABASE_URL` out of what it writes |
| `0` | `jarvis-migrate` **(hook)** | an api pod against an unmigrated schema is the failure all of this prevents |
| `1` | api and web Deployments, Services | the web pod's `/readyz` proxies the api's health endpoint, so it cannot be ready before the api is |
| `2` | Ingress, PodDisruptionBudgets | an Ingress applied early is a route to a 503 |
| `2` | Ingress, PodDisruptionBudgets, the web's HPA | an Ingress applied early is a route to a 503; the HPA, below |
Argo does not begin a wave until the previous one reports healthy, which is what the `kubectl wait`
lines are doing by hand.
lines are doing by hand. The last wave is applied and not waited on, which is why the web's
HorizontalPodAutoscaler is there rather than beside its Deployment: on a first install it has no
metric for pods seconds old, Argo reads that as Degraded, and in wave 1 it failed the first sync
every time. The application can read Degraded for the minute after a first install, until
metrics-server has scraped the new pods; the sync itself has succeeded.
### Why the two Jobs are hooks