OpenTelemetry Kubernetes Attributes Processor v1.0: Migration Guide

OpenTelemetry Kubernetes Attributes Processor v1.0: Migration Guide

OpenTelemetry Kubernetes Attributes Processor v1.0: Migration Guide

On September 16, 2026, the OpenTelemetry project declared the k8sattributes processor stable at v1.0.0. For most teams that sounds like good news and a non-event. It is neither. The release switched the processor to the stable Kubernetes semantic conventions by default, and seven attribute names changed. If a dashboard, alert rule, sampling policy or log-routing filter in your estate keys on k8s.pod.labels.team or container.image.tag, it can quietly stop matching the day a Collector image is bumped.

The risk is not the stability promise itself. The risk is that the processor sits in the enrichment path of nearly every Kubernetes telemetry pipeline, so a rename here propagates to every backend, every query and every team that never read the release notes. The good news is that the maintainers shipped a reversible, dual-emission migration path built on two feature gates, and you can run it on your own schedule.

By the end of this guide you will know exactly what changed, how the two gates interact, what to put in your Collector config and RBAC, and how to roll the change through a fleet without breaking a single dashboard.

What this covers: what v1.0.0 promises and what it does not, the full rename table, the feature-gate state machine, before and after configuration, RBAC and memory implications, agent and gateway rollout, and the failure modes to rehearse before production.

Context and Background

The Kubernetes Attributes Processor, k8sattributes in Collector configuration and k8s_attributes in the processors block, is one of the oldest and most widely deployed components in the OpenTelemetry Collector ecosystem. Its job is simple to state. Telemetry leaves an application with little knowledge of where it runs, perhaps an IP address and a service name. The processor watches the Kubernetes API, remembers which pod owns which IP address, and stamps each span, metric data point and log record with resource attributes such as k8s.namespace.name, k8s.pod.name and k8s.deployment.name.

That enrichment is what makes Kubernetes telemetry usable. Without it, a latency spike is a number with no owner. With it, you can group by namespace, join traces to the node that served them, and route logs by team label. We cover how this fits among alternative collection agents in our comparison of the OpenTelemetry Collector, Vector and Fluent Bit, and how enrichment sits inside a unified pipeline in our piece on the OpenTelemetry logs and unified telemetry pipeline.

The path to v1.0.0 was long. According to the OpenTelemetry project’s announcement, the Collector SIG launched a component stability initiative in late 2025, the Kubernetes semantic conventions group began focused stabilization work in November 2025, the conventions reached release candidate in March 2026 and shipped as stable in semantic conventions v1.42.0 in June 2026. The processor reached v1.0.0 on September 16, 2026, available in opentelemetry-collector-contrib, in the opentelemetry-collector-k8s distribution and in custom builds. The project’s own stability table now lists logs, metrics and traces as stable and profiles as development, with the processor’s semantic conventions version recorded as 1.42.0.

Stability in the OpenTelemetry sense is a narrow promise. It means the configuration surface and Go API will not break without a major version bump, and that the component meets bars for testing, benchmarking, documentation and telemetry. It does not promise that the attribute names it emits will never change. Attribute names are governed by the semantic conventions, and those had been unstable until June. The processor v1.0.0 therefore carries one deliberate, documented break at the data layer: it moved from the old unstable schema, which this article calls v0, to the stable schema, v1.

Everything below is drawn from the processor README in the contrib repository and the project’s announcement, which are the primary sources. The announcement itself is short and points to the README for the migration specifics, so this guide concentrates on the details the announcement leaves out.

What v1.0.0 Actually Changes

The short answer: v1.0.0 keeps the processor’s configuration surface stable but changes the attribute names it emits by default. Two feature gates, both enabled by default, make the processor emit only the stable v1 Kubernetes names. You can switch back to v0 or emit both schemas at once, but only temporarily, because the gates remain in beta only for a period.

The seven renames

