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]>
129 lines
5.8 KiB
YAML
129 lines
5.8 KiB
YAML
---
|
|
# promtool unit tests for platform/cloudflared/alerts.yaml.
|
|
#
|
|
# Found by platform/monitoring/tests/run.sh through the COMPANION convention
|
|
# (`<manifest>.promtool-test.yaml` beside `<manifest>.yaml`). Run just this file with
|
|
# platform/monitoring/tests/run.sh platform/cloudflared/alerts.promtool-test.yaml
|
|
# The harness flattens the PrometheusRule's `.spec.groups` into alerts.rules.yaml. Assertions query
|
|
# the synthetic ALERTS series (not exp_alerts), so annotation wording is not pinned here.
|
|
#
|
|
# Each rule is pinned BOTH ways, plus the two self-silencing traps:
|
|
# * series ABSENT (nothing scraped) must FIRE, not read as healthy;
|
|
# * working-then-broken must RE-FIRE, not latch cleared.
|
|
rule_files:
|
|
- alerts.rules.yaml
|
|
evaluation_interval: 1m
|
|
|
|
tests:
|
|
- interval: 1m
|
|
name: healthy - two replicas, four edge connections each, both rules quiet
|
|
input_series:
|
|
- series: 'cloudflared_tunnel_ha_connections{namespace="cloudflared",pod="cloudflared-a"}'
|
|
values: '4x60'
|
|
- series: 'cloudflared_tunnel_ha_connections{namespace="cloudflared",pod="cloudflared-b"}'
|
|
values: '4x60'
|
|
- series: 'kube_deployment_status_replicas_available{namespace="cloudflared",deployment="cloudflared"}'
|
|
values: '2x60'
|
|
promql_expr_test:
|
|
- expr: 'count(ALERTS{alertname=~"Cloudflared.*"})'
|
|
eval_time: 50m
|
|
exp_samples: []
|
|
|
|
- interval: 1m
|
|
name: nothing scraped at all - TunnelDown still fires (no self-silencing on absence)
|
|
input_series:
|
|
# kube-state-metrics says both pods are Available, but no cloudflared series exists — the
|
|
# broken-PodMonitor / dropped-scrape case. The rule must not read this as healthy.
|
|
- series: 'kube_deployment_status_replicas_available{namespace="cloudflared",deployment="cloudflared"}'
|
|
values: '2x60'
|
|
promql_expr_test:
|
|
- expr: 'ALERTS{alertname="CloudflaredTunnelDown"}'
|
|
eval_time: 5m
|
|
exp_samples:
|
|
- labels: 'ALERTS{alertname="CloudflaredTunnelDown", alertstate="pending", severity="warning"}'
|
|
value: 1
|
|
- expr: 'ALERTS{alertname="CloudflaredTunnelDown", alertstate="firing"}'
|
|
eval_time: 12m
|
|
exp_samples:
|
|
- labels: 'ALERTS{alertname="CloudflaredTunnelDown", alertstate="firing", severity="warning"}'
|
|
value: 1
|
|
- expr: 'ALERTS{alertname="CloudflaredReplicasDegraded"}'
|
|
eval_time: 30m
|
|
exp_samples: []
|
|
|
|
- interval: 1m
|
|
name: scraped but zero edge connections - TunnelDown fires after 10m
|
|
input_series:
|
|
- series: 'cloudflared_tunnel_ha_connections{namespace="cloudflared",pod="cloudflared-a"}'
|
|
values: '0x60'
|
|
- series: 'cloudflared_tunnel_ha_connections{namespace="cloudflared",pod="cloudflared-b"}'
|
|
values: '0x60'
|
|
- series: 'kube_deployment_status_replicas_available{namespace="cloudflared",deployment="cloudflared"}'
|
|
values: '0x60'
|
|
promql_expr_test:
|
|
- expr: 'ALERTS{alertname="CloudflaredTunnelDown", alertstate="firing"}'
|
|
eval_time: 9m
|
|
exp_samples: []
|
|
- expr: 'ALERTS{alertname="CloudflaredTunnelDown", alertstate="firing"}'
|
|
eval_time: 12m
|
|
exp_samples:
|
|
- labels: 'ALERTS{alertname="CloudflaredTunnelDown", alertstate="firing", severity="warning"}'
|
|
value: 1
|
|
- expr: 'ALERTS{alertname="CloudflaredReplicasDegraded", alertstate="firing"}'
|
|
eval_time: 17m
|
|
exp_samples:
|
|
- labels: 'ALERTS{alertname="CloudflaredReplicasDegraded", alertstate="firing", severity="warning"}'
|
|
value: 1
|
|
|
|
- interval: 1m
|
|
name: working then the series vanish - TunnelDown re-fires instead of latching cleared
|
|
input_series:
|
|
# 20 healthy samples, then the series stop (stale) — the scrape broke or the pods died.
|
|
- series: 'cloudflared_tunnel_ha_connections{namespace="cloudflared",pod="cloudflared-a"}'
|
|
values: '4x20 stale'
|
|
- series: 'cloudflared_tunnel_ha_connections{namespace="cloudflared",pod="cloudflared-b"}'
|
|
values: '4x20 stale'
|
|
- series: 'kube_deployment_status_replicas_available{namespace="cloudflared",deployment="cloudflared"}'
|
|
values: '2x60'
|
|
promql_expr_test:
|
|
- expr: 'ALERTS{alertname="CloudflaredTunnelDown"}'
|
|
eval_time: 15m
|
|
exp_samples: []
|
|
- expr: 'ALERTS{alertname="CloudflaredTunnelDown", alertstate="firing"}'
|
|
eval_time: 35m
|
|
exp_samples:
|
|
- labels: 'ALERTS{alertname="CloudflaredTunnelDown", alertstate="firing", severity="warning"}'
|
|
value: 1
|
|
|
|
- interval: 1m
|
|
name: one replica lost - ReplicasDegraded fires after 15m, tunnel itself still up
|
|
input_series:
|
|
- series: 'cloudflared_tunnel_ha_connections{namespace="cloudflared",pod="cloudflared-a"}'
|
|
values: '4x60'
|
|
- series: 'kube_deployment_status_replicas_available{namespace="cloudflared",deployment="cloudflared"}'
|
|
values: '1x60'
|
|
promql_expr_test:
|
|
- expr: 'ALERTS{alertname="CloudflaredReplicasDegraded", alertstate="firing"}'
|
|
eval_time: 14m
|
|
exp_samples: []
|
|
- expr: 'ALERTS{alertname="CloudflaredReplicasDegraded", alertstate="firing"}'
|
|
eval_time: 17m
|
|
exp_samples:
|
|
- labels: 'ALERTS{alertname="CloudflaredReplicasDegraded", alertstate="firing", severity="warning"}'
|
|
value: 1
|
|
- expr: 'ALERTS{alertname="CloudflaredTunnelDown"}'
|
|
eval_time: 30m
|
|
exp_samples: []
|
|
|
|
- interval: 1m
|
|
name: kube-state-metrics series absent - ReplicasDegraded fires rather than going quiet
|
|
input_series:
|
|
- series: 'cloudflared_tunnel_ha_connections{namespace="cloudflared",pod="cloudflared-a"}'
|
|
values: '4x60'
|
|
promql_expr_test:
|
|
- expr: 'ALERTS{alertname="CloudflaredReplicasDegraded", alertstate="firing"}'
|
|
eval_time: 17m
|
|
exp_samples:
|
|
- labels: 'ALERTS{alertname="CloudflaredReplicasDegraded", alertstate="firing", severity="warning"}'
|
|
value: 1
|