Operating an edge
Everything below lives on 127.0.0.1:8081 — the admin
port, reachable only from inside the box the edge runs on, never
mapped out. This demo's own zone was pushed the same way every
other zone on this fleet is: files copied into /config
on each of the four edges, then either /reload or a
bumped config.version the watcher picks up within
about five seconds.
The admin API
| endpoint | token | answers |
|---|---|---|
GET /health | no | liveness only — up, and holding a config |
GET /metrics | no | Prometheus text, edge-wide, no tenant in any series |
POST /reload | yes | re-read /config now |
POST/PATCH /plane | yes | publish or overlay the plane in the request body — the panel's own transport |
GET /status | yes | this edge as it sees itself: version, origins, jobs, caches, quarantined zones |
GET /usage | yes | per zone, per UTC hour, three hours retained |
POST /purge?host=&uri= | yes | drop one URL from this edge's disk cache |
GET /zone?host= | yes | the compiled view this edge holds for a hostname |
/health and /metrics need no token
deliberately: a liveness probe that fails on one tenant's dead
origin would pull a healthy edge out of rotation, and metrics carry
no tenant identity to protect. Everything else names tenants and
sits behind Authorization: Bearer $ADMIN_TOKEN — an
endpoint that isn't declared answers 401 without the
token and 404 with it.
/status was open once, and it listed every tenant's
origin address — including, from inside the container, this
server's own 127.0.0.1:8081. The compiler now also
refuses loopback and link-local origin addresses outright, so the
door is bolted from both sides.
Four monitoring surfaces, not interchangeable
| surface | auth | scope | answers |
|---|---|---|---|
/metrics | none | edge-wide aggregates only | is the fleet healthy → alerts |
/status | token | as seen from HERE | what is wrong with THIS edge → diagnosis |
/usage | token | per zone, per UTC hour | what did each tenant do → billing |
| access log | — | per request | what happened to that one request → forensics |
There is deliberately no per-zone label on any
/metrics series: ten thousand zones times a
dozen series is 120,000 series per edge before the fleet
multiplier, on an endpoint that is intentionally unauthenticated —
that would be a scraper outage and a tenant-disclosure bug in one
change. /usage is the per-zone interface, and it is
sparse: absence means zero, not "ask again louder."
What counting actually costs
The log phase runs on every request, so a request only bumps plain Lua tables in its own worker; a one-second timer folds the deltas into shared memory. Measured A/B on one 16-core box, cached-HIT path: 137.7k req/s with counting compiled out, 132.5k with it on — about 4%, p50 unchanged at ~1.2 ms. Numbers a scrape sees are at most one second behind.
A handful of the sixteen alerts worth building
| # | alert | expression |
|---|---|---|
| 1 | edge down | up{job="cdn-edge"} == 0 |
| 8 | one edge blind | max by(job)(origins_down) > 0 and min by(job)(origins_down) == 0 |
| 12 | capacity headroom | share of requests under le="0.0005" falling |
| 14 | traffic collapse | rate(cdn_requests_total[10m]) < 40% of an hour ago |
#8 exists because one edge that cannot reach an origin the others
can is invisible in any fleet-wide number — the identity lives at
/status origin_probes, keyed by (address,
probe), per edge. This demo's own edges are checked the same way in
the measurements page: per-edge, with --resolve, not
averaged together.
What a fleet must share
Three things must match across every edge, or a visitor moved
between them by DNS pays for it: the config/ plane
itself, ticket.key (the TLS session ticket key — one
file, copied to every box, or a resumed session on a different edge
silently becomes a full handshake), and the image tag. Each edge
keeps its own disk cache; that is fine and expected — a cold cache
on one edge is not a fleet-wide event.