Manage statistics with tenant-aware configuration

Overview

F5 BIG-IP Next for Kubernetes supports tenant-aware statistics management for both single-tenant and multi-tenant Kubernetes deployments through a set of Kubernetes Custom Resources (CRs). This feature lets each tenant or namespace independently control which metrics they collect, how often they collect them, and how they label and export the data — all without affecting other tenants in the same cluster.

TMM instances generate metrics organized into tables. Each table contains metrics and labels. Labels identify the entity being measured, such as a virtual server’s name or destination address. When multiple TMM instances report the same metric with same labels, the system combines them into a single exported value instead of sending a separate data point for each TMM.

For more details on metrics and tables, see OpenTelemetry Metric Details

In this document, tenant means the Kubernetes namespace where the traffic configuration CRs and metric collection CRs are applied.

Why you need this feature

Tenant-aware statistics management gives each tenant independent control over their monitoring configuration. This allows,

  • Independent-interval metric scraping

  • Selective metric collection and export

  • Custom metric labeling and relabeling

  • Configurable metric aggregation modes (Aggregated, SemiAggregated, Diagnostic)

Note

In single-tenant deployments, these features can simplify monitoring by separating export policies, scrape intervals, and aggregation behavior for different workloads or namespaces.

Workflow

alt text

The telemetry pipeline uses a gRPC-based flow to collect and export data efficiently. The Operator watches for Scrape custom resources and schedules collection jobs when they are valid. The Observer then sends gRPC requests to each target receiver and collects the data. To save resources, the Operator merges overlapping intervals. For example, if two tenants request data from the same table at two-second and three-second intervals, the Operator schedules a single scrape at the shortest interval. The Observer filters the results and sends them to each tenant’s endpoint at the requested frequency. The pipeline then exports the processed data to OpenTelemetry (OTel).

Use cases

Use case Description
Namespace or Tenant-specific metric collection Each tenant selects only the metrics that matter to their workloads, reducing noise and resource usage.
Custom scrape intervals per workload or tenant Different tenants can collect the same metric at different frequencies — for example, one tenant every 10 seconds and another every 60 seconds.
Aggregation mode selection Tenants choose how metrics from multiple Traffic Management Microkernel (TMM) instances are combined: Aggregated, SemiAggregated, or Diagnostic.
Label customization Tenants can relabel or extract new labels from existing metrics to match their own indexing and dashboarding requirements.
Namespace-based isolation and workload separation Each tenant operates in their own Kubernetes namespace. Configuration of namespace cannot interfere with another.
Administrator-controlled policies Cluster administrators define which namespaces and metrics each tenant can access, enforcing security and governance boundaries.
Narrowing export volume Tenants can limit jobs to a subset of metrics within a table using metricNames, reducing OpenTelemetry (OTel) export volume.
Single-tenant workload separation Use the same CR framework to isolate monitoring policies for different applications or teams within a single cluster.

Understand tenant-aware statistics management

The statistics infrastructure uses three custom resource kinds — ScrapeClass, ScrapeTemplate, and Scrape — to give administrators policy-level control while granting tenants the flexibility to define their own monitoring workflows.

The three custom resources

Three Custom Resource kinds work together to manage tenant-aware statistics:

Resource API version Scope Purpose
ScrapeClass metrics.f5.net/v1alpha1 Cluster Defines which namespaces or instances a scrape can target. Referenced by both ScrapeTemplate and Scrape.
ScrapeTemplate metrics.f5.net/v1alpha1 Namespaced Declares which tables to export, at what interval, and with what aggregation mode Auto-generates a Scrape CR when applied.
Scrape metrics.f5.net/v1alpha1 Namespaced Holds the PromQL queries that define what metrics to export. Exposes a per-job lifecycle and status.

Default behavior

A ScrapeClass named default-scrape-class and a ScrapeTemplate named default-scrape-template are pre-installed with the product. Together they export all available tables once per minute with aggregation enabled.

Note

The pre-installed default-scrape-class is not annotated with scrapeclass.io.observer/is-default-class: "true". It will not be automatically resolved when a Scrape or ScrapeTemplate omits the scrapeClass field.

CR parameters

ScrapeClass CR fields

