> ## Documentation Index
> Fetch the complete documentation index at: https://metoro.io/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# CRD Managed Ingestion Rules

> Exclude logs and trace spans before they leave a cluster with MetoroClusterIngestionRule custom resources

In addition to configuring [log filters](/docs/ingestion-settings/log-filters) and [trace filters](/docs/ingestion-settings/trace-filters) in the UI, you can declare exclusion rules as Kubernetes custom resources with `MetoroClusterIngestionRule` under the `observability.metoro.io/v1alpha1` API group. Rules are enforced by the Metoro exporter inside the cluster, so matching log lines and trace spans are dropped **before they leave the cluster**, the same enforcement point as UI filters. This lets you keep ingestion filtering in version control and roll it out through GitOps alongside the workloads it applies to.

```yaml theme={null}
apiVersion: observability.metoro.io/v1alpha1
kind: MetoroClusterIngestionRule
metadata:
  name: drop-health-check-logs
spec:
  rules:
    - signals: [logs]
      action: Exclude
      match:
        matchExpressions:
          - key: message
            operator: RegexIn
            values: ["GET /healthz", "GET /readyz"]
```

Rules synced from Kubernetes appear in **Settings → Data Ingestion Settings** with a **Managed** badge and are read-only in the UI: Kubernetes is the source of truth, so edit or delete the custom resource instead. Hovering the **Managed by** column shows the source resource and the time of the last sync.

<Warning>
  Kubernetes RBAC is the trust boundary. Anyone who can write
  `MetoroClusterIngestionRule` resources in a cluster controls which telemetry
  leaves that cluster, so restrict write access to them accordingly.
</Warning>

## How it works

* The `MetoroClusterIngestionRule` CRD is installed by the Metoro exporter chart on every monitored cluster, so it is available on both Metoro cloud and on-prem. It ships with `metoro-exporter` chart version **0.476.0** or later (Metoro cloud) and `metoro-exporter-onprem` chart version **11.0.0** or later. On an older chart the CRD does not exist and `kubectl apply` fails with `no matches for kind "MetoroClusterIngestionRule"`. On Metoro cloud, [upgrade the exporter chart](/docs/getting-started/upgrading-metoro) first. On-prem, the CRD is available from the 11.x release line; existing 10.x installations cannot upgrade to 11.x in place, see [Upgrading from 10.x](/docs/on-premises/11.x/upgrades/upgrading-from-10-x) for the supported path.
* Exporters fetch the active filter set about once a minute and enforce it before telemetry egress.
* Rules sync near real time: a new or changed rule takes effect about a minute after you apply it. Telemetry produced before the rule takes effect is ingested normally.

If the exporter is configured with a `METORO_K8S_RESOURCES` allowlist restricting which Kubernetes resources it watches, the list must include `mcingrule` (or another `MetoroClusterIngestionRule` alias); otherwise applied rules are never discovered and never take effect. If the variable is unset, all resources including ingestion rules are watched.

## Scope and semantics

* **Cluster-scoped, per-cluster effect.** A rule only affects telemetry from the cluster where the custom resource is created; there is no cross-cluster field. To apply the same rules in several clusters, apply the same manifest to each of them.
* **Exclude-only.** The only supported `action` is `Exclude`, and it is required: every rule states explicitly that it removes data. Rules declared in Kubernetes cannot include/allow telemetry; include filters remain UI-only.
* **Rules are ORed.** Each entry in `spec.rules` is an independent rule: a record is excluded when *any* single rule matches it. Rules from all sources compose the same way: a record is dropped if any exclude filter matches it, whether it came from the UI or from a `MetoroClusterIngestionRule`.

## Match semantics

