Klio provides built-in support for OpenTelemetry, enabling comprehensive observability through distributed tracing and metrics collection. This allows you to monitor backup operations, performance characteristics, and system health across your Klio deployment.
Available Telemetry
Klio automatically collects the following:
- Traces
- Distributed WAL streaming and processing
- Backup lifecycle (backup, backup run, verification, maintenance)
- Metrics
- Server
- Server uptime
- Backup metrics
- Number of snapshots
- Number of files in the latest snapshot
- Number of directories in the latest snapshot
- Size of the latest snapshot
- Age of the latest snapshot
- Age of the oldest snapshot
- Total number of backup verifications (split by outcome and tier)
- WAL processing metrics
- Number of WAL files written
- Bytes written
- Timestamp of the most recently written WAL file
- LSN progress of WAL ingestion (Tier 1) and archival (Tier 2)
- Timeline of the latest WAL on Tier 1 and Tier 2
- Queue metrics
- Number of messages in the queue
- Number of bytes in the queue
- GRPC metrics
- Go runtime statistics
- Host metrics
- Controller runtime metrics
- Sidecar
- Backup metrics
- Number of backups currently in progress
- Timestamp of the most recent backup start
- Timestamp of the most recent successful completion
- Timestamp of the most recent failure
- Duration of the most recent backup
- Total number of backup runs (split by outcome)
- Total number of backup verifications (split by outcome)
- GRPC metrics
- Go runtime statistics
- Host metrics
- Controller runtime metrics
- Backup metrics
- Server
Note
Log exporters are not currently supported.
Traces Reference
Backup lifecycle spans
When a backup is triggered through CNPG-I, Klio creates
the following spans under the klio.plugin.backup tracer:
| Span Name | Description |
|---|---|
backup | Root span covering the entire backup operation (run + verify + maintenance) |
backup_run | Child span for the actual data backup execution |
backup_verify | Child span for post-backup verification |
backup_maintenance | Child span for post-backup maintenance |
The backup span includes the following attributes:
| Attribute | Type | Description |
|---|---|---|
backup.name | string | Name assigned to the backup |
On failure, the span records the error and sets its status to
ERROR.
Metrics Reference
Klio metric names follow the
klio.<component>.<domain>.<measurement> taxonomy. The component
segment identifies which process emits the metric:
plugin— the CNPG plugin sidecar running in each PostgreSQL pod.server— the Klio server StatefulSet (hosts the Kopia server, the WAL gRPC ingest, the embedded NATS JetStream queue, and the tier-2 WAL consumer).operator— the Klio operator deployment. Bridges controller-runtime Prometheus metrics to OTLP and adds Go runtime and host instrumentation.client— reserved for future instrumentation; no metrics are emitted today.
Attributes
Klio metrics carry the following attributes. Each per-metric table below repeats the applicable attributes in its descriptions; this section is the central reference for the value space of each attribute key.
| Attribute | Values | Applies to |
|---|---|---|
tier | tier1 (local disk on the Klio server), tier2 (remote object store) | All klio.server.wal.* and klio.server.backup.* instruments. |
cluster_name | Name of the PostgreSQL cluster the recording belongs to | All klio.server.wal.* instruments. |
outcome | success, failure | klio.plugin.backup.runs, klio.plugin.backup.verifications, klio.server.backup.verifications. |
snapshot_source | Kopia source descriptor (userName@hostName:path) | All klio.server.backup.* base snapshot gauges (snapshots, latest_snapshot_*, oldest_snapshot_age). |
stream | JetStream stream name (klio-wal-stream, klio-backup-stream, klio-latest-uploaded-wal-per-cluster-stream) | klio.server.queue.messages, klio.server.queue.bytes. |
Backup lifecycle metrics (plugin sidecar)
These metrics are emitted by the plugin sidecar and track backup operations on each PostgreSQL instance:
| Metric Name | Type | Unit | Description |
|---|---|---|---|
klio.plugin.backup.in_progress | UpDownCounter | {backups} | Number of backups currently in progress |
klio.plugin.backup.latest_start_time | Gauge | s | Unix epoch timestamp when the most recent backup started |
klio.plugin.backup.latest_completion_time | Gauge | s | Unix epoch timestamp when the most recent backup completed successfully |
klio.plugin.backup.latest_failure_time | Gauge | s | Unix epoch timestamp when the most recent backup failed |
klio.plugin.backup.latest_duration | Gauge | s | Duration of the most recent backup |
klio.plugin.backup.runs | Counter | {backups} | Total number of backup runs, split by the outcome attribute (success / failure) |
klio.plugin.backup.verifications | Counter | {verifications} | Total number of backup verification attempts, split by the outcome attribute (success / failure) |
Note
These metrics are tied to the plugin sidecar lifecycle: when the
sidecar restarts (for example, after a pod reschedule or PostgreSQL
instance failover) the counters reset to zero and the gauges are
re-initialized on the next backup. As a result,
klio.plugin.backup.runs and klio.plugin.backup.verifications
report totals since the last sidecar start rather than over the life
of the cluster, and may diverge from the count of Backup
resources.
WAL ingest metrics (server)
The WAL ingest series is unified across tiers: WAL bytes and files
written to local disk by the WAL gRPC server (tier 1) and uploaded
to remote storage by the consumer (tier 2) share a single instrument
family and are distinguished by the tier attribute ("tier1" or
"tier2").
| Metric Name | Type | Unit | Description |
|---|---|---|---|
klio.server.wal.written_size | Counter | By | Number of bytes written for WAL files (per tier) |
klio.server.wal.written | Counter | - | Number of WAL files written (per tier) |
klio.server.wal.latest_written_time | Gauge | s | Unix epoch timestamp of the most recently written WAL file (per tier) |
klio.server.wal.latest_written_lsn | Gauge | By | LSN of the most recently written WAL byte. On tier 1 this is the flush pointer (matches pg_current_wal_flush_lsn() semantics); on tier 2 this is the last byte of the most recently archived WAL segment |
klio.server.wal.latest_written_timeline | Gauge | - | Timeline ID of the most recently completed WAL file (per tier) |
Every recording carries a cluster_name attribute identifying the
PostgreSQL cluster, alongside the tier discriminator.
Backup verification metrics (server)
The server verifies each backup that the plugin records via
klio.plugin.backup.*. Verification happens at two points: once
against the tier-1 local copy immediately after the backup
completes, and again against the tier-2 remote copy after migration.
The tier attribute ("tier1" or "tier2") identifies which check the
recording refers to.
| Metric Name | Type | Unit | Description |
|---|---|---|---|
klio.server.backup.verifications | Counter | {verifications} | Number of backup verifications, split by the outcome attribute (success / failure; failure indicates corruption detected) and the tier attribute |
Alerting on stalled WAL processing
The same klio.server.wal.latest_written_time instrument is emitted
from two stages of the WAL pipeline, distinguished by the tier
attribute. A stale value signals a different failure depending on
the tier:
tier="tier1"reflects when the Klio server last received a WAL file from PostgreSQL streaming replication and persisted it to local disk. A stale value means PostgreSQL is no longer shipping WALs to Klio, which may indicate a replication problem, or that writing on disk is failing.tier="tier2"reflects when the consumer last uploaded a WAL file to tier-2 object storage. A stale value means the remote backend is no longer receiving WALs, even though PostgreSQL replication may still be working, or that uploading the WAL to the object store is failing.
Server metrics
| Metric Name | Type | Unit | Description |
|---|---|---|---|
klio.server.uptime | Gauge | s | Klio server uptime in seconds |
The klio.server.wal.latest_written_lsn instrument provides a
complementary view of the same two pipeline stages, expressed as a
byte offset rather than a wall-clock timestamp:
tier="tier1"is updated on every flushed WAL block received by the WAL server (trackspg_current_wal_flush_lsn()semantics).tier="tier2"is updated once per completed WAL file by the consumer. Its value is the LSN of the last byte of the WAL segment just archived.
The companion klio.server.wal.latest_written_timeline gauge
exposes the timeline ID of the WAL file each tier is currently
handling.
Warning
While usually increasing, the LSN gauge may decrease after the promotion of a lagging standby.
Use these gauges alongside the timestamp gauges to distinguish a slow pipeline (timestamps advancing, LSN gap growing) from a stalled one (timestamps and LSN both frozen).
Base backup metrics (server)
These metrics are emitted by the Klio server base backup component and track Kopia snapshot statistics:
| Metric Name | Type | Unit | Description |
|---|---|---|---|
klio.server.backup.snapshots | Gauge | - | Total number of base snapshots |
klio.server.backup.latest_snapshot_size | Gauge | By | Size of latest base snapshot in bytes (ignoring compression and deduplication) |
klio.server.backup.latest_snapshot_files | Gauge | - | Number of files in latest base snapshot |
klio.server.backup.latest_snapshot_dirs | Gauge | - | Number of directories in latest base snapshot |
klio.server.backup.latest_snapshot_age | Gauge | s | Age of latest base snapshot in seconds |
klio.server.backup.oldest_snapshot_age | Gauge | s | Age of oldest base snapshot in seconds |
Every recording carries a tier attribute (tier1 for the local
disk repository, tier2 for the remote object store) and a
snapshot_source attribute identifying the source descriptor
(userName@hostName:path) the snapshot belongs to.
Queue metrics (server)
These metrics are emitted by the Klio server and track the state of
the embedded NATS JetStream streams used for asynchronous Tier 2
offloading of WAL files and backups. Each sample carries a stream
attribute identifying the source stream — typically
klio-wal-stream (WAL work queue), klio-backup-stream (backup work
queue), and klio-latest-uploaded-wal-per-cluster-stream (retention
safeguard, capped to one message per cluster):
| Metric Name | Type | Unit | Description |
|---|---|---|---|
klio.server.queue.messages | Gauge | - | Number of messages currently stored in the JetStream stream identified by stream |
klio.server.queue.bytes | Gauge | By | Number of bytes currently stored in the JetStream stream identified by stream |
Migration from the previous metric names
Klio is in alpha and previously emitted metrics under a flat namespace. The component-based taxonomy above replaces those names in a single hard rename — there is no dual emission. Update dashboards and alerts according to the following table:
| Previous name | New name | Notes |
|---|---|---|
klio.backup.* | klio.plugin.backup.* | Plugin sidecar metrics. |
klio.backup.running | klio.plugin.backup.in_progress | Renamed and switched from a 0/1 gauge to an UpDownCounter; reports the number of concurrent backups in progress. |
klio.backup.latest_duration_seconds | klio.plugin.backup.latest_duration | The _seconds suffix was dropped — the unit (s) is conveyed via the OpenTelemetry metric metadata, per semantic conventions guidelines. The Prometheus export name is unchanged (klio_plugin_backup_latest_duration_seconds) because the Prometheus exporter appends the unit suffix when the OpenTelemetry name lacks it. |
klio.backup.successes, klio.backup.failures | klio.plugin.backup.runs | Collapsed into a single counter with an outcome attribute (success / failure). |
klio.backup.verifications | klio.plugin.backup.verifications | Now split by an outcome attribute (success / failure); the total attempt count is sum by () (verifications). |
klio.backup.verification_failures | klio.plugin.backup.verifications{outcome="failure"} | Folded into the verifications counter via the outcome attribute. |
klio.wal.written_size | klio.server.wal.written_size | Carries tier="tier1". |
klio.wal.written | klio.server.wal.written | Carries tier="tier1". |
klio.wal.latest_written_time | klio.server.wal.latest_written_time | Carries tier="tier1". |
klio.wal.latest_written_lsn | klio.server.wal.latest_written_lsn | Carries tier="tier1". |
klio.wal.latest_written_timeline | klio.server.wal.latest_written_timeline | Carries tier="tier1". |
klio.consumer.written_size | klio.server.wal.written_size | Folded into the unified WAL series with tier="tier2". |
klio.consumer.written | klio.server.wal.written | Folded into the unified WAL series with tier="tier2". |
klio.consumer.latest_written_time | klio.server.wal.latest_written_time | Folded into the unified WAL series with tier="tier2". |
klio.consumer.latest_written_lsn | klio.server.wal.latest_written_lsn | Folded into the unified WAL series with tier="tier2". |
klio.consumer.latest_written_timeline | klio.server.wal.latest_written_timeline | Folded into the unified WAL series with tier="tier2". |
klio.consumer.backup_verification_success | klio.server.backup.verifications{outcome="success"} | Moved under server.backup to pair with plugin.backup; collapsed into a single counter with an outcome attribute. |
klio.consumer.backup_verification_failure | klio.server.backup.verifications{outcome="failure"} | Same as above. |
klio.base.uptime | klio.server.uptime | Server-level metric, not tied to Kopia. |
klio.base.* | klio.server.backup.* | Snapshot metrics, folded under server.backup alongside the verification counters. |
klio.queue.* | klio.server.queue.* | NATS JetStream metrics. Values are now reported per stream via a stream attribute instead of a single global aggregate. |
Configuration
Klio automatically detects OpenTelemetry configuration through standard environment variables. If no OpenTelemetry environment variables are present, Klio will use no-op providers that don't collect any telemetry data.
Traces and metrics exporters can be configured independently through the
autoexport package.
General Settings
The following environment variables are used to configure OpenTelemetry:
OTEL_SERVICE_NAME: (required) Name of the service, e.g.,klio-serverOTEL_RESOURCE_ATTRIBUTES: Comma-separated list of resource attributes (e.g.,deployment.environment=production,service.namespace=klio-system)OTEL_RESOURCE_DETECTORS: Comma-separated list of resource detectors from theautodetectpackage, used to automatically populate resource attributes
Traces exporter
To enable the traces exporter, set the OTEL_TRACES_EXPORTER environment
variable to one of the supported exporters:
otlp: OpenTelemetry Protocol (OTLP) exporterconsole: Console exporter (useful for debugging)none: No-op exporter (disables tracing)
You can define the OTLP protocol using the OTEL_EXPORTER_OTLP_TRACES_PROTOCOL
variable, or the general OTEL_EXPORTER_OTLP_PROTOCOL. Supported protocols
include:
http/protobuf(default)grpc
Additional configuration options for trace exporters can be found in the documentation of the respective exporters:
Metrics Exporter
To enable the metrics exporter, set the OTEL_METRICS_EXPORTER environment
variable to one of the supported exporters:
otlp: OpenTelemetry Protocol (OTLP) exporterprometheus: Prometheus exporter + HTTP serverconsole: Console exporter (useful for debugging)none: No-op exporter (disables metrics)
You can define the OTLP protocol using the OTEL_EXPORTER_OTLP_METRICS_PROTOCOL
variable, or the general OTEL_EXPORTER_OTLP_PROTOCOL. Supported protocols
include:
http/protobuf(default)grpc
Additional configuration options for metrics exporters can be found in the documentation of the respective exporters:
For the Prometheus exporter, you can configure the host and port of the HTTP server using the following environment variables:
OTEL_EXPORTER_PROMETHEUS_HOST(default:localhost)OTEL_EXPORTER_PROMETHEUS_PORT(default:9464)
Exporters and receivers
The OTLP exporter pushes telemetry to any OTLP-compatible receiver. Common options include:
- An OpenTelemetry Collector,
which can receive OTLP data and fan it out to multiple backends
(Prometheus, Jaeger, Grafana, etc.). In Kubernetes, the
OpenTelemetry Operator
manages collectors via the
OpenTelemetryCollectorCRD and can expose a stable in-cluster OTLP endpoint for Klio to target. - Any backend with native OTLP support.
The Prometheus exporter starts a local HTTP server that Prometheus scrapes directly, with no intermediate collector required.
Configuring Klio with OpenTelemetry in Kubernetes
When running in a Kubernetes environment, Klio will automatically define
CONTAINER_NAME, POD_NAME and NAMESPACE_NAME environment variables.
When any of these environment variables are set, Klio will automatically add
the corresponding resource attributes (k8s.container.name, k8s.pod.name,
k8s.namespace.name) to all telemetry data. Each attribute is added
independently - you don't need all three environment variables to be present.
Info
If you have already defined any of these attributes in
OTEL_RESOURCE_ATTRIBUTES, Klio will not override them. Only missing
attributes will be added from the environment variables. This allows you to
customize the values while still benefiting from automatic defaults for any
attributes you don't explicitly set.
Klio server with OpenTelemetry
When deploying a Klio Server, you can configure OpenTelemetry by specifying
the necessary settings in the template section of the Server spec:
- Set the required environment variables for OpenTelemetry configuration in
the
servercontainer. - Mount any necessary TLS certificates for secure communication with the OpenTelemetry Collector.
For simpler management, use a ConfigMap to store the OpenTelemetry configuration:
apiVersion: v1 kind: ConfigMap metadata: name: klio-otel-config data: OTEL_SERVICE_NAME: "klio-server" OTEL_RESOURCE_DETECTORS: "telemetry.sdk,host,os.type,process.executable.name" OTEL_TRACES_EXPORTER: "otlp" OTEL_EXPORTER_OTLP_TRACES_PROTOCOL: "grpc" OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: "https://otel-collector:4317" OTEL_EXPORTER_OTLP_TRACES_COMPRESSION: "gzip" OTEL_EXPORTER_OTLP_TRACES_TIMEOUT: "10000" OTEL_EXPORTER_OTLP_TRACES_INSECURE: "false" OTEL_EXPORTER_OTLP_TRACES_CERTIFICATE: "/otel/ca.crt" OTEL_EXPORTER_OTLP_TRACES_CLIENT_CERTIFICATE: "/otel/tls.crt" OTEL_EXPORTER_OTLP_TRACES_CLIENT_KEY: "/otel/tls.key" OTEL_METRICS_EXPORTER: "otlp" OTEL_METRIC_EXPORT_INTERVAL: "60000" OTEL_EXPORTER_OTLP_METRICS_PROTOCOL: "grpc" OTEL_EXPORTER_OTLP_METRICS_ENDPOINT: "https://otel-collector:4317" OTEL_EXPORTER_OTLP_METRICS_TIMEOUT: "60000" OTEL_EXPORTER_OTLP_METRICS_INSECURE: "false" OTEL_EXPORTER_OTLP_METRICS_CERTIFICATE: "/otel/ca.crt" OTEL_EXPORTER_OTLP_METRICS_CLIENT_CERTIFICATE: "/otel/tls.crt" OTEL_EXPORTER_OTLP_METRICS_CLIENT_KEY: "/otel/tls.key" --- apiVersion: klio.enterprisedb.io/v1alpha1 kind: Server metadata: name: my-klio-server spec: # ... other configuration ... template: spec: containers: - name: server envFrom: - configMapRef: name: klio-otel-config volumeMounts: - mountPath: /otel name: otel volumes: - name: otel projected: sources: - secret: name: otel-collector-tls items: - key: ca.crt path: ca.crt - secret: name: otel-client-cert items: - key: tls.crt path: tls.crt - key: tls.key path: tls.key
Klio plugins with OpenTelemetry
When deploying Klio as a CNPG Cluster plugin, configure OpenTelemetry by
specifying the necessary environment variables in the containers section of
the PluginConfiguration spec. The available container names are:
klio-plugin: Main plugin sidecar for backup managementklio-restore: Restore operations sidecar
Create a ConfigMap for the shared OpenTelemetry configuration:
apiVersion: v1 kind: ConfigMap metadata: name: cluster-klio-otel-config data: OTEL_RESOURCE_DETECTORS: "telemetry.sdk,host,os.type,process.executable.name" OTEL_TRACES_EXPORTER: "otlp" OTEL_METRICS_EXPORTER: "otlp" OTEL_EXPORTER_OTLP_PROTOCOL: "grpc" OTEL_EXPORTER_OTLP_ENDPOINT: "https://otel-collector:4317" OTEL_EXPORTER_OTLP_COMPRESSION: "gzip" OTEL_EXPORTER_OTLP_TIMEOUT: "10000" OTEL_EXPORTER_OTLP_INSECURE: "false" OTEL_EXPORTER_OTLP_CERTIFICATE: "/projected/ca.crt" OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE: "/projected/tls.crt" OTEL_EXPORTER_OTLP_CLIENT_KEY: "/projected/tls.key"
Configure the PluginConfiguration to inject the environment variables into
each sidecar container:
apiVersion: klio.enterprisedb.io/v1alpha1 kind: PluginConfiguration metadata: name: client-config-cluster-example spec: serverAddress: klio.default clientSecretName: cluster-example-klio-user serverSecretName: klio-server-tls clusterName: cluster-example containers: - name: klio-plugin env: - name: OTEL_SERVICE_NAME value: "klio-plugin" envFrom: - configMapRef: name: cluster-klio-otel-config - name: klio-restore env: - name: OTEL_SERVICE_NAME value: "klio-restore" envFrom: - configMapRef: name: cluster-klio-otel-config
Mount the OpenTelemetry certificates using the Cluster's projectedVolumeTemplate.
The projected volume is mounted at /projected/ and is accessible to all
sidecar containers:
apiVersion: postgresql.cnpg.io/v1 kind: Cluster metadata: name: cluster-example spec: instances: 3 projectedVolumeTemplate: sources: - secret: name: otel-collector-tls items: - key: ca.crt path: ca.crt - secret: name: otel-client-cert items: - key: tls.crt path: tls.crt - key: tls.key path: tls.key plugins: - name: klio.enterprisedb.io enabled: true parameters: pluginConfigurationRef: client-config-cluster-example storage: size: 10Gi
Klio operator with OpenTelemetry
The operator bridges the controller-runtime Prometheus metrics
registry to OTLP and adds Go runtime and host instrumentation.
When no OTEL_* environment variables are present, a no-op
meter provider is installed and the operator runs without
telemetry overhead.
The existing Prometheus /metrics endpoint remains available
for pull-based scraping regardless of whether OTLP export is
enabled.
To enable OTLP export, set OTEL_* variables through the Helm
chart's controllerManager.manager.env value:
controllerManager: manager: env: OTEL_SERVICE_NAME: "klio-operator" OTEL_EXPORTER_OTLP_ENDPOINT: "http://otel-collector:4318" OTEL_EXPORTER_OTLP_PROTOCOL: "http/protobuf" # or "grpc"
The Helm chart automatically injects POD_NAME,
NAMESPACE_NAME, and CONTAINER_NAME via the Kubernetes
downward API, so the corresponding k8s.* resource attributes
are populated without additional configuration.