Field Type Required Default Description
spec.scope Cluster | Namespaced No Cluster Controls whether this ScrapeClass allows Scrape CRs to export metrics cluster-wide from all namespaces, or only from the namespace where the Scrape is applied. See Scope.
spec.allowedNamespaces []string No — A list of namespaces permitted to reference this ScrapeClass. Scrapes or ScrapeTemplates applied outside this list will not be scheduled.
spec.matchers []Matcher No — Defines global label filtering rules that are automatically applied to every scrape/job referencing this ScrapeClass. See Matchers.
spec.controllerNamespace string No — Restricts this ScrapeClass to a specific Observer controller (Operator) namespace. In multi-deployment setups with multiple Operators, only the Operator in the specified namespace will process Scrapes referencing this class. If not set, any Operator can use this ScrapeClass.

Scope

The scope field controls where a scrape job collects metrics from—either across all tenant namespaces in the cluster or only within the namespace where the Scrape resource is defined. This is especially important in multi-tenant environments to ensure proper data isolation.

Value Behavior
Cluster (default) Scrape jobs collect metrics from all namespaces in the cluster. Recommended for single-tenant deployments or when global visibility is required.
Namespaced Scrape jobs collect metrics only from the namespace where the Scrape or ScrapeTemplate is defined. Recommended for multi-tenant environments to restrict tenant visibility.

Note

Some TMM metric tables (for example, tmm_stat) represent system-wide data and are not associated with any tenant namespace. Metrics such as f5.tmm.cpu_utilization.1min apply to the entire TMM instance. These metrics require Cluster scope, because a Namespaced scrape cannot match data without a namespace association.

Matchers

Matchers provide additional filtering by applying label-based conditions to scrape jobs. They help control which TMM instances or metric streams are included when a ScrapeClass is used.

Field Description
name Label to evaluate (for example, __bucket_name__, __bucket_namespace__, or __bucket_group__).
operator Type of comparison applied to the label value.
value Value to compare against. For Regexp and NotRegexp, this is a regular expression.

Supported Operators

Operator Description
Equal Matches when the label value equals the specified value.
NotEqual Matches when the label value does not equal the specified value.
Regexp Matches when the label value matches the regular expression.
NotRegexp Matches when the label value does not match the regular expression.

For configuration see Configure metrics For examples see CR Samples

ScrapeTemplate CR fields

Field Type Required Default Description
spec.scrapeClass string No (default class) Name of the ScrapeClass to use. If you omit this field, the system uses the default ScrapeClass. Note: You can define the default ScrapeClass by adding the annotation scrapeclass.io.observer/is-default-class: "true" to a ScrapeClass.
spec.jobs []JobTemplate Yes — List of scrape jobs. You must include at least one job.
spec.jobs[].name string Yes — A unique name for the scrape job.
spec.jobs[].table string Yes — The descriptor table name to scrape (for example, virtual_server_stat).
spec.jobs[].every duration Yes — Defines how often to run the scrape job (for example, 10s, 30s, 1m, 5m).
spec.jobs[].metricNames []string No (all metrics) Limits the job to a subset of metrics within the table. Use alias names from the descriptor.
spec.jobs[].aggregation string No Aggregated Controls how metrics from multiple TMM instances are combined: Aggregated, SemiAggregated, or Diagnostic. See Aggregation modes
spec.jobs[].scrapeInterval duration No same value specified in every Defines how often the receiver polls TMM for new metrics. For shorter export intervals, set this slightly lower than every (for example, scrapeInterval: 4.5s with every: 5s) to ensure each export cycle uses the most recent data.
spec.suspend bool No false When true, the generated Scrape is suspended and no metrics are exported.

Note

The minimum recommended value for every and scrapeInterval is 3s. Setting below this threshold may cause high resource usage or unstable behavior.

Aggregation modes

When multiple TMM instances report the same metric with identical label values, the system combines them before exporting. The aggregation field on a ScrapeTemplate job controls how that combination works.

Mode Behavior
Aggregated (default) Metrics are summed across all TMM instances, including those that have been removed.
SemiAggregated Metrics are summed across currently active TMM instances only. Removed instances are excluded.
Diagnostic No aggregation. Each TMM instance's metrics are exported individually.