The README lists the breaking differences between the old and new schemas exactly. There are seven, and they fall into two families: a plural-to-singular change in label and annotation keys, and a change from a single tag to a list of tags for container images.

v0 attribute (old) v1 attribute (new) Family
container.image.tag container.image.tags Scalar string to list
k8s.pod.labels.<key> k8s.pod.label.<key> Plural to singular
k8s.pod.annotations.<key> k8s.pod.annotation.<key> Plural to singular
k8s.node.labels.<key> k8s.node.label.<key> Plural to singular
k8s.node.annotations.<key> k8s.node.annotation.<key> Plural to singular
k8s.namespace.labels.<key> k8s.namespace.label.<key> Plural to singular
k8s.namespace.annotations.<key> k8s.namespace.annotation.<key> Plural to singular

The plural-to-singular family looks cosmetic and is the more dangerous of the two, because any query that references one of these keys by literal name simply returns nothing after the change. There is no error. A Grafana panel that groups by k8s.pod.labels.team renders an empty graph, and an alert whose expression filters on that label evaluates against zero series. The data is still arriving, only under a different key.

The image tag change is subtler. In v0 the value was a string such as 0.112.0. In v1 it is a list, and the upstream metadata table describes container.image.tags as a slice that defaults to latest when the image reference carries no tag, unless a digest is present in the image path. Any downstream processor or backend that treated the value as a scalar string will now meet an array. Comparisons, regex matching and index mappings written for a keyword field can fail or silently coerce.

What did not change

Everything that you configure stays as it was. The extract.metadata list still takes the same names, pod_association still works the same way, filter options are unchanged, and the labels and annotations extraction rules still use tag_name, key and from. The default metadata set is unchanged: k8s.namespace.name, k8s.pod.name, k8s.pod.uid, k8s.pod.start_time, k8s.deployment.name and k8s.node.name. Those six names were already stable in spirit and are identical in both schemas, which is why a deployment that only extracts the defaults sees no change at all.

One caveat deserves flagging because it follows from the table rather than being spelled out in the README. When you extract labels with an explicit tag_name, you choose the output attribute name yourself, so that rule is not affected by the rename. The rename bites when you omit tag_name and rely on the processor’s generated name, which uses the k8s.pod.labels.<key> pattern in v0. Treat this as an inference from the documented naming, and verify it against your own output before you depend on it.

k8sattributes processor data flow from Kubernetes API informers to enriched resource attributes

Figure 1: How the k8sattributes processor builds its cache from API informers, associates incoming telemetry to a pod, extracts metadata and applies the schema gates last.

The figure shows where the schema decision sits. The processor first builds a cache from Kubernetes API watches, then associates each incoming resource with a pod using the configured rules, then extracts the requested metadata. Only at the final step do the two feature gates decide whether the output keys use v0 names, v1 names or both. That placement matters for the rollout: the gates change names, not lookups, so flipping them neither adds API load nor changes which pods match.

The Feature-Gate State Machine

The short answer: the processor exposes processor.k8sattributes.EmitV1K8sConventions and processor.k8sattributes.DontEmitV0K8sConventions. In v1.0.0 both are enabled, so only v1 names are emitted. Disable the second to emit both schemas, disable both to return to v0. Disabling only EmitV1 is an invalid combination and fails at startup.

Two gates, four states

The processor follows the Collector’s semantic-convention migration RFC. That RFC defines a pair of gates per component and area, and a truth table for how they combine. Translated to this processor:

EmitV1K8sConventions DontEmitV0K8sConventions Result
Disabled Disabled v0 names only, a full rollback
Disabled Enabled Startup error, since nothing would be emitted
Enabled Disabled Dual emission, v0 and v1 together
Enabled Enabled v1 names only, the default in v1.0.0

Feature gate decision flow for the k8sattributes processor EmitV1 and DontEmitV0 gates

Figure 2: The four gate combinations and the behavior each produces, including the one invalid combination.

