Skip to content

fix: Make the gateway certificate alerts able to fire - #437

Draft
ecv wants to merge 5 commits into
mainfrom
fix/controller-metrics-registry
Draft

fix: Make the gateway certificate alerts able to fire#437
ecv wants to merge 5 commits into
mainfrom
fix/controller-metrics-registry

Conversation

@ecv

@ecv ecv commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Summary

Alerts that tell on-call when a customer's HTTPS listener has gone dark, or its certificate is about to expire, could never fire. The certificate health measurements they depend on were recorded somewhere the alerting system never reads, so the alerts had nothing to evaluate.

A related set of measurements had the opposite problem: it reported a flat zero from every edge location instead of a real value from the one place that actually measures it. Both are fixed, and the underlying measurements now separate customers who happen to reuse the same gateway name, so one customer's certificate status can no longer overwrite another's.

Test plan

  • Unit tests pass locally
  • Verified the new registration check fails against a deliberately broken metric, then reverted the probe
  • After release, confirm the two certificate alerts evaluate in production
  • After release, confirm the previously-zeroed measurements report real values from the control plane

Fixes #359
Related to https://github.com/datum-cloud/infra/issues/4005

https://claude.ai/code/session_011XoSfWG6XKn8ckkRAAGiCP

ecv added 5 commits September 9, 2026 11:14
Every metric in internal/controller registered against
prometheus.DefaultRegisterer, which the manager never serves. The manager
exposes controller-runtime's own registry at /metrics, so none of these
families has ever reached Prometheus.

The cost is not only absence. Two alerts shipped in this repo's own
telemetry bundle, GatewayListenerCertUnusable and
GatewayListenerCertExpiringSoon, select on nso_gateway_listener_cert_*
and are deployed to production today, where they can never fire. A
customer's HTTPS listener can be withheld for an unusable certificate
with no signal at all.

Worse, three plain gauges do reach production, and they lie. The
extension server serves DefaultRegisterer itself, and it links this
package, so nso_location_source_total, nso_location_published_total and
nso_location_retained_total are exported from every edge cell at a
constant 0. The location publisher runs in the manager, not there, so
the values are placeholders that read as a real measurement. After this
change they are exported once, by the process that sets them.

Key changes:
- Register every internal/controller metric with
  promauto.With(ctrlmetrics.Registry) so the manager's /metrics serves
  them
- Correct the package comment, which asserted the opposite and is
  presumably why this went unnoticed
- Add TestControllerMetricsAreServedByTheManager, which gathers from the
  registry the manager actually serves and fails on the pre-fix
  registration

internal/extensionserver keeps its own metrics on DefaultRegisterer,
which it registers and serves itself.

Fixes #359

Claude-Session: https://claude.ai/code/session_011XoSfWG6XKn8ckkRAAGiCP
GatewayReconciler is multicluster: upstream gateways live in
per-project control planes, and 129 of 131 production gateways sit in
namespace "default". Labelling the five gateway certificate and
programmed metrics by namespace+name alone collapses two projects that
happen to share a gateway name onto one series, and
clearListenerCertMetrics then deletes the other project's series on
every reconcile via DeletePartialMatch. The alerts built on these
series could silently stop firing for the collided project.

Thread the upstream cluster name through as a project label on
gatewayProgrammedTotal, gatewayListenerCertWithheld,
gatewayListenerCertGatingTotal, gatewayListenerCertExpiryTime and
gatewayListenerCertManaged, matching the existing metricLabelProject
convention used by missingAllocationsTotal. Surface the label in the
GatewayListenerCertUnusable and GatewayListenerCertExpiringSoon alert
annotations so on-call can see which project a firing alert belongs
to.

Claude-Session: https://claude.ai/code/session_011XoSfWG6XKn8ckkRAAGiCP
The registry-coverage test named every metric by hand, so a metric
added later with a bare promauto.New* call (registering against
prometheus.DefaultRegisterer instead of ctrlmetrics.Registry) stayed
invisible to it. Add a second test that gathers
prometheus.DefaultGatherer directly and fails on any "nso_" family
that isn't "nso_extension_", catching that mistake regardless of
which metric it is.

