Files
home-network-k3s-impl/platform/cloudflared/alerts.yaml
T
jakman85andClaude Opus 5.5 aabc8c60b6 feat(cloudflared): Cloudflare Tunnel connector, Phase 1 (isolated, no traffic change)
Adds platform/cloudflared: a locally-managed tunnel whose ingress rules live in
git. LAN/VPN-only hosts (plex, plex-vip, jackett, pihole, pihole-dns, semaphore,
qbittorrent, radarr, sonarr, headlamp, unifi, crafty-mc) map to 404. The apex
and *.home go to cilium-gateway-shared.gateway over TLS with matchSNItoHost.
Last comes a catch-all 404. Nothing routes to the Kubernetes API.

- 2-replica Deployment, hard node spread, non-root, pinned digest, PDB
- default-deny plus an egress CNP: Cloudflare edge 7844/443, the Gateway, and
  Gateway backends by gateway-access=shared minus the LAN-only namespaces
  (needed because of the CVE-2025-30162 L7LB egress re-check)
- PodMonitor plus two alerts that fire on absent series, with promtool tests
- its own Flux Kustomization (dependsOn platform-config, wait:false, nothing
  depends on it), plus SOPS decryption patches in both overlays
- placeholder credentials (SOPS-encrypted) so it can merge before enrolment
- L0 cloudflared-ingress-rules, and a cloudflared_ scrape owner
- runbooks/cloudflare-tunnel.md and decisions/2026-11-08-cloudflare-tunnel.md

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-08 12:38:22 -06:00

65 lines
3.5 KiB
YAML

---
# Alert rules for the Cloudflare Tunnel connector.
#
# THE DESIGN CONSTRAINT: NEITHER RULE MAY GO QUIET WHEN ITS OWN DATA DISAPPEARS. The repo has
# shipped self-silencing rules before (a bare `up{}` that clears exactly when scraping breaks), so:
#
# * CloudflaredTunnelDown reads `cloudflared_tunnel_ha_connections` — the per-replica gauge of
# live edge connections — and falls back to `vector(0)` when NO such series exists. A dead
# tunnel, zero pods, a broken PodMonitor, or a NetworkPolicy that drops the scrape all make the
# series absent, and all of them FIRE rather than read as healthy.
# * CloudflaredReplicasDegraded reads kube-state-metrics, which does not depend on cloudflared
# being scrapable at all, and has the same `vector(0)` fallback.
#
# Both directions are pinned by the companion alerts.promtool-test.yaml (healthy is quiet; absent
# series fires; working-then-broken re-fires).
#
# NOTE FOR PHASE 1: until the operator completes runbooks/cloudflare-tunnel.md steps 1-5 the
# Deployment cannot connect and CloudflaredTunnelDown fires. That is the honest state — the tunnel
# is not serving — and nothing routes through it yet, which is why both rules are `warning`.
# Raise TunnelDown to `critical` in the change that points public DNS at the tunnel.
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: cloudflared
namespace: cloudflared
labels:
app.kubernetes.io/name: cloudflared
spec:
groups:
- name: platform-health.cloudflare-tunnel
rules:
- alert: CloudflaredTunnelDown
# Sum of live edge connections across every replica. `or on() vector(0)` turns an EMPTY
# result (nothing scraped) into 0 so it fires instead of evaluating to no series.
expr: (sum(cloudflared_tunnel_ha_connections{namespace="cloudflared"}) or on() vector(0)) < 1
# 10m rides out a rolling update and a Cloudflare edge reconnect, not a real outage.
for: 10m
labels:
severity: warning
annotations:
summary: Cloudflare Tunnel has no healthy edge connections
description: >-
No cloudflared replica reports a live connection to the Cloudflare edge (or none is
being scraped at all). Every hostname routed through the tunnel is unreachable from
the internet. Before the operator has enrolled the tunnel this is expected.
runbook_url: "https://${BASE_DOMAIN}/runbooks/cloudflare-tunnel"
- alert: CloudflaredReplicasDegraded
# Fewer than the 2 replicas deployment.yaml asks for are Available — the tunnel has lost
# its HA, and the next node drain or reboot takes it down. Keep `2` in step with
# spec.replicas. Absent series (kube-state-metrics gone, Deployment deleted) -> 0 -> fires.
expr: >-
(max(kube_deployment_status_replicas_available{namespace="cloudflared",deployment="cloudflared"})
or on() vector(0)) < 2
for: 15m
labels:
severity: warning
annotations:
summary: cloudflared has fewer than 2 available replicas
description: >-
The cloudflared Deployment has fewer than two Available replicas, so the tunnel has
no redundancy (or is down entirely). Check for unenrolled credentials, a node that
cannot schedule the hard-spread pod, or an image pull failure.
runbook_url: "https://${BASE_DOMAIN}/runbooks/cloudflare-tunnel"