VeleroNoRecentBackup could not fire for the failure mode that actually happened.
`(time() - velero_backup_last_successful_timestamp{schedule!=""}) > 24*3600` is the
right SHAPE — per-schedule, un-aggregated — but Velero only creates that series once
a backup for the schedule SUCCEEDS, and `time() - <absent series>` yields no series.
So it detects "backed up once, then stopped" and is structurally INERT on "never
backed up". Prod ran with `velero-daily` (the schedule carrying VOLUME DATA) paused
and never-successful: no volume backups at all, nothing alerted, and it was masked
because `velero-daily-resources-only` completed cleanly every 03:00, so every other
backup signal read green while only Kubernetes objects were protected.
A PromQL-only fix is impossible, verified rather than assumed: velero v1.18.1 exports
35 velero_* metric names and every one carrying a `schedule` label is created by a
backup ATTEMPT, not by the Schedule object. A throwaway `paused: true` Schedule on
prod produced ZERO velero_* series, and nothing exposes `spec.paused`. The fact is
not in the TSDB, so the Schedule CRs are exported via kube-state-metrics'
custom-resource-state collector — the path this repo already uses for
kube_node_labels — as kube_velero_schedule_{created,paused,protects_volume_data,info}.
Proven by running KSM v2.20.0 with this exact config against prod's API: the CRD
list/watch rule the chart adds is MANDATORY, not cosmetic — without it KSM emits the
CRS metrics as an empty set, silently.
Three rules, all keyed on PROPERTIES rather than schedule names:
* VeleroScheduleNeverBackedUp (critical) — a Schedule older than 36h with no
successful backup ever. The 36h grace comes from creationTimestamp so a newly
added schedule is not paged before its first run is even due.
* VeleroSchedulePaused (warning) — the direct root-cause signal; a paused backup
schedule must never be silently indefinite.
* VeleroNoVolumeDataBackup (critical) — THE ANTI-MASKING RULE. Fires unless some
schedule with spec.template.defaultVolumesToFsBackup=true succeeded in 36h, so a
healthy resources-only schedule can never make the platform look protected.
Reading the property that DEFINES volume protection (not a name list) matches
platform/velero/volume-policies.yaml and survives renames. `or vector(0)` is
load-bearing: `count(<empty>) == 0` returns no series and would be inert —
absence of the exporter is deliberately the alarm.
Adds the repo's first Prometheus rule-unit-test harness (platform/monitoring/tests/,
wired as the `prom-rules` gate stage). It flattens the COMMITTED PrometheusRule CRs —
never a copy — and pins every rule in the group both ways: quiet on today's healthy
prod state, firing on the reconstructed bad state, with `for:` durations asserted.
Two mutation checks confirm the tests are not vacuous: dropping `or vector(0)` and
breaking the `unless on (schedule)` join each turn the suite red. This is the tool
that makes both of this repo's recurring alert bugs — permanently-firing and
structurally inert — provable offline, which neither yamllint, Kyverno, nor a healthy
cluster can do.
Proof of the fix is by construction; the live metrics cannot show it any more, because
`velero-daily` was unpaused on 2026-09-12 and has since completed 2492/2492 items.
The throwaway probe Schedule and the probe KSM deployment were deleted and the
deletion confirmed; `velero-daily` remains unpaused.
Co-Authored-By: Claude Opus 5 <[email protected]>
45 lines
1.5 KiB
Bash
Executable File
45 lines
1.5 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# ci/prom-rules.sh — Stage: Prometheus RULE-UNIT tests (no cluster; live now).
|
|
#
|
|
# Thin wrapper over platform/monitoring/tests/run.sh, which runs `promtool test rules` against the
|
|
# COMMITTED PrometheusRule manifests. It is a gate stage because the two alerting defects this repo
|
|
# has actually shipped — a permanently-firing rule and a structurally INERT one — are both invisible
|
|
# to every other stage: yamllint parses them, Kyverno admits them, and a healthy cluster cannot tell
|
|
# a correct alert from one that can never fire. Only the bad state, constructed on purpose, can.
|
|
#
|
|
# Exit codes per ci/lib.sh: 0 pass / 1 fail / 3 skipped (never green).
|
|
set -euo pipefail
|
|
CI_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
# shellcheck disable=SC1091
|
|
source "${CI_DIR}/lib.sh"
|
|
|
|
log "Stage: Prometheus rule-unit tests (promtool test rules; no cluster)"
|
|
|
|
RUNNER="${REPO_ROOT}/platform/monitoring/tests/run.sh"
|
|
if [ ! -x "$RUNNER" ]; then
|
|
skip "rule-unit harness not present at ${RUNNER} — UNWIRED, not green"
|
|
summary "- prom-rules: **SKIPPED** (harness missing)"
|
|
exit 3
|
|
fi
|
|
|
|
set +e
|
|
"$RUNNER"
|
|
rc=$?
|
|
set -e
|
|
|
|
case "$rc" in
|
|
0)
|
|
ok "Prometheus rule-unit tests passed"
|
|
summary "- prom-rules: **passed**"
|
|
;;
|
|
3)
|
|
skip "promtool unavailable (no promtool on PATH, no podman/docker) — NOT green"
|
|
summary "- prom-rules: **SKIPPED** (promtool toolchain unavailable)"
|
|
;;
|
|
*)
|
|
err "Prometheus rule-unit tests FAILED"
|
|
summary "- prom-rules: **FAILED**"
|
|
;;
|
|
esac
|
|
exit "$rc"
|