Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions .chloggen/k8sattributes-semconv-gates.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Use this changelog template to create an entry for release notes.

# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
change_type: breaking

# The name of the component, or a single word describing the area of concern, (e.g. receiver/filelog)
component: processor/k8sattributes

# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
note: Introduce semantic conventions compliant feature gate pair for k8sattributes processor

# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists.
issues: [44693]

# (Optional) One or more lines of additional information to render under the primary note.
# These lines will be padded with 2 spaces and then inserted directly into the document.
# Use pipe (|) for multiline entries.
subtext: |
- Added `processor.k8sattributes.EmitV1K8sConventions` feature gate to enable stable semantic convention attributes (singular form: `k8s.<workload>.label.<key>` and `k8s.<workload>.annotation.<key>`)
- Added `processor.k8sattributes.DontEmitV0K8sConventions` feature gate to disable legacy non-compliant attributes (plural form: `k8s.<workload>.labels.<key>` and `k8s.<workload>.annotations.<key>`)
- Both feature gates are in `alpha` stage and disabled by default
- The processor now validates that legacy attributes cannot be disabled without enabling stable attributes
- Deprecated `k8sattr.labelsAnnotationsSingular.allow` feature gate in favor of the new semconv-compliant gates (will be removed in v0.150.0)
Comment thread
ChrsMark marked this conversation as resolved.
- During migration period, both legacy and stable attributes can coexist when `EmitV1K8sConventions` is enabled but `DontEmitV0K8sConventions` is not