For configuration see Configure metrics For examples see CR Samples

Scrape CR fields

Note

ScrapeTemplate auto-generates a Scrape CR with default queries to export metrics. To override template and customize the PromQL queries, create a Scrape CR.

Field Type Required Default Description
spec.scrapeClass string No (default class) Name of the ScrapeClass to use. If you omit this field, the system uses the default ScrapeClass.
spec.jobs []Job Yes — List of scrape jobs. Each job includes a name, interval, and PromQL query.
spec.jobs[].name string Yes — A unique name for the scrape job.
spec.jobs[].every duration Yes — Defines how often to run the scrape job (for example, 10s, 30s, 1m).
spec.jobs[].query string Yes — The PromQL query that defines which metrics to collect.
spec.jobs[].scrapeInterval duration No same value specified in every Defines how often the receiver polls TMM for new metrics. For shorter export intervals, set this slightly lower than every (for example, scrapeInterval: 4.5s with every: 5s) to ensure each export cycle uses the most recent data.
spec.suspend bool No false When true, pauses all jobs and removes their scheduled executions.

Note

The minimum recommended value for every and scrapeInterval is 3s. Setting below this threshold may cause high resource usage or unstable behavior.

Understanding PromQL queries

The query field in a Scrape job defines a PromQL expression that selects which metrics to export. PromQL (Prometheus Query Language) is used to filter, transform, and aggregate time-series metrics.

  • When a Scrape is created from a ScrapeTemplate, the query is automatically generated based on:

    • Table name

    • Aggregation mode

    • Optional metric filters

  • When creating a Scrape manually, you must define the query yourself.

The system validates the query at scheduling time. If the query is invalid, the Scrape moves to a Failed phase with a QueryError reason.

Note

  • The Observer runs queries internally using PromQL-compatible logic.

  • Only instant queries are supported. Range queries and historical metrics are not supported.

Basic queries

Basic queries use the __name__ label to match metrics by name. These are useful when aggregation and label transformations are not required. You can use basic queries for,

  • Simple metric selection

  • Debugging

  • Lightweight exports

Advanced queries with aliases and internal labels
  • Aliases Metrics and labels use standardized alias names (dot‑separated and table‑prefixed), which are used directly in queries, where the metric alias is the __name__ value and label aliases appear as regular labels.

Examples:

# Select all metrics matching a pattern
query: "{__name__=~\"f5.virtual_server.*\"}"

# Select a specific metric
query: "{__name__=\"f5.virtual_server.clientside.received.bytes\"}"

Note

Always use alias names exactly as defined in the tables reference.

  • Internal labels Internal labels are automatically attached during metric processing. These labels start with __ and are removed from exported metrics unless explicitly renamed.

Label Description
__name__ Metric alias name
__fqn__ Fully qualified metric identifier
__table__ Source metrics table
__namespace__ Kubernetes namespace of configuration
__bucket_namespace__ Namespace of the TMM instance
__bucket_name__ Name of the TMM instance
__bucket_group__ Group of the TMM instance
Custom functions

The system extends standard PromQL with custom functions to help you combine, modify, and enrich metric data during queries.

Use these when you need to:

  • Combine multiple metrics

  • Rename labels for export or standardisation

  • Add metadata to metrics

Functions

Function Syntax Description
coalesce coalesce(expr1, expr2, ...) Combines all samples from input vectors into a single result set to enable querying multiple metrics from the same table in one query.
label_move label_move(v, src1, dst1, ...) Renames existing labels, often used to expose or standardise internal label names.
label_add label_add(v, key1, val1, ...) Adds constant labels to all samples in the metric vector.
Aggregation and Grouping

Aggregation and grouping help you summarise and organise metrics effectively when writing queries. By selecting the right labels and controls, you can ensure accurate results and avoid mixing unrelated data.

Best Practices

Topic Details
Include required labels Always include essential labels such as __namespace__ and __bucket_namespace__. Add other relevant label aliases as needed.
Use __fqn__ when necessary Include __fqn__ in grouping when metrics share the same alias but differ by additional labels.
Control aggregation scope To exclude data from removed or merged TMM instances, use:

text __bucket_name__ != "merged"
Use aggregation operators Common operators include sum, min, and max to calculate totals or identify minimum and maximum values.