The figure is the whole mental model. Read it as two independent questions. The first gate asks whether the new names should appear. The second asks whether the old names should disappear. Dual emission is the state where the first answer is yes and the second is no, and it is the state the README explicitly advises for the migration period.

The commands

The README gives the dual-emission flag verbatim:

--feature-gates=-processor.k8sattributes.DontEmitV0K8sConventions,processor.k8sattributes.EmitV1K8sConventions

The leading minus disables a gate and a bare name enables it. The following three forms summarize the useful states. The third is derived from the RFC truth table above rather than quoted from the README, so test it in a non-production cluster first.

# State 1: dual emission (documented in the README, recommended for migration)
--feature-gates=-processor.k8sattributes.DontEmitV0K8sConventions,processor.k8sattributes.EmitV1K8sConventions

# State 2: v1 only (this is the v1.0.0 default, no flag needed)
--feature-gates=processor.k8sattributes.DontEmitV0K8sConventions,processor.k8sattributes.EmitV1K8sConventions

# State 3: full rollback to v0 (derived from the RFC table)
--feature-gates=-processor.k8sattributes.DontEmitV0K8sConventions,-processor.k8sattributes.EmitV1K8sConventions

In Kubernetes you pass these through the Collector container arguments. With the OpenTelemetry Operator or a Helm chart that exposes args, it looks like this:

containers:
  - name: otel-collector
    image: otel/opentelemetry-collector-contrib:<version-with-k8sattributes-v1>
    args:
      - --config=/conf/collector.yaml
      - --feature-gates=-processor.k8sattributes.DontEmitV0K8sConventions,processor.k8sattributes.EmitV1K8sConventions

Replace the image tag placeholder with the release you have validated. This guide does not pin a Collector version because the README documents the processor, not a specific Collector image tag, and you should pin whichever release your platform team has qualified.

How long do the gates last?

The README says the gates “will remain in beta for some period in which the component stays at v1.x.x“, allowing users to switch back to the old schema if needed. It does not publish a date. The generic RFC the processor follows describes the intended lifecycle: after the gates reach beta, they are promoted to stable after four minor Collector releases, at which point only the new conventions are available, and removed after a further four minor releases. Whether this processor follows that exact cadence is the maintainers’ call, so read the schedule as the stated design intent of the RFC and not as a commitment for this component.

The practical consequence is a deadline you do not control. Dual emission is a bridge, not a destination. Plan to finish the migration within a few Collector release cycles and watch the processor changelog for the gate promotion.

The ecosystem pattern

This is not unique to Kubernetes. The semantic conventions specification defines a OTEL_SEMCONV_STABILITY_OPT_IN environment variable for SDK instrumentation, with values such as k8s to emit only stable conventions and k8s/dup to emit both. The Collector deliberately did not use an environment variable, because Collector users expect experimental behavior to be toggled with feature gates and expect to be able to roll back after an upgrade, which an environment variable does not support. Understanding that design choice helps you predict the behavior of other Collector components as they graduate to stable conventions: expect the same two-gate pattern.

Migration Walk-Through: Config, RBAC, and Rollout

This section is the hands-on part. It uses only options documented in the processor README, and every YAML block below is either quoted from it or assembled from documented fields.

Step 1: Find every consumer of the old keys

The processor change is free. The cost lives downstream. Before touching a Collector, inventory every place that references one of the seven old names. Search these locations at minimum:

  • Dashboard JSON in Grafana or your vendor’s equivalent, including template variables.
  • Alert and recording rules, which are the most dangerous because a broken matcher fails silent.
  • Downstream Collector processors in the same pipeline: filter, transform, routing, attributes, resource and tail-sampling policies that match on attribute keys.
  • Log parsing and index mappings in your storage layer, especially for container.image.tag.
  • Cost and chargeback reports that group by a team label.

A quick way to scope the problem is to grep your repositories for the literal strings.