# If your change doesn't affect end users or the exported elements of any package,
# you should instead start your pull request title with [chore] or use the "Skip Changelog" label.
# Optional: The change log or logs in which this entry should be included.
# e.g. '[user]' or '[user, api]'
# Include 'user' if the change is relevant to end users.
# Include 'api' if there is a change to a library API.
# Default: '[user]'
change_logs: []
4 changes: 2 additions & 2 deletions processor/k8sattributesprocessor/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,8 +175,8 @@ type ExtractConfig struct {
type FieldExtractConfig struct {
// TagName represents the name of the resource attribute that will be added to logs, metrics or spans.
// When not specified, a default tag name will be used of the format:
// - k8s.pod.annotations.<annotation key> (or k8s.pod.annotation.<annotation key> when k8sattr.labelsAnnotationsSingular.allow is enabled)
// - k8s.pod.labels.<label key> (or k8s.pod.label.<label key> when k8sattr.labelsAnnotationsSingular.allow is enabled)
// - k8s.pod.annotations.<annotation key> (or k8s.pod.annotation.<annotation key> when processor.k8sattributes.EmitV1K8sConventions is enabled)
// - k8s.pod.labels.<label key> (or k8s.pod.label.<label key> when processor.k8sattributes.EmitV1K8sConventions is enabled)
// For example, if tag_name is not specified and the key is git_sha,
// then the attribute name will be `k8s.pod.annotations.git_sha` (or `k8s.pod.annotation.git_sha` with the feature gate).
// When key_regex is present, tag_name supports back reference to both named capturing and positioned capturing.
Expand Down
4 changes: 3 additions & 1 deletion processor/k8sattributesprocessor/documentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,8 @@ This component has the following feature gates:

| Feature Gate | Stage | Description | From Version | To Version | Reference |
| ------------ | ----- | ----------- | ------------ | ---------- | --------- |
| `k8sattr.labelsAnnotationsSingular.allow` | alpha | When enabled, default k8s label and annotation resource attribute keys will be singular, instead of plural | v0.125.0 | N/A | [Link](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/39774) |
| `k8sattr.labelsAnnotationsSingular.allow` | deprecated | When enabled, default k8s label and annotation resource attribute keys will be singular, instead of plural | v0.125.0 | v0.145.0 | [Link](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/39774) |
| `processor.k8sattributes.DontEmitV0K8sConventions` | alpha | When enabled, semconv legacy attributes are disabled. | v0.145.0 | N/A | [Link](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/44589) |
| `processor.k8sattributes.EmitV1K8sConventions` | alpha | When enabled, semconv stable attributes are enabled. | v0.145.0 | N/A | [Link](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/44589) |

For more information about feature gates, see the [Feature Gates](https://github.com/open-telemetry/opentelemetry-collector/blob/main/featuregate/README.md) documentation.
76 changes: 43 additions & 33 deletions processor/k8sattributesprocessor/internal/kube/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -918,18 +918,25 @@ func (c *WatchClient) extractPodAttributes(pod *api_v1.Pod) map[string]string {
}
}

formatterLabel := K8sPodLabelsKey
if metadata.K8sattrLabelsAnnotationsSingularAllowFeatureGate.IsEnabled() {
formatterLabel = K8sPodLabelKey
}
enableStable := metadata.ProcessorK8sattributesEmitV1K8sConventionsFeatureGate.IsEnabled()
disableLegacy := metadata.ProcessorK8sattributesDontEmitV0K8sConventionsFeatureGate.IsEnabled()

for _, r := range c.Rules.Labels {
r.extractFromPodMetadata(pod.Labels, tags, formatterLabel)
if !disableLegacy {
r.extractFromPodMetadata(pod.Labels, tags, K8sPodLabelsKey)
}
if enableStable {
r.extractFromPodMetadata(pod.Labels, tags, K8sPodLabelKey)
}
}

formatterAnnotation := K8sPodAnnotationsKey
if metadata.K8sattrLabelsAnnotationsSingularAllowFeatureGate.IsEnabled() {
formatterAnnotation = K8sPodAnnotationKey
for _, r := range c.Rules.Annotations {
if !disableLegacy {
r.extractFromPodMetadata(pod.Annotations, tags, K8sPodAnnotationsKey)
}
if enableStable {
r.extractFromPodMetadata(pod.Annotations, tags, K8sPodAnnotationKey)
}
}

if c.Rules.ServiceName {
Expand All @@ -942,9 +949,6 @@ func (c *WatchClient) extractPodAttributes(pod *api_v1.Pod) map[string]string {
copyLabel(pod, tags, "app.kubernetes.io/version", conventions.ServiceVersionKey)
}

for _, r := range c.Rules.Annotations {
r.extractFromPodMetadata(pod.Annotations, tags, formatterAnnotation)
}
return tags
}

Expand Down Expand Up @@ -1163,22 +1167,25 @@ func (c *WatchClient) extractPodContainersAttributes(pod *api_v1.Pod) PodContain
func (c *WatchClient) extractNamespaceAttributes(namespace *api_v1.Namespace) map[string]string {
tags := map[string]string{}

formatterLabel := K8sNamespaceLabelsKey
if metadata.K8sattrLabelsAnnotationsSingularAllowFeatureGate.IsEnabled() {
formatterLabel = K8sNamespaceLabelKey
}
enableStable := metadata.ProcessorK8sattributesEmitV1K8sConventionsFeatureGate.IsEnabled()
disableLegacy := metadata.ProcessorK8sattributesDontEmitV0K8sConventionsFeatureGate.IsEnabled()

for _, r := range c.Rules.Labels {
r.extractFromNamespaceMetadata(namespace.Labels, tags, formatterLabel)
}

formatterAnnotation := K8sNamespaceAnnotationsKey
if metadata.K8sattrLabelsAnnotationsSingularAllowFeatureGate.IsEnabled() {
formatterAnnotation = K8sNamespaceAnnotationKey
if !disableLegacy {
r.extractFromNamespaceMetadata(namespace.Labels, tags, K8sNamespaceLabelsKey)
}
if enableStable {
r.extractFromNamespaceMetadata(namespace.Labels, tags, K8sNamespaceLabelKey)
}
}

for _, r := range c.Rules.Annotations {
r.extractFromNamespaceMetadata(namespace.Annotations, tags, formatterAnnotation)
if !disableLegacy {
r.extractFromNamespaceMetadata(namespace.Annotations, tags, K8sNamespaceAnnotationsKey)
}
if enableStable {
r.extractFromNamespaceMetadata(namespace.Annotations, tags, K8sNamespaceAnnotationKey)
}
}

return tags
Expand All @@ -1187,22 +1194,25 @@ func (c *WatchClient) extractNamespaceAttributes(namespace *api_v1.Namespace) ma
func (c *WatchClient) extractNodeAttributes(node *api_v1.Node) map[string]string {
tags := map[string]string{}

formatterLabel := K8sNodeLabelsKey
if metadata.K8sattrLabelsAnnotationsSingularAllowFeatureGate.IsEnabled() {
formatterLabel = K8sNodeLabelKey
}
enableStable := metadata.ProcessorK8sattributesEmitV1K8sConventionsFeatureGate.IsEnabled()
disableLegacy := metadata.ProcessorK8sattributesDontEmitV0K8sConventionsFeatureGate.IsEnabled()

for _, r := range c.Rules.Labels {
r.extractFromNodeMetadata(node.Labels, tags, formatterLabel)
}

formatterAnnotation := K8sNodeAnnotationsKey
if metadata.K8sattrLabelsAnnotationsSingularAllowFeatureGate.IsEnabled() {
formatterAnnotation = K8sNodeAnnotationKey
if !disableLegacy {
r.extractFromNodeMetadata(node.Labels, tags, K8sNodeLabelsKey)
}
if enableStable {
Comment thread
ChrsMark marked this conversation as resolved.
r.extractFromNodeMetadata(node.Labels, tags, K8sNodeLabelKey)
}
}

for _, r := range c.Rules.Annotations {
r.extractFromNodeMetadata(node.Annotations, tags, formatterAnnotation)
if !disableLegacy {
r.extractFromNodeMetadata(node.Annotations, tags, K8sNodeAnnotationsKey)
}
if enableStable {
r.extractFromNodeMetadata(node.Annotations, tags, K8sNodeAnnotationKey)
}
}
return tags
}
Expand Down
18 changes: 12 additions & 6 deletions processor/k8sattributesprocessor/internal/kube/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1074,9 +1074,11 @@ func TestExtractionRules(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if tc.singularFeatureGate {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.K8sattrLabelsAnnotationsSingularAllowFeatureGate.ID(), true))
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.ProcessorK8sattributesEmitV1K8sConventionsFeatureGate.ID(), true))
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.ProcessorK8sattributesDontEmitV0K8sConventionsFeatureGate.ID(), true))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.K8sattrLabelsAnnotationsSingularAllowFeatureGate.ID(), false))
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.ProcessorK8sattributesEmitV1K8sConventionsFeatureGate.ID(), false))
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.ProcessorK8sattributesDontEmitV0K8sConventionsFeatureGate.ID(), false))
}()
}