PromQL query examples

Aggregated — sum across all TMM instances including removed ones

This example demonstrates adding a custom label to the exported metrics.

query: |-
  label_add(
    label_move(
      coalesce(
        sum by (__name__, __namespace__, __bucket_namespace__,
          "f5.virtual_server.destination", "f5.virtual_server.name", "f5.virtual_server.source") (
          {__name__="f5.virtual_server.clientside.received.bytes", __table__="virtual_server_stat"}
        ),
        sum by (__name__, __namespace__, __bucket_namespace__,
          "f5.virtual_server.destination", "f5.virtual_server.name", "f5.virtual_server.source") (
          {__name__="f5.virtual_server.clientside.transmitted.bytes", __table__="virtual_server_stat"}
        )
      ),
      "__namespace__", "k8s.namespace.name",
      "__bucket_name__", "target.name",
      "__bucket_namespace__", "target.namespace"
    ),
    "my.custom.label", "my-value"
  )

Diagnostic — raw per-TMM, no aggregation

This example for TMM instance exports its metrics as separate data points. The target.name field identifies the specific TMM instance that generated each metric sample.

query: |-
  label_move(
    coalesce(
      {__name__="f5.virtual_server.clientside.received.bytes", __table__="virtual_server_stat"},
      {__name__="f5.virtual_server.clientside.transmitted.bytes", __table__="virtual_server_stat"}
    ),
    "__namespace__", "k8s.namespace.name",
    "__bucket_name__", "target.name",
    "__bucket_namespace__", "target.namespace"
  )

Configure metrics

  1. Create a ScrapeClass. A ScrapeClass defines the access policy for a tenant. Set scope: Namespaced to restrict tenants to metrics in their own namespace.

    Note

    You can define the default ScrapeClass by adding the annotation scrapeclass.io.observer/is-default-class: "true". When a Scrape or ScrapeTemplate omits the scrapeClass field, the default ScrapeClass is resolved automatically. Only one default ScrapeClass may exist at a time.

    Example

    apiVersion: metrics.f5.net/v1alpha1
    kind: ScrapeClass
    metadata:
      name: tenant-a-class
    spec:
      scope: Namespaced
      allowedNamespaces:
        - tenant-a
    
  2. Apply a ScrapeTemplate. A ScrapeTemplate declares which tables to export and at what interval. Apply the template in the tenant’s namespace.

    Example

    apiVersion: metrics.f5.net/v1alpha1
    kind: ScrapeTemplate
    metadata:
      name: tenant-a-template
      namespace: tenant-a
    spec:
      scrapeClass: tenant-a-class
      jobs:
        - name: virtual-server-job
          table: virtual_server_stat
          every: 10s
    
  3. (Optional) Create a direct Scrape if you need fine-grained control beyond what a ScrapeTemplate provides with specific PromQL queries. This overrides the template layer entirely.

    Example

    apiVersion: metrics.f5.net/v1alpha1
    kind: Scrape
    metadata:
      name: custom-scrape
      namespace: default
    spec:
      scrapeClass: my-scrape-class
      jobs:
        - name: vs-received-bytes
          every: 30s
          query: "{__name__=\"f5.virtual_server.clientside.received.bytes\"}"
        - name: vs-transmitted-bytes
          every: 30s
          query: "{__name__=\"f5.virtual_server.clientside.transmitted.bytes\"}"
    

Verification

# Check the ScrapeTemplate status
$ oc get scrapetemplate <template-name> -n <namespace>

ScrapeTemplate status

After you apply a ScrapeTemplate, the Operator updates status to indicate whether a Scrape resource was successfully generated.

Field Description
status.scrapeName Name of the generated Scrape resource.
status.conditions[].type: Ready Indicates whether the Scrape was generated successfully. Set to True on success, or False message on failure is included.
# Check the generated Scrape CR and its phase
$ oc get scrape -n <namespace>
# View detailed status of a Scrape CR
oc describe scrape <scrape-name> -n <namespace>

Scrape status