Within one rule, a record matches when **every** entry in `match.matchExpressions` matches it (expressions are ANDed). Within a single expression, the `values` are ORed: the attribute matches if it equals (`In`) or matches (`RegexIn`, unanchored [RE2](https://github.com/google/re2/wiki/Syntax)) any of them. To exclude on independent conditions, add separate rules.

<Warning>
  `match` is optional, and **omitting it means the rule matches everything**:
  an `Exclude` rule without a `match` drops *all* records of its signals from
  the cluster. That is the intended way to stop a signal leaving a cluster
  entirely; just make sure it's what you meant.
</Warning>

For `logs` rules a record is a log line. For `traces` rules it is an individual span: each span that matches is dropped, and other spans of the same trace are exported normally. To drop everything a trace contains, make sure the rule's expressions match every span of it.

Trace attribute matching is limited to **string-valued** attributes: a span attribute carrying a non-string OTLP value (for example, an integer `http.status_code` on traces sent from an OpenTelemetry SDK) is read as an empty string, so an `In` expression on its displayed value will not match it.

## Attribute keys

These keys are supported on both CRD families with the same meaning:

| Key            | Signals      | Matches                                                                                                                                  |
| -------------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `service.name` | logs, traces | The service name (`/k8s/<namespace>/<service>`). On traces it matches when either the server **or** the client side of the span matches. |
| `namespace`    | logs, traces | The Kubernetes namespace. On traces: server or client side.                                                                              |
| `log_level`    | logs         | The detected severity, the same values the log UI shows (`debug`, `info`, `warning`, `error`, …).                                        |
| `message`      | logs         | The log body. `In` = exact body match, `RegexIn` = unanchored regex.                                                                     |
| `statusCode`   | traces       | The span status: `STATUS_CODE_ERROR`, or `STATUS_CODE_UNSET` (no error recorded, the status of successful spans).                        |

<Warning>
  `In` compares against the **stored attribute value**, and for `service.name`
  that is the full canonical form `/k8s/<namespace>/<service>` (for example
  `/k8s/production/frontend`), not the short name the Metoro UI displays. An
  `In` expression with the display name (`frontend`) matches nothing, and it
  fails silently. To get the exact value, open a log line or span in Metoro
  and copy it from the `service.name` attribute. Alternatively, use `RegexIn`
  with the short name: the regex is unanchored, so `frontend` matches anywhere
  in the stored value (including any other service whose name contains it, so
  anchor the pattern if that matters).
</Warning>

Any other key is matched verbatim against the record's resource and log/span attributes, for example `http.path`, `http.method` or `net.peer.name` on spans, or a field of your structured logs. To target one side of a span explicitly, use the stored keys `server.service.name`, `client.service.name`, `server.namespace`, `client.namespace`.

<Warning>
  Only put both signals in one rule's `signals` when matching on `namespace`
  or `service.name`. Those two are designed as cross-signal shorthands: on
  logs they match the log's own namespace and service name attributes, and on
  traces they match when either the server or the client side of the span
  matches. Any other attribute key belongs to one signal (`http.path` exists
  on spans, a structured-log field exists on log lines), so in a multi-signal
  rule the expression silently matches nothing for the other signal. For
  those, write a separate rule per signal instead.
</Warning>

<Warning>
  Two keys are rejected at sync time rather than silently never matching:
  `environment` (rules are already bound to their cluster), and the per-signal
  keys used on the wrong signal (`message`/`log_level` on traces, `statusCode`
  on logs).
</Warning>

## Spec reference

| Field                                 | Required                 | Description                                                           |
| ------------------------------------- | ------------------------ | --------------------------------------------------------------------- |
| `spec.rules`                          | yes                      | 1–256 independent rules, ORed.                                        |
| `spec.rules[].signals`                | yes                      | `logs`, `traces`, or both.                                            |
| `spec.rules[].action`                 | yes                      | Only `Exclude` is supported.                                          |
| `spec.rules[].match`                  | no                       | Omit to match everything for the rule's signals.                      |
| `spec.rules[].match.matchExpressions` | yes, when `match` is set | Up to 64 expressions, ANDed. Keys must be unique within a rule.       |
| `...matchExpressions[].key`           | yes                      | Attribute key, up to 256 characters.                                  |
| `...matchExpressions[].operator`      | yes                      | `In` or `RegexIn`.                                                    |
| `...matchExpressions[].values`        | yes                      | 1–256 values, ORed within the expression. Each up to 1024 characters. |

The resource is cluster-scoped, with short name `mcingrule` and category `metoro`:

```bash theme={null}
kubectl get metoroclusteringestionrules
```

`kubectl get mcingrules` lists them.

## Examples

### Silence a namespace and drop probe noise, one resource

```yaml theme={null}
apiVersion: observability.metoro.io/v1alpha1
kind: MetoroClusterIngestionRule
metadata:
  name: quiet-this-cluster
spec:
  rules:
    # Drop everything from dev-scratch, logs and spans alike. One match block
    # covers both signals: namespace matches the log attribute and either
    # side of a span.
    - signals: [logs, traces]
      action: Exclude
      match:
        matchExpressions:
          - key: namespace
            operator: In
            values: [dev-scratch]

    # ORed with the rule above: drop the frontend's probe log lines.
    # Expressions are ANDed: the service AND the body must match.
    - signals: [logs]
      action: Exclude
      match:
        matchExpressions:
          - key: service.name
            operator: In
            values: [/k8s/production/frontend]
          - key: message
            operator: RegexIn
            values: ["^GET /healthz", "^GET /readyz"]
```

### Drop all debug logs from this cluster

```yaml theme={null}
apiVersion: observability.metoro.io/v1alpha1
kind: MetoroClusterIngestionRule
metadata:
  name: drop-debug-logs
spec:
  rules:
    - signals: [logs]
      action: Exclude
      match:
        matchExpressions:
          - key: log_level
            operator: In
            values: [debug]
```

### Drop successful health-check spans

```yaml theme={null}
apiVersion: observability.metoro.io/v1alpha1
kind: MetoroClusterIngestionRule
metadata:
  name: drop-health-check-spans
spec:
  rules:
    - signals: [traces]
      action: Exclude
      match:
        matchExpressions:
          - key: http.path
            operator: RegexIn
            values: ["^/(healthz|readyz)$"]
          - key: statusCode
            operator: In
            values: [STATUS_CODE_UNSET]
```

## Validation and failure modes

* The CRD schema validates rules at `kubectl apply` time: unknown fields, invalid signals or operators, a missing or non-`Exclude` action, duplicate expression keys, `message` on a `traces` rule, and empty `rules` are all rejected by the Kubernetes API server with specific messages.
* The sync validates each rule again before it reaches enforcement, most importantly that every regex compiles as RE2 and that reserved keys are used on the right signal. An invalid resource is skipped and logged; it never partially applies.
* When a resource is skipped as invalid, every previously synced rule of it stays in effect unchanged. You can spot this in the UI: the **Last synced** time on the managed filters stops advancing. Fix the custom resource and it syncs again within about a minute.

## Deleting rules

Delete the custom resource; all of its managed filters are removed and exporters stop applying them within a couple of minutes:

```bash theme={null}
kubectl delete metoroclusteringestionrule drop-health-check-logs
```

Removing a single rule from `spec.rules` (or a signal from a rule's `signals`) removes exactly the corresponding managed filters.

If a cluster stops reporting to Metoro entirely (for example, its exporter is down), its rules are deliberately kept at their last-synced state rather than removed: a disconnected cluster keeps filtering the way it did when it was last seen.