Expand Down Expand Up @@ -1378,9 +1380,11 @@ func TestNamespaceExtractionRules(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if tc.singularFeatureGate {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.K8sattrLabelsAnnotationsSingularAllowFeatureGate.ID(), true))
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.ProcessorK8sattributesEmitV1K8sConventionsFeatureGate.ID(), true))
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.ProcessorK8sattributesDontEmitV0K8sConventionsFeatureGate.ID(), true))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.K8sattrLabelsAnnotationsSingularAllowFeatureGate.ID(), false))
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.ProcessorK8sattributesEmitV1K8sConventionsFeatureGate.ID(), false))
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.ProcessorK8sattributesDontEmitV0K8sConventionsFeatureGate.ID(), false))
}()
}

Expand Down Expand Up @@ -1636,9 +1640,11 @@ func TestNodeExtractionRules(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if tc.singularFeatureGate {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.K8sattrLabelsAnnotationsSingularAllowFeatureGate.ID(), true))
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.ProcessorK8sattributesEmitV1K8sConventionsFeatureGate.ID(), true))
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.ProcessorK8sattributesDontEmitV0K8sConventionsFeatureGate.ID(), true))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.K8sattrLabelsAnnotationsSingularAllowFeatureGate.ID(), false))
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.ProcessorK8sattributesEmitV1K8sConventionsFeatureGate.ID(), false))
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.ProcessorK8sattributesDontEmitV0K8sConventionsFeatureGate.ID(), false))
}()
}