Field Description
status.phase Current lifecycle phase: Pending, Running, Error, or Failed.
status.ready Number of ready jobs out of total (for example, 2/3).
status.conditions[].type: JobsScheduled Set to True once all jobs are accepted for scheduling. Set to False with a reason and message if scheduling is blocked.
status.conditions[].type: Ready Set to True when all jobs are running successfully. Set to False with a list of unready job names otherwise.
status.jobStatuses Per-job status list including job name, ready (boolean), and a message describing the current state or last error. When a job is ready, ready: true is shown with no message. When not ready, ready is omitted and the message contains the reason.

Lifecycle

A Scrape moves through the following phases:

Phase Description
Pending The Scrape is waiting to be scheduled. This can happen if the referenced ScrapeClass is missing, spec.suspend is set to true, or the jobs have not completed their first run. The system retries automatically every minute.
Running All jobs are scheduled and have completed at least one successful run.
Error Jobs are running, but one or more jobs fail during execution. The Scrape continues to retry.
Failed A configuration issue prevents scheduling. The Scrape does not retry. Check status.conditions for details. Common causes include an invalid job query, restricted namespace access, or a configuration mismatch.
# Check the reason for `Pending`, `Error`, or `Failed`
oc describe scrape <name>

CR Samples

Cluster-scoped ScrapeClass

apiVersion: metrics.f5.net/v1alpha1
kind: ScrapeClass
metadata:
  name: my-scrape-class
spec:
  scope: Cluster

Restrict scraping using matchers

This example excludes metrics from the beta namespace while allowing all others.

apiVersion: metrics.f5.net/v1alpha1
kind: ScrapeClass
metadata:
  name: test-class
spec:
  scope: Cluster
  matchers:
    - name: "__bucket_namespace__"
      operator: "NotEqual"
      value: "beta"

Multi-tenant isolation

In this example, two tenants each have their own namespace, ScrapeClass, and ScrapeTemplate. Tenant A collects virtual_server_stat every minute. Tenant B collects the same table every 10 seconds.

Tenant A:

apiVersion: metrics.f5.net/v1alpha1
kind: ScrapeClass
metadata:
  name: tenant-a-class
spec:
  scope: Namespaced
  allowedNamespaces:
    - tenant-a
---
apiVersion: metrics.f5.net/v1alpha1
kind: ScrapeTemplate
metadata:
  name: tenant-a-template
  namespace: tenant-a
spec:
  scrapeClass: tenant-a-class
  jobs:
    - name: virtual-server-job
      table: virtual_server_stat
      every: 1m

Tenant B:

apiVersion: metrics.f5.net/v1alpha1
kind: ScrapeClass
metadata:
  name: tenant-b-class
spec:
  scope: Namespaced
  allowedNamespaces:
    - tenant-b
---
apiVersion: metrics.f5.net/v1alpha1
kind: ScrapeTemplate
metadata:
  name: tenant-b-template
  namespace: tenant-b
spec:
  scrapeClass: tenant-b-class
  jobs:
    - name: virtual-server-job
      table: virtual_server_stat
      every: 10s

A ScrapeTemplate applied in tenant-b cannot reference tenant-a-class. If a namespace restriction is violated, the generated Scrape enters a Failed phase.

Narrowing export to specific metrics

To reduce OTel export volume, use metricNames to limit a job to a subset of metrics within a table:

apiVersion: metrics.f5.net/v1alpha1
kind: ScrapeTemplate
metadata:
  name: vs-bytes-only
  namespace: default
spec:
  scrapeClass: my-scrape-class
  jobs:
    - name: vs-bytes-job
      table: virtual_server_stat
      every: 5m
      metricNames:
        - f5.virtual_server.clientside.received.bytes
        - f5.virtual_server.clientside.transmitted.bytes

Cluster-wide scraping

For system-wide observability, an administrator can create a cluster-scoped ScrapeClass:

apiVersion: metrics.f5.net/v1alpha1
kind: ScrapeClass
metadata:
  name: my-scrape-class
spec:
  scope: Cluster
---
apiVersion: metrics.f5.net/v1alpha1
kind: ScrapeTemplate
metadata:
  name: my-scrape-template
  namespace: default
spec:
  scrapeClass: my-scrape-class
  jobs:
    - name: virtual-server-job
      table: virtual_server_stat
      every: 1m

For more details see, Distributed Telemetry and Operations Data Aggregation (TODA) for statistics.