grep -rnE 'k8s\.(pod|node|namespace)\.(labels|annotations)\.|container\.image\.tag([^s]|$)' \
  dashboards/ alerts/ collector-configs/ helm-values/

This catches literal references. It will not catch keys assembled at runtime, nor those inside a vendor UI that is not in version control, so also ask each backend for its saved queries.

Step 2: Your configuration before and after

Because the processor’s option surface is unchanged, a good deal of your configuration does not need to be edited at all. The interesting edits are where you extract labels or annotations without a tag_name, and where you extract container.image.tag.

Here is a typical v0-era agent configuration. Treat it as a representative baseline built from documented options.

processors:
  k8s_attributes:
    filter:
      node_from_env_var: KUBE_NODE_NAME
    extract:
      metadata:
        - k8s.namespace.name
        - k8s.pod.name
        - k8s.pod.uid
        - k8s.deployment.name
        - k8s.node.name
        - k8s.container.name
        - container.image.name
        - container.image.tag
      labels:
        - key: team
          from: pod
        - tag_name: app.label.component
          key: app.kubernetes.io/component
          from: pod
      otel_annotations: true
    pod_association:
      - sources:
          - from: resource_attribute
            name: k8s.pod.uid
      - sources:
          - from: connection

The first label rule has no tag_name, so its output name is generated. Under v0 that is k8s.pod.labels.team, and under v1 it becomes k8s.pod.label.team. The second rule names its output app.label.component, which is stable across the migration.

The after configuration is almost the same, with two intentional changes: the image tag request is updated to the plural, and implicit names are made explicit so they stop depending on a schema.

processors:
  k8s_attributes:
    filter:
      node_from_env_var: KUBE_NODE_NAME
    extract:
      metadata:
        - k8s.namespace.name
        - k8s.pod.name
        - k8s.pod.uid
        - k8s.deployment.name
        - k8s.node.name
        - k8s.container.name
        - container.image.name
        - container.image.tags
      labels:
        - tag_name: team
          key: team
          from: pod
        - tag_name: app.label.component
          key: app.kubernetes.io/component
          from: pod
      otel_annotations: true
    pod_association:
      - sources:
          - from: resource_attribute
            name: k8s.pod.uid
      - sources:
          - from: connection

Two points about this edit. First, the README’s metadata table lists both container.image.tag and container.image.tags as available, with the old one marked deprecated, so listing the plural name is the forward-compatible choice. Verify on a test cluster how your build behaves when you list one name while the other schema is emitted. Second, switching an implicit label name to an explicit tag_name is a design decision, not a requirement. It decouples your attribute contract from the processor’s schema and means future convention changes cannot rename your keys. The trade-off is that you must pick and document the names yourself.

Notice that container.id is deliberately absent. The README notes that to extract container-level attributes in a multi-container pod, the incoming telemetry must carry either container.id or k8s.container.name, and that container.id also needs k8s.container.restart_count to be reliable when used that way. Adding it by habit costs lookups and gains nothing unless your SDK sets those attributes.

Step 3: Enable dual emission and ship

With config in hand, roll the Collector to the v1.0.0-era build with the dual-emission flag from the previous section. During this phase each resource carries both the old and the new names, so existing dashboards keep working while you build and test replacements.

Dual emission has a cost you should budget for. Every labeled resource carries extra attributes, so payload size and downstream cardinality grow in proportion to the number of labels and annotations you extract. The upstream documentation does not publish a measured overhead, and the size of the increase depends entirely on your extraction rules, so measure it in staging by comparing exported bytes per second before and after. If you extract a lot of labels through a key_regex rule, the doubling can be material, and it is worth narrowing the regex before you start.

Step 4: RBAC, which has not changed but deserves an audit