Also drop the three location metrics from the hand-maintained name
list: draft PR #413 deletes them, and naming them here would make that
PR fail to build against this branch.

Claude-Session: https://claude.ai/code/session_011XoSfWG6XKn8ckkRAAGiCP
The package comment said prometheus.DefaultRegisterer is also the
registry controller-runtime exposes via
sigs.k8s.io/controller-runtime/pkg/metrics. It is not: the manager
serves ctrlmetrics.Registry, and the extension server is a separate
binary that serves DefaultRegisterer itself via promhttp.Handler(). A
metric registered here was never invisible because of that false
claim, but the claim is exactly what this branch's controller-side fix
exists to correct, so leaving it uncorrected here would contradict it.

Claude-Session: https://claude.ai/code/session_011XoSfWG6XKn8ckkRAAGiCP
locationSourceTotal, locationPublishedTotal and locationRetainedTotal
are plain gauges set only by the leader-elected location publisher,
while production runs 3 replicas of the manager. A non-leader exports
each of them at a constant 0 from the registry the manager serves, so
sum() or a bare selector against these metrics undercounts by however
many non-leader replicas are running. Say so in the Help text so the
gauge's query semantics travel with it into Grafana and any new
PromQL, without restructuring the publisher.

Claude-Session: https://claude.ai/code/session_011XoSfWG6XKn8ckkRAAGiCP
@ecv ecv changed the title fix: serve controller metrics from the manager registry fix: Make the gateway certificate alerts able to fire Sep 9, 2026
@ecv

ecv commented Sep 9, 2026

Copy link
Copy Markdown
Contributor Author

Parking this. It is finished and reviewed, but it adds alerts, and the current goal is a channel at zero before any new alert is created.

Releasing it turns two certificate alerts from never-firing to roughly ten to twenty firing at once, all long-standing, almost all demo, lab or test projects. Nothing decays while it waits: the bug is a registration line, the fix does not rot, and no enterprise workload is dark today.

Unparking needs two things first. The exclusion for gateways already dark, https://github.com/datum-cloud/infra/issues/4997, so the release does not arrive as a page wave. And staging back on main, https://github.com/datum-cloud/infra/issues/4998, so there is somewhere to soak it. Both are recorded as blockers on #359.

Marking as draft so it is not merged by accident.

@ecv
ecv marked this pull request as draft September 9, 2026 15:43
@ecv

ecv commented Sep 9, 2026

Copy link
Copy Markdown
Contributor Author

Correcting the severity I gave earlier in this thread. Both certificate alerts carry warning severity, and the Alertmanager configuration has no alertname matcher for either, so they route to the warning channel rather than to on-call.

The rollout still puts ten to twenty entries in that channel at once, which is what matters against a zero-alert goal, but it is channel noise and not a page wave. The issue body is corrected.

@ecv

ecv commented Sep 9, 2026

Copy link
Copy Markdown
Contributor Author

Unpark conditions, updated. The alert-side guard is no longer one of them.

An attempt to gate the certificate alert on recent breakage is closed, https://github.com/datum-cloud/infra/pull/5003, because no standing gate can hold back listeners that were already dark without also muting listeners that are dark from creation. The operator records the managed and withheld gauges in the same loop iteration, so a born-broken listener never accumulates a healthy sample, and born-broken is the main shape this alert reports.

So the wave gets a one-time answer rather than a rule change. Before releasing this:

  1. Take stock of the listeners that are dark at that moment. Ten gateways carry one today, nine of them in demo, lab, test or onboarding projects.
  2. Apply a time-bounded Alertmanager silence covering them, sized to the window in which they will be fixed or cleared.
  3. Clear or fix them inside that window rather than extending the silence.
  4. Restore staging to main first, https://github.com/datum-cloud/infra/issues/4998, so the release soaks somewhere before production and the sixteen edge cells take it.

Two review findings on this branch also bear on the release. The metrics carry a project label as of the fixes here, so the silence should be scoped by project rather than by gateway name, which collides across projects. And the location gauges will move from sixteen edge cells to the control plane replicas, where the non-leaders report zero, so read them with max().

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: controller metrics register on a registry the manager never serves

1 participant