Expand Down
7 changes: 6 additions & 1 deletion processor/k8sattributesprocessor/internal/kube/kube.go
Original file line number Diff line number Diff line change
Expand Up @@ -372,7 +372,12 @@ func (r *FieldExtractionRule) extractFromMetadata(metadata, tags map[string]stri
}
}
} else if v, ok := metadata[r.Key]; ok {
tags[r.Name] = r.extractField(v)
// Use formatter to determine attribute name if no custom name was specified
name := r.Name
if name == "" {
name = fmt.Sprintf(formatter, r.Key)
}
tags[name] = r.extractField(v)
}
}

Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 14 additions & 1 deletion processor/k8sattributesprocessor/metadata.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -136,11 +136,24 @@ resource_attributes:

feature_gates:
- id: k8sattr.labelsAnnotationsSingular.allow
stage: alpha
stage: deprecated
description: >-
When enabled, default k8s label and annotation resource attribute keys will be singular, instead of plural
from_version: v0.125.0
to_version: v0.145.0
reference_url: https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/39774
- id: processor.k8sattributes.DontEmitV0K8sConventions
stage: alpha
description: >-
When enabled, semconv legacy attributes are disabled.
from_version: v0.145.0
reference_url: https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/44589
- id: processor.k8sattributes.EmitV1K8sConventions
stage: alpha
description: >-
When enabled, semconv stable attributes are enabled.
Comment thread
ChrsMark marked this conversation as resolved.
from_version: v0.145.0
reference_url: https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/44589

tests:
config:
Expand Down
18 changes: 3 additions & 15 deletions processor/k8sattributesprocessor/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,8 @@
package k8sattributesprocessor // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/k8sattributesprocessor"

import (
"fmt"
"os"
"regexp"
"strings"
"time"

conventions "go.opentelemetry.io/otel/semconv/v1.39.0"
Expand Down Expand Up @@ -240,7 +238,7 @@ func withDeploymentNameFromReplicaSet(enabled bool) option {
// withExtractLabels allows specifying options to control extraction of pod labels.
func withExtractLabels(labels ...FieldExtractConfig) option {
return func(p *kubernetesprocessor) error {
labels, err := extractFieldRules("labels", labels...)
labels, err := extractFieldRules(labels...)
if err != nil {
return err
}
Expand All @@ -252,7 +250,7 @@ func withExtractLabels(labels ...FieldExtractConfig) option {
// withExtractAnnotations allows specifying options to control extraction of pod annotations tags.
func withExtractAnnotations(annotations ...FieldExtractConfig) option {
return func(p *kubernetesprocessor) error {
annotations, err := extractFieldRules("annotations", annotations...)
annotations, err := extractFieldRules(annotations...)
if err != nil {
return err
}
Expand All @@ -261,7 +259,7 @@ func withExtractAnnotations(annotations ...FieldExtractConfig) option {
}
}

func extractFieldRules(fieldType string, fields ...FieldExtractConfig) ([]kube.FieldExtractionRule, error) {
func extractFieldRules(fields ...FieldExtractConfig) ([]kube.FieldExtractionRule, error) {
var rules []kube.FieldExtractionRule
for _, a := range fields {
name := a.TagName
Expand All @@ -270,16 +268,6 @@ func extractFieldRules(fieldType string, fields ...FieldExtractConfig) ([]kube.F
a.From = kube.MetadataFromPod
}

if name == "" && a.Key != "" {
// name for KeyRegex case is set at extraction time/runtime, skipped here
// Use singular form when feature gate is enabled
fieldTypeName := fieldType
if metadata.K8sattrLabelsAnnotationsSingularAllowFeatureGate.IsEnabled() {
fieldTypeName = strings.TrimSuffix(fieldType, "s")
}
name = fmt.Sprintf("k8s.%v.%v.%v", a.From, fieldTypeName, a.Key)
}

var keyRegex *regexp.Regexp
var hasKeyRegexReference bool
if a.KeyRegex != "" {
Expand Down
Loading