The v1.0.0 release does not change permissions, and the announcement says nothing about RBAC. This is still a good moment to audit them, because the permissions you need depend on which metadata you extract, and teams routinely over-grant. The README spells out the rules:

  • Pods and namespaces need get, watch and list for cluster-wide enrichment.
  • ReplicaSets are needed when you extract k8s.deployment.uid, or labels and annotations with from: deployment or from: replicaset.
  • Nodes are needed for k8s.node.uid or node-sourced labels and annotations.
  • Jobs are needed for k8s.cronjob.uid, or label and annotation extraction with from: job or from: cronjob, and CronJobs additionally for from: cronjob.
  • With only k8s.cronjob.name, no Jobs permission is needed, because the name is derived with a heuristic.

The README’s cluster-scoped example grants the broadest set. It looks like this.

apiVersion: v1
kind: ServiceAccount
metadata:
  name: collector
  namespace: observability
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: otel-collector
rules:
- apiGroups: [""]
  resources: ["pods", "namespaces", "nodes"]
  verbs: ["get", "watch", "list"]
- apiGroups: ["apps"]
  resources: ["replicasets", "deployments", "statefulsets", "daemonsets"]
  verbs: ["get", "list", "watch"]
- apiGroups: ["batch"]
  resources: ["jobs", "cronjobs"]
  verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: otel-collector
subjects:
- kind: ServiceAccount
  name: collector
  namespace: observability
roleRef:
  kind: ClusterRole
  name: otel-collector
  apiGroup: rbac.authorization.k8s.io

The README example also includes an extensions API group entry for ReplicaSets, which is a legacy group, and it uses the placeholder <OTEL_COL_NAMESPACE> where this example uses observability. If you only extract the six default attributes, you can trim this to pods, and add namespaces if you extract namespace labels. A leaner ClusterRole is a smaller blast radius if the Collector pod is ever compromised.

For multi-tenant clusters where a team runs its own Collector, the README supports a namespace-scoped Role and RoleBinding combined with filter.namespace. The limit matters: with only a Role, the processor cannot read cluster-scoped objects, so it cannot query node or namespace labels and annotations, and it cannot set k8s.cluster.uid, which is derived from the kube-system namespace.

Step 5: Cut over to v1 only

When your dashboards, alerts and pipelines have been rewritten, run a verification window long enough to cover your slowest consumer, whether that is a weekly report or a monthly chargeback job. During the window, confirm that queries for the old keys return no hits from anything but the Collector’s own dual-emitted copy. Then re-enable DontEmitV0K8sConventions, which returns the fleet to the v1.0.0 default, and remove the dual-emission argument from your manifests.

Migration timeline for the k8sattributes processor from inventory to dual emission to v1 only

Figure 3: The rollout sequence, from inventory through dual emission and dashboard rewrites to the final switch to v1 names only.

The sequence is deliberately boring. The only moment of risk is the final step, and it is reversible with a one-flag change because the gates stay available while the component remains at v1.x.x. Keep the rollback flag in your runbook until the gates are promoted.

Step 6: Agent and gateway, and the pass-through trap

Most production estates run an agent DaemonSet that forwards to a gateway Deployment, and the processor behaves differently in each place. Understanding this matters for the migration because you must apply gates consistently across both tiers, or you will get mixed schemas in one backend.

In an agent, the processor detects the IP of the sender from the connection and queries the API for pods on its own node. The README is emphatic that agents should apply a discovery filter, using filter.node_from_env_var with the downward API, because without it each agent watches every pod in the cluster. The downward API snippet is in the README.

env:
  - name: KUBE_NODE_NAME
    valueFrom:
      fieldRef:
        apiVersion: v1
        fieldPath: spec.nodeName

In a gateway, the connection IP belongs to the agent and not the pod, so enrichment cannot work from the connection alone. The README’s recommended pattern is to run the agents in pass-through mode, which only adds the pod IP as a resource attribute and makes no Kubernetes API calls, and to let the gateway associate by that IP and extract the metadata.

# agent
processors:
  k8s_attributes:
    passthrough: true

