observability/

directory
v0.20.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 25, 2026 License: Apache-2.0

README

Observability

The local Prometheus and Grafana stack for the dashboards and alert rules the framework ships, and the notes for their maintainers. The templates themselves live in internal/observability/templates/ and are embedded in the ocf CLI; render them for your operator with ocf observability render --metric-namespace <namespace>, where the namespace is the argument you gave ocm.NewOperatorConditionsGauge. Full documentation: docs/observability.md.

From a clone, make dashboards METRIC_NAMESPACE=myoperator or make alerts METRIC_NAMESPACE=myoperator runs the same command through go run ./cmd/ocf into generated/. generated/alerts/ contains the per-operator condition rules, named after the metric namespace, plus the shared ocf-* rules for controller-runtime and the managed resource counters, which are installed once per cluster. Add NAMESPACE_LABEL=namespace if your scrape keeps the exported namespace label, and ALERT_FORMAT=rules for plain rule files instead of PrometheusRule objects. PROMETHEUSRULE_NAMESPACE and PROMETHEUSRULE_LABELS set the metadata of the PrometheusRule objects; for kube-prometheus-stack pass PROMETHEUSRULE_LABELS=release=<name>.

The rest of this file is for maintainers of the templates.

Local stack

dev/ holds a docker compose file for Prometheus and Grafana and a Go simulator that plays a scripted operator. The simulator records the framework's own series through the real metrics.Recorder; the controller-runtime, workqueue, REST client and leader election series are lookalikes with the same names, labels and buckets, guarded by the parity test below. You need docker with the compose plugin and Go.

make observability-up

The target renders both dashboards and plain rule files for the metric namespace demo (OBS_DEV_NAMESPACE) into generated/dev/, rewrites every for: in the rendered rules to 2m so the alerts fire within minutes (this rewrite exists only in the dev render), starts the containers, waits for Prometheus to become ready, and runs the simulator in the foreground with go run. Prometheus listens on 127.0.0.1:9090 and Grafana on 127.0.0.1:3000 with anonymous admin access and both dashboards provisioned into the OCF folder. Prometheus scrapes the simulator every five seconds with the static target labels job="demo-operator", namespace="operators" and pod="demo-operator-0", so the namespace the condition gauge exports collides into exported_namespace exactly as it does in a cluster. Stop the simulator with Ctrl-C, then make observability-down removes the containers.

SIMULATOR_ARGS passes extra flags to the simulator; make observability-up SIMULATOR_ARGS="-leader=false" reports the replica as a standby so OperatorLeaderMissing fires.

The world has two controllers, webapp (owner kind WebApp, components server and ingress) and database (owner kind Database, components storage and backup). Each scenario targets one alert or one dashboard feature:

Scenario Exercises
Fifty healthy WebApp owners across five namespaces, reconciling on a jittered interval and converging with none No alert fires for them; the baseline the ratio thresholds are tested against
webapp-01's server/configmap applied as updated four times a second while its other resources converge ManagedResourceNotConverging, the Updated rate and Not-converging ratio panels
Database shop/orders-db Ready=False with a lastTransitionTime eight hours in the past CustomResourceConditionStuck at once, CustomResourceNotReady after the shortened for:
Database shop/users-db Ready=Unknown and StorageReady=Unknown CustomResourceConditionUnknown, the Owners Unknown tile
Database analytics/reports-db Ready=False with its reason flipping every three minutes CustomResourceNotReady keeps firing: a reason change does not restart the for: clock
A cluster-scoped Database shared-gateway with no namespace label The CRD Conditions Browser's wildcard All keeps it visible; the alerts' cluster-scoped wording
storage/pvc applies failing three times out of four ManagedResourceApplyFailing, the Apply error ratio panel
Thirty percent of database reconciles returning an error ControllerReconcileErrors
database reconciles taking five seconds to two minutes ControllerReconcileLatencyHigh
database queue wait observations of several minutes with a deep queue and both workers busy ControllerWorkqueueBacklog, the Workqueue row
A database panic one minute in and every twenty minutes after ControllerReconcilePanics
leader_election_master_status{name="demo-operator"} at 1, or 0 with -leader=false The Leader tile, OperatorLeaderMissing

Within a few minutes of starting, every alert in the table except OperatorLeaderMissing, which needs SIMULATOR_ARGS="-leader=false", is visible as firing on http://localhost:9090/alerts.

Testing

Three checks guard the artifacts. CI runs make test-alerts and make lint-dashboards in the "Alerts and dashboards" job; the simulator parity test is an ordinary Go test and runs under make test in the unit-test job.

make test-alerts needs promtool, which ships with Prometheus and is deliberately left out of make all so contributors without it are unaffected. It renders the rules in the plain rules format with the metric namespace test_operator into two directories, one with exported_namespace and one with namespace as the namespace label, lints every file with promtool check rules --lint=all --lint-fatal, and runs the unit tests with promtool test rules --diff. Each rule file has a test file of the same name under internal/observability/templates/alerts/tests/ (crd_conditions_test.yaml for crd_conditions.tpl.yaml), whose rule_files point one directory up at the rendered file, and every alert has a firing case plus the negative cases that justify its design, among them legitimate churn at scale and a single edit on an idle resource for ManagedResourceNotConverging, sporadic conflicts among successful applies for ManagedResourceApplyFailing, and a reason change mid-window and a cluster-scoped owner for the condition rules. Annotations are compared exactly, so a wording change shows up as a diff. After editing a rule, run make test-alerts and make lint-dashboards; the latter also asserts that every alert still has a unit test.

make lint-dashboards runs go test ./internal/observability/, which covers Options.Validate and Render (both alert formats, the PrometheusRule metadata, the output file set and the removal of a previous render) and lints the embedded templates: it renders every template with a fixed namespace and checks that each dashboard is valid JSON, keeps its uid (<namespace>_<file name>) and its disabled auto-refresh, and that no {{placeholder}} survived rendering. It then pulls every metric name out of every panel query, variable query and alert expression and asserts each one exists: the framework's own families and the condition gauge are gathered from the real collectors, the controller-runtime, workqueue, client-go, leader election and process families from a fixed list. For the alerts it also asserts that every rule carries only the severity label and has a promtool unit test. The same test pins the 17 character cap on the metric namespace to the longest dashboard file name, so a renamed dashboard that no longer fits Grafana's 40 character uid limit fails here.

go test ./observability/dev/simulator/ includes the parity test that keeps the simulator honest: it starts a real, unmanaged controller-runtime controller, drives one request through its workqueue so controller-runtime initialises its reconcile and workqueue series, then asserts that every lookalike family the simulator defines exists in controller-runtime's registry with the same type, label names and histogram buckets. A controller-runtime upgrade that renames a label the dashboards depend on fails this test rather than emptying a panel.

Directories

Path Synopsis
dev
simulator command
Command simulator exposes a synthetic operator's metrics on /metrics for the local observability stack under observability/dev, and the kube-state-metrics series the dashboards join against on /ksm/metrics.
Command simulator exposes a synthetic operator's metrics on /metrics for the local observability stack under observability/dev, and the kube-state-metrics series the dashboards join against on /ksm/metrics.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL