External Health Signals
License: all tiers.
External health signals let an outside monitor — CloudWatch, Datadog, or any webhook — post a health verdict for a pool member, which Nexus blends with its own health-check probe before deciding whether to answer DNS with that member. This closes the gap where a backend passes Nexus's TCP/HTTP probe but a system Nexus cannot see (a degraded upstream database, a saturated queue, an application-level SLO breach) knows it is unwell.
Signals are ephemeral: each carries a TTL and simply expires. There is no config to persist and no rqlite table — an external verdict with a TTL is the same class of ephemeral health input the post-ARCH contract routes over NATS, alongside heartbeats, RTTs, and RUM.
How It Works
CloudWatch / Datadog / your webhook
│ POST /api/v1/members/{id}/health-signal (optionally ?format=<vendor>)
▼
Nexus API ── store.Put(ip, source, healthy, ttl) ──▶ SignalStore (node A)
│ │
│ (clustered) NATS KV mirror
│ ▼
│ SignalStore on nodes B, C
▼
Manager.IsHealthy(member) = blend(probe result, external verdict, mode)
▼
DNS resolver drops / keeps the member accordingly; self-heals when the TTL lapses
The verdict is consulted at the single Manager.IsHealthy choke point, so every resolution path — normal DNS, the query tracer, filter chains — sees it.
Blend Modes
Each member has an externalSignalMode (WebUI member panel, gslbctl members add/update --signal-mode, Terraform external_signal_mode on nexus_member). It controls how the aggregated external verdict combines with the probe result:
| Mode | Member is healthy when… | Use when |
|---|---|---|
and (default) |
the probe and every external source agree it is healthy | safest — either signal can take a member out |
or |
the probe or an external source reports healthy | external monitor can keep a member in despite a probe blip |
override |
the external verdict wins outright (probe ignored) | the external system is the source of truth |
Multiple sources per member aggregate conservatively: any unexpired source reporting unhealthy makes the member externally unhealthy, so any monitor flagging degradation counts.
Ingesting a Verdict
POST /api/v1/members/{id}/health-signal (permission members:write). Native body:
{ "source": "datadog", "healthy": false, "score": 0.2, "ttl": 120 }source(required) — a stable label; verdicts from the same source overwrite.healthy(required) — the verdict.score— optional 0–1 health score (informational).ttl— seconds the verdict stays live (default 300, max 3600). Post again before it lapses to keep it, or posthealthy:true/ let it expire to clear it.
Vendor adapters
Most monitors will not emit the native shape, so ?format=<vendor> translates a vendor payload in-endpoint (no separate adapter service):
format |
Payload | Healthy when | Source label |
|---|---|---|---|
cloudwatch |
{"AlarmName","NewStateValue"} |
NewStateValue == "OK" |
cloudwatch:<AlarmName> |
datadog |
{"alert_transition","alert_type","title"} |
Recovered / alert_type == "success" |
datadog:<title> |
generic |
{"healthy","score?","source?","ttl?"} |
healthy field |
source or webhook |
CloudWatch: deliver the SNS message with raw message delivery (or unwrap it upstream) so the endpoint sees the alarm JSON directly. Datadog: configure the webhook body as {"alert_transition":"$ALERT_TRANSITION","alert_type":"$ALERT_TYPE","title":"$EVENT_TITLE"}.
Reading Live Verdicts
GET /api/v1/members/{id}/health-signals (permission members:read) returns the member's unexpired signals with their source, verdict, score, and expiry. The WebUI member panel shows the same list live, and the topology member node carries an ext:<mode> pill when a non-default blend mode governs the member.
Cluster Propagation
A verdict is posted to a single node's API. When NATS state sync is configured, that node mirrors the signal into the gslb_<cluster>_health_signals KV bucket (2h TTL); every node watches the bucket and applies peer verdicts into its own store, so all nodes route around the member consistently. Peer-applied verdicts are not re-published (no loop). Without NATS, signals stay node-local — the receiving node blends them and still works, but other nodes are unaffected until they receive their own verdict.
The KV bucket-create failure degrades to node-local; it never blocks startup.
Notes
- Verdicts survive a
gslbdrestart only as long as the poster keeps refreshing them within the TTL — there is no durable state by design. - The per-member mode is read from a 30s-cached lookup, so a mode change can take up to 30s to affect blending.
- OIDC has no bearing here; ingestion is gated purely by
members:write.