# gateway
processors:
  k8s_attributes:
    extract:
      metadata:
        - k8s.namespace.name
        - k8s.pod.name
        - k8s.deployment.name
        - k8s.node.name
    pod_association:
      - sources:
          - from: resource_attribute
            name: k8s.pod.ip

Agent and gateway topology for the k8sattributes processor with passthrough agents

Figure 4: A two-tier topology. Pass-through agents add the pod IP, and the gateway does the enrichment, so only the gateway tier needs the schema gates and the broader RBAC.

This topology has a migration advantage that is easy to miss. Pass-through agents extract no metadata, so the schema gates have no effect on them. In this design only the gateway tier needs the feature-gate change, which shrinks the blast radius of the migration from every node to a handful of replicas. If instead you enrich at the agent, you must roll the gates across the whole DaemonSet.

Operating the Processor at Scale: Memory, Association, and Startup

Migration is a good moment to revisit the operational settings, because a Collector restart is already on the calendar. The v1.0.0 README documents several knobs that are easy to overlook, and the README’s own warning is blunt: the processor caches Kubernetes metadata and therefore consumes more memory than most processors, a cost that compounds when you do not restrict it to the local node.

Where the memory goes

The cache holds one entry per observed pod, and the size of each entry grows with what you extract. The upstream production guide lists the drivers: how many pods are monitored, how many metadata fields are extracted, how many label and annotation rules you run, and whether you pull workload metadata for Deployments, StatefulSets, DaemonSets, Jobs and CronJobs. The README states that extracting from those workload kinds is disabled by default and that enabling it adds memory cost.

The deployment-name behavior is a good example of a hidden trade-off. By default the processor derives k8s.deployment.name from the ReplicaSet name by trimming the pod-template-hash suffix, and it does this without watching ReplicaSets at all. As soon as you add k8s.deployment.uid, or extract labels or annotations from: deployment, the ReplicaSet and Deployment informers start and the name is resolved from the API. You gain accuracy, because very long Deployment names between 247 and 253 characters can be truncated in the ReplicaSet name, and you pay in memory and RBAC. CronJob names follow the same pattern with a Job informer. One detail trips people up: you must still list k8s.deployment.name in extract.metadata for the name to be emitted, even though no Deployment access is needed.

The upstream project publishes load-test results for the processor, run against clusters simulated with KWOK, and links memory and CPU charts for a 5,000-workload cluster on its benchmarks page. This article does not quote figures from that page, because they change with each run; read the charts for the Collector release you plan to deploy and compare against your own pod counts.

Cache refresh and churn

Three documented settings influence behavior under churn and at startup.

Option Default What it does When to change it
watch_sync_period 5m Informer resync interval Set to 0s in very large clusters, since the README says watch events already push changes and resyncs cause CPU spikes and garbage collection
pod_delete_grace_period 120s Keeps a deleted pod’s metadata in cache before eviction Raise if late-arriving telemetry from terminated pods loses its enrichment
wait_for_metadata false Blocks readiness until metadata is synced Enable if an empty cache on startup causes un-enriched data you cannot tolerate

The wait_for_metadata setting has a sharp edge that the README states plainly. When it is enabled, Collector startup blocks until metadata syncs, and if the timeout, 10 seconds by default through wait_for_metadata_timeout, is reached the processor fails to start and the Collector exits. That turns a degraded start into a crash loop. It can be the right trade when un-enriched telemetry is worse than a delayed start, but pair it with a realistic timeout and a readiness probe so a slow API server does not take your telemetry path down during an upgrade.

Two more options deal with API client behavior. kube_api_qps defaults to 5 and kube_api_burst to 10, and the README suggests raising them if you see client-side throttling warnings. That is most relevant when a gateway tier restarts across many replicas at once and each performs a full list of every pod.

Association, the other half of correctness

