Monitoring¶
The stack is conventional: Prometheus pulls metrics, Loki/Vector handle logs, Alertmanager fans out alerts, Grafana glues the views together. The only custom piece is the Discord proxy.
Components¶
| Component | Where | Notes |
|---|---|---|
kube-prometheus-stack |
monitoring ns |
Prometheus, Alertmanager (HA, 2 replicas), Grafana, kube-state-metrics, prometheus-operator |
| Loki | monitoring/loki-0 |
Single-binary mode, sufficient for one user |
| Vector | DaemonSet + aggregator StatefulSet | Agents on every node, ship to Loki |
| Discord proxy | monitoring/discord-alert-proxy |
Tiny HTTP service, takes Alertmanager and Falco webhooks, posts to channels |
| Falco | security/falco |
Runtime threat detection on the cluster, alerts via Discord |
External LB IPs let me reach things from outside the cluster without port-forwarding:
| Service | LB IP |
|---|---|
| Grafana | 192.168.1.229 |
| Prometheus | 192.168.1.230 |
| Loki | 192.168.1.231 |
| Vector aggregator | 192.168.1.232 |
| Alertmanager | 192.168.1.233 |
Alert routing¶
Alerts go through Alertmanager into the Discord proxy. The proxy splits them by route:
- Infrastructure (NodeDown, KubePodCrashLooping, MemoryPressure) into
#ops. - Media stack failures into
#media. - Falco runtime events into
#security. - Trading and science workloads into their own channels.
The proxy also filters routine churn. Restarts and scrape blips that resolve in under 60 seconds get suppressed so the Discord channels stay scannable.
What this stack covers and what it does not¶
What it does well:
- Pull-based metrics from anything that exposes
/metrics, including the apps I write. - Cluster health (node, pod, kubelet, scheduler, etcd, apiserver).
- Logs centralized off the nodes.
- Alerts that I will see, because Discord is where I already am.
The layer above it, added 2026-09-19¶
The stack above is layer 1, and it cannot report its own death. It runs inside the Kubernetes cluster, stores its data on NFS PVCs on the NAS, and reaches Discord through a proxy that also runs in that cluster. On 2026-09-19 an NFS deadlock took Prometheus and Loki down and nothing alerted for eleven hours.
There are now three layers, by blast radius:
| Layer | What | Survives |
|---|---|---|
| 1 | Prometheus, Alertmanager, Grafana, Loki | Rich rule-based alerting, as long as the cluster and NAS are healthy |
| 2 | Uptime Kuma on vm117, outside the cluster, disk not on NFS, alerting Discord directly | The cluster or the NAS going down |
| 3 | Healthchecks.io watching Alertmanager's Watchdog (ADR 0005) |
The entire lab going away |
flowchart TB
subgraph LAB["Lab failure domain<br/>LAN 192.168.1.0/24, direct 10G storage link 10.10.12.0/24"]
subgraph CLUSTER["Layer 1, inside the Kubernetes cluster<br/>Monitoring shares the cluster and NAS storage with monitored workloads"]
subgraph STACK["Monitoring stack, all four services use NFS PVCs"]
PROM["Prometheus<br/>192.168.1.230"]
AM["Alertmanager<br/>192.168.1.233"]
GRAF["Grafana<br/>192.168.1.229"]
LOKI["Loki<br/>192.168.1.231"]
end
PROXY["discord-alert-proxy<br/>Runs in this same cluster"]
API["Three kube-apiservers<br/>k8cluster1, 192.168.1.90<br/>k8cluster2, 192.168.1.89<br/>k8cluster3, 192.168.1.91"]
WORK["NFS-dependent workloads<br/>Share storage with monitoring"]
BLIND["Failure mode<br/>Cluster or shared storage failure<br/>can silence its own alerting"]
end
NAS["TrueNAS<br/>192.168.1.15<br/>Shared NFS storage"]
subgraph L2["Layer 2, outside Kubernetes, inside the lab<br/>Shares the LAN and NAS probe target, disk is not on NFS"]
KUMA["Uptime Kuma<br/>VM 117, 192.168.1.129<br/>Disk deliberately not on NFS"]
end
end
subgraph OFFSITE["Outside the lab failure domain"]
subgraph L3["Layer 3, off site, no shared lab infrastructure<br/>Only monitoring layer that survives the whole lab being dead"]
HC["Healthchecks.io<br/>Detects missing Watchdog pings"]
EMAIL["Email notification<br/>Still works when the lab is dead"]
end
DISCORD["Discord"]
end
PROM -->|"Alerts"| AM
AM -->|"Routes Discord alerts"| PROXY
PROXY -->|"Delivers cluster alerts"| DISCORD
STACK -->|"NFS PVC storage for all four services"| NAS
WORK -->|"NFS storage dependency"| NAS
STACK -.->|"Shares the failure"| BLIND
KUMA -->|"Probes /-/ready"| PROM
KUMA -->|"Probes /-/ready"| AM
KUMA -->|"Probes /api/health"| GRAF
KUMA -->|"Probes /ready"| LOKI
KUMA -->|"Probes /readyz on all three"| API
KUMA -->|"Real NFS sentinel file read every 60 seconds"| NAS
KUMA -->|"Alerts directly, bypasses discord-alert-proxy"| DISCORD
AM -->|"Built-in Watchdog alert pings every cycle"| HC
HC -->|"Pings stop"| EMAIL
classDef neutral fill:#f8fafc,stroke:#475569,color:#0f172a;
classDef degraded fill:#fff7ed,stroke:#9a3412,color:#7c2d12;
classDef healthy fill:#f0fdf4,stroke:#166534,color:#14532d;
class PROM,AM,GRAF,LOKI,PROXY,API,WORK,NAS,DISCORD neutral;
class BLIND degraded;
class KUMA,HC,EMAIL healthy;
style LAB fill:none,stroke:#475569,stroke-width:2px
style CLUSTER fill:none,stroke:#9a3412,stroke-width:2px
style STACK fill:none,stroke:#64748b
style L2 fill:none,stroke:#475569
style OFFSITE fill:none,stroke:#475569,stroke-dasharray:6 4
style L3 fill:none,stroke:#166534,stroke-width:2px
Layer 2 is ADR 0007.
What is still not covered¶
- Off-site vantage. Kuma is on the LAN, on the same Proxmox cluster. A total power or ISP loss takes layer 1 and layer 2 together, leaving only layer 3. That is a deliberate tradeoff, argued in ADR 0007, not an oversight.
- Heartbeats for scheduled work. "Did this cron job actually run." Partly addressed: Kuma push monitors work this way and the NFS probe is one. Backups and scheduled runs are not wired up yet.
- Public status page. Discord channels are not a status page.
- Third-party uptime. GHCR, the upstream DNS provider, etc.
- A stuck mount on one specific client. The NFS probe reads from a fourth client over v3, so it catches the NAS failing for everyone, not one node's wedged session. That gap is what made 2026-09-19 hard to see.
Observability lessons captured here¶
- Alertmanager grouping matters. A flapping condition on three nodes should be one Discord message, not three.
group_by,group_wait,group_interval. - Prometheus is great at "things that have metrics," not "things that should have run." Heartbeats need a different tool.
- "Pod is Ready" is a load-bearing signal. The MetalLB / pg_hba / Grafana cascade in this incident was kicked off by a single readiness probe failing, which then had blast radius all the way to the LoadBalancer IP not answering ARP.