A Prometheus exporter for Omni.
It connects to the Omni API using a service account and exposes the state of the Omni instance as per-object Prometheus metrics: clusters, machines, machine sets, upgrades and etcd backups. Think of it as kube-state-metrics for Omni. It works against both self-hosted and SaaS Omni instances.
Omni itself also exposes Prometheus metrics (with the omni_ prefix), but those are instance-level aggregates on an internal endpoint, e.g. the total number of machines.
This exporter complements them with per-cluster and per-machine series that can be scraped with your own observability stack, so you can alert on things like "cluster X is not ready" or "machine Y is disconnected".
All metrics of this exporter use the omni_exporter_ prefix, so the two sets never collide.
Create a read-only service account on your Omni instance:
omnictl serviceaccount create --use-user-role=false --role=Reader omni-exporterThe Reader role is enough for everything the exporter reads, do not give it more.
Note that --use-user-role defaults to true, which would clone the role of the user running the command instead.
The command prints an OMNI_SERVICE_ACCOUNT_KEY.
Run the exporter with it:
docker run -d -p 10048:10048 \
-e OMNI_ENDPOINT=https://<account>.omni.siderolabs.io \
-e OMNI_SERVICE_ACCOUNT_KEY=<key> \
ghcr.io/siderolabs/omni_exporter:latestMetrics are served on http://localhost:10048/metrics.
The key can alternatively be read from a file via --omni.service-account-key-file, e.g. for Kubernetes secret mounts.
See --help for all flags, including --web.config.file for TLS and basic authentication on the metrics endpoint.
Example scrape configuration:
scrape_configs:
- job_name: omni
static_configs:
- targets: ["localhost:10048"]State metrics representing an enum (phases, stages, statuses) emit one series per possible value with a 0/1 value, so alerting expressions can rely on simple == 1 matches and series do not come and go on state transitions.
Name (prefix omni_exporter_ omitted) |
Labels | Description |
|---|---|---|
cluster_phase |
cluster_id, phase |
Whether the cluster is in the given phase. |
cluster_ready |
cluster_id |
Whether the cluster is ready. |
cluster_available |
cluster_id |
Whether the cluster is available. |
cluster_kubernetes_api_ready |
cluster_id |
Whether the Kubernetes API of the cluster is ready. |
cluster_control_plane_ready |
cluster_id |
Whether the control plane of the cluster is ready. |
cluster_machines |
cluster_id |
Number of machines in the cluster. |
cluster_machines_healthy |
cluster_id |
Number of healthy machines in the cluster. |
cluster_machines_connected |
cluster_id |
Number of machines in the cluster connected to Omni. |
cluster_machines_requested |
cluster_id |
Number of machines requested for the cluster, which can differ from cluster_machines when machine classes are used. |
cluster_info |
cluster_id, talos_version, kubernetes_version |
Cluster information. |
cluster_talos_upgrade_phase |
cluster_id, phase |
Whether the Talos upgrade of the cluster is in the given phase. |
cluster_kubernetes_upgrade_phase |
cluster_id, phase |
Whether the Kubernetes upgrade of the cluster is in the given phase. |
cluster_etcd_backup_enabled |
cluster_id |
Whether etcd backups are enabled for the cluster. |
cluster_etcd_backup_status |
cluster_id, status |
Whether the last etcd backup of the cluster is in the given status (series exist only after the first backup attempt, so enabled=1 with no status series means no backup was attempted yet). |
cluster_etcd_backup_last_success_timestamp_seconds |
cluster_id |
Unix timestamp of the last successful etcd backup. |
cluster_etcd_backup_last_attempt_timestamp_seconds |
cluster_id |
Unix timestamp of the last etcd backup attempt. |
machine_connected |
machine_id |
Whether the machine is connected to Omni. |
machine_power_state |
machine_id, state |
Whether the machine is in the given power state. |
machine_info |
machine_id, cluster_id, role, hostname, management_address, talos_version, platform, arch |
Machine information to be joined with the state metrics, with an empty cluster_id for unallocated machines. |
cluster_machine_stage |
cluster_id, machine_set_id, machine_id, stage |
Whether the cluster machine is in the given stage. |
cluster_machine_ready |
cluster_id, machine_set_id, machine_id |
Whether the cluster machine is ready. |
machine_set_phase |
cluster_id, machine_set_id, phase |
Whether the machine set is in the given phase. |
machine_set_ready |
cluster_id, machine_set_id |
Whether the machine set is ready. |
machine_set_machines |
cluster_id, machine_set_id |
Number of machines in the machine set. |
machine_set_machines_healthy |
cluster_id, machine_set_id |
Number of healthy machines in the machine set. |
machine_set_machines_connected |
cluster_id, machine_set_id |
Number of machines in the machine set connected to Omni. |
machine_set_machines_requested |
cluster_id, machine_set_id |
Number of machines requested for the machine set. |
etcd_backup_store_info |
configuration_name |
Etcd backup store information, with the configured store type, e.g. s3 or disabled. |
etcd_backup_store_ready |
Whether the etcd backup store configuration of the instance has no errors (it is 1 also when backups are disabled altogether, check etcd_backup_store_info for that). |
|
up |
Whether Omni is reachable and every collector has completed its initial sync and renders successfully. | |
reachable |
Whether Omni answered the reachability probe of the current scrape. | |
collector_success |
collector |
Whether the object metrics of a single collector are exposed: Omni reachable, initial sync completed, rendering succeeded. |
collector_events_total |
collector, event |
Watch events applied to the in-memory view, excluding the initial sync. |
collector_watch_attempts_total |
collector |
Watch establishment attempts, counted before the attempt; each success is a full resynchronization (above one means a previous attempt had failed). |
collector_cached_resources |
collector |
Number of resources in the in-memory view. |
collector_last_sync_timestamp_seconds |
collector |
Unix timestamp of the last sync or applied event (absent until the first sync, and quiet resource types legitimately keep an old value). |
build_info |
version, revision, ... |
Exporter build information. |
The build_info metric and the standard Go and process metrics are added by the exporter binary, everything else comes from the collector library (see below).
Aggregations are left to PromQL.
For example, the number of machines of a cluster by stage is sum by (cluster_id, stage) (omni_exporter_cluster_machine_stage), and the machine count of the whole instance is count(omni_exporter_machine_connected).
The exporter maintains one watch per resource type, keeps an in-memory view updated by the events, and scrapes render from that view. The cost of running it is therefore driven by the actual state churn, not by the scrape frequency: the only Omni access on the scrape path is a tiny reachability probe. The probe is bounded by a timeout of a few seconds, so keep the Prometheus scrape timeout above that.
Object metrics are suppressed only when serving them would be an outright wrong answer, not merely an old one:
- Every scrape starts with a cheap, deadline-bounded reachability probe.
When Omni does not answer it, the object metrics are suppressed for that scrape (Prometheus marks the series stale immediately, exactly as if the objects were read on every scrape),
omni_exporter_reachableandomni_exporter_upgo to 0, and only the exporter self-metrics keep being served. An unreachable Omni puts no bound on how far the view has drifted, which is what makes suppression the right answer there. - Until a resource type has completed its initial sync, its object metrics are suppressed and its
omni_exporter_collector_successis 0. A half-filled view would read as "these objects do not exist", so this is the one case that has to fail closed.
A watch failure after the initial sync does not suppress anything.
The client resumes the stream from its last bookmark, and a resume the backend refuses surfaces as an error which re-synchronizes that resource type from scratch, visible in omni_exporter_collector_watch_attempts_total.
A resume applies the missed events to the existing view, a re-synchronization swaps in a fresh one when it completes, and either way the last view keeps being served throughout, so the metrics go stale instead of disappearing and coming back.
That choice has a cost. omni_exporter_up covers reachability and the initial sync, but says nothing about the ongoing health of a watch.
So if Omni keeps answering the reachability probe while a watch stays broken, the exporter serves an ever older view with up at 1, for as long as that lasts.
Alerting on rate(omni_exporter_collector_watch_attempts_total[10m]) > 0 catches a resource type that keeps re-synchronizing.
omni_exporter_collector_last_sync_timestamp_seconds cannot fill that role, because a resource type with no changes legitimately keeps an old value.
An outage of Omni therefore looks the same as it would with a read-per-scrape exporter: object metrics disappear and omni_exporter_up is 0, within one scrape interval.
Notes:
- Alerts on object metrics should be gated on
omni_exporter_up == 1, andomni_exporter_up == 0deserves its own alert. In particular, absence-based alerts cannot distinguish a deleted object from an unreachable Omni without that gate. - An expired, deleted or unauthorized service account surfaces through the reachability probe on the next scrape. Service account keys have a limited lifetime (one year by default), and rotating the key requires a restart.
- The
cluster_id,machine_idandmachine_set_idlabels are deliberately not namedclusteretc., to avoid clashing with target labels commonly attached by scrape configurations. - Cluster and machine set IDs are user-chosen names. A cluster deleted and recreated under the same name continues the same series.
- Different resource types are read independently within a scrape, so they can be a moment apart from each other.
Use alert
fordurations longer than one scrape interval instead of expecting cross-metric consistency within a single scrape.
The collectors live in the importable pkg/collector package: it takes a COSI state and returns a prometheus.Collector, and only depends on the Omni client SDK.
The watches are started via its Run method, which retries all upstream failures until the given context is canceled.
The standalone binary wires it to the Omni API, but it can be embedded into any process with access to an Omni state.
Mozilla Public License 2.0, see LICENSE.