Dual-emission safety depends on associations working, so verify them before and after. The processor evaluates pod_association rules in order and uses the first that matches, with each rule allowing up to four sources, all of which must match. Rules must be unique, and duplicates are rejected at validation. Two behaviors matter operationally.

First, if a rule’s source attribute is present but does not match any pod, the association fails and later rules are not evaluated. That is why ordering rules from most specific to most general matters, and why a stale k8s.pod.ip attribute set by an upstream proxy can defeat a perfectly good connection-based fallback.

Second, the connection source needs the original connection context, which batching and tail sampling remove. The README says the processor must therefore appear before any batching or tail-sampling stage. When teams add batch early in a pipeline during a performance tuning pass, enrichment can silently degrade. A migration is a convenient time to audit processor ordering.

The component also emits its own telemetry. The generated documentation lists a counter named otelcol.k8s.pod.association, with a status attribute of success or error and a pod_identifier attribute that records which sources were used without exposing actual values, plus counters for watcher add, update and delete events. That metric is marked as development stability, so treat the name as subject to change, but it is the most direct way to see whether associations are failing. Chart the error share per signal during your dual-emission phase, and alert on a sustained increase.

Trade-offs, Gotchas, and What Goes Wrong

Stability labels reduce risk but do not remove it. These are the failure modes worth rehearsing.

Silent breakage downstream. The most likely incident is not a Collector failure at all. It is a panel, alert or routing rule that goes quiet because a key was renamed. Dual emission exists to prevent exactly this, so the highest-value habit is to resist the temptation to skip it because the upgrade “worked” in staging. Staging seldom contains the quarterly report or the on-call alert that fires once a month.

Type changes in the image tag. Moving from a string to a list can break typed storage. If your backend maps container.image.tag as a keyword and receives a list under the new name, mapping behavior varies by product. Run a representative batch through each backend in staging, and confirm that array-valued resource attributes are accepted and queryable.

Mixed fleets during rollout. If half of your gateway replicas emit v1 only and the rest emit v0, a single backend sees both schemas for the same workload, and aggregations split. Roll gates per tier and complete a tier before moving on. Use one configuration source, a single Helm values file for example, rather than per-cluster overrides.

Cardinality growth in dual mode. Each extracted label produces two attributes while both schemas are on. If a label rule uses a broad key_regex such as (.*), the README itself warns that extracting everything can create many attributes. Doubling a high-cardinality set can push a metrics backend over a series limit and trigger cost or throttling. Narrow the rule before enabling dual emission.

Host networking and sidecars. The README lists two cases where the processor does not work properly. Pods in host network mode cannot be identified by IP, so enrichment for them requires association rules based on something other than an IP attribute. Running the processor as a sidecar does not support detecting containers in the same pod, and the maintainers suggest using the downward API to inject values directly instead. If you rely on either, migration does not change this, but do not assume v1.0.0 fixed it.

A deadline you do not control. Because the gates stay in beta only for a period, a team that stays in dual emission indefinitely will eventually find the old schema removed in an upgrade. Put the gate promotion on your upgrade checklist and set a date for the cutover.

Not every attribute is guaranteed. The README says that not all attributes are guaranteed to be added, and that only names from the metadata section should be used in pod_association sources, because empty values are ignored. Pipelines that assume a key is always present should handle its absence.

Practical Recommendations

Treat this as a data-contract migration with a Collector flag on top. The work is mostly in consumers, and the Collector part is a one-line change you can reverse.

Start with the inventory. A thorough grep and a review of saved queries in each backend will tell you whether you are exposed at all. Teams that extract only default metadata and use explicit tag_name values for everything else may find they need no changes, and the upgrade is then genuinely a non-event.

Where you are exposed, prefer explicit tag_name values going forward. They give you ownership of the contract and make future convention changes invisible. Roll dual emission through the gateway tier first if you use pass-through agents, hold it for a full reporting cycle, and then cut to v1 only. Keep the rollback flag documented until the gates are promoted to a later stage.

Use the migration to tighten operations as well: enforce node filtering on agents, trim RBAC to the attributes you really extract, set watch_sync_period deliberately for large clusters, and chart the association metric.

A short checklist:

  • [ ] Inventory every consumer of the seven old attribute names, including vendor-side saved queries.
  • [ ] Replace implicit label and annotation names with explicit tag_name values.
  • [ ] Update the image request from container.image.tag to container.image.tags and test array handling in each backend.
  • [ ] Upgrade with -processor.k8sattributes.DontEmitV0K8sConventions,processor.k8sattributes.EmitV1K8sConventions.
  • [ ] Measure payload size and series growth in dual mode against your staging baseline.
  • [ ] Rewrite dashboards, alerts, routing and sampling rules to the v1 names.
  • [ ] Hold dual emission for one complete reporting cycle and confirm no hits on old keys.
  • [ ] Re-enable DontEmitV0K8sConventions, then remove the override from manifests.
  • [ ] Audit RBAC, node filtering and processor ordering while you are there.
  • [ ] Watch the processor changelog for gate promotion and set a calendar date for completion.

If your pipeline also runs Grafana’s distribution of the Collector, the same processor and gates apply, since it is an upstream component. Our Grafana Alloy and OpenTelemetry Collector tutorial covers how Alloy exposes Collector components, and you should confirm which processor version your Alloy release bundles before assuming v1.0.0 behavior. For a broader view of whether metadata enrichment at the Collector is even the right layer, our eBPF Kubernetes observability decision record discusses collecting context at the kernel layer instead.

Frequently Asked Questions

What is the k8sattributes processor?

The k8sattributes processor is an OpenTelemetry Collector component that watches the Kubernetes API, keeps a cache of pod metadata, and adds that metadata to spans, metrics and logs as resource attributes. Typical attributes are the namespace, pod name, pod UID, deployment name and node name. It associates telemetry to a pod using the connection IP address or resource attributes, so your backend can group and filter by Kubernetes context.

What broke in k8sattributes v1.0.0?

Seven attribute names changed when the processor moved to the stable Kubernetes semantic conventions. container.image.tag became container.image.tags, and the plural label and annotation keys for pods, nodes and namespaces became singular, for example k8s.pod.labels.<key> to k8s.pod.label.<key>. The configuration options themselves did not change. Queries and rules keyed on the old names stop matching unless you enable dual emission.

How do I keep the old attribute names after upgrading?

Disable the processor.k8sattributes.EmitV1K8sConventions and processor.k8sattributes.DontEmitV0K8sConventions gates to return to v0 names. To emit both schemas during migration, which the README recommends, disable DontEmitV0K8sConventions and enable EmitV1K8sConventions. Disabling only the V1 gate while the other stays enabled is an error at startup under the RFC’s rules. The gates stay in beta only for a period, so treat this as temporary.

Is dual emission safe for production?

It is the maintainers’ recommended migration path, and it is reversible with a flag. The cost is that each extracted label or annotation appears under two names, which increases payload size and, for metrics backends, series cardinality. Measure the growth in staging, narrow broad key_regex rules first, and keep the dual phase as short as your reporting cycle allows.

Do I need new RBAC permissions for v1.0.0?

The announcement documents no RBAC changes. Permissions depend on which metadata you extract: pods and namespaces for the basics, ReplicaSets for deployment UIDs and workload labels, nodes for node UIDs, and Jobs and CronJobs for CronJob UIDs or labels. Use the migration to trim any excess, and check the README’s rules for each attribute you enable.

Which Collector distributions include the processor?

According to the announcement and README, the processor is available in opentelemetry-collector-contrib, in the opentelemetry-collector-k8s distribution and in custom builds. Check your vendor or Helm chart for the exact release that bundles v1.0.0 before planning the upgrade, because distributions release on their own cadence.

Further Reading

By Riju — about

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *