Skip to content

Commit 0049158

Browse files
committed
[processor/k8sattributes] introduce semconv compaint feature gate pair
Signed-off-by: odubajDT <[email protected]>
1 parent b1e96be commit 0049158

File tree

6 files changed

+122
-46
lines changed

6 files changed

+122
-46
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Use this changelog template to create an entry for release notes.
2+
3+
# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
4+
change_type: breaking
5+
6+
# The name of the component, or a single word describing the area of concern, (e.g. receiver/filelog)
7+
component: processor/k8sattributes
8+
9+
# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
10+
note: Introduce semantic conventions compliant feature gate pair for k8sattributes processor
11+
12+
# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists.
13+
issues: [44693]
14+
15+
# (Optional) One or more lines of additional information to render under the primary note.
16+
# These lines will be padded with 2 spaces and then inserted directly into the document.
17+
# Use pipe (|) for multiline entries.
18+
subtext: |
19+
- Added `semconv.k8s.k8sattributes.enableStable` feature gate to enable stable semantic convention attributes (singular form: `k8s.<workload>.label.<key>` and `k8s.<workload>.annotation.<key>`)
20+
- Added `semconv.k8s.k8sattributes.disableLegacy` feature gate to disable legacy non-compliant attributes (plural form: `k8s.<workload>.labels.<key>` and `k8s.<workload>.annotations.<key>`)
21+
- Both feature gates are in `alpha` stage and disabled by default
22+
- The processor now validates that legacy attributes cannot be disabled without enabling stable attributes
23+
- Deprecated `k8sattr.labelsAnnotationsSingular.allow` feature gate in favor of the new semconv-compliant gates (will be removed in v0.150.0)
24+
- During migration period, both legacy and stable attributes can coexist when `enableStable` is enabled but `disableLegacy` is not
25+
26+
# If your change doesn't affect end users or the exported elements of any package,
27+
# you should instead start your pull request title with [chore] or use the "Skip Changelog" label.
28+
# Optional: The change log or logs in which this entry should be included.
29+
# e.g. '[user]' or '[user, api]'
30+
# Include 'user' if the change is relevant to end users.
31+
# Include 'api' if there is a change to a library API.
32+
# Default: '[user]'
33+
change_logs: []

processor/k8sattributesprocessor/internal/kube/client.go

Lines changed: 59 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515

1616
"github.com/distribution/reference"
1717
"go.opentelemetry.io/collector/component"
18+
"go.opentelemetry.io/collector/featuregate"
1819
"go.opentelemetry.io/otel/attribute"
1920
conventions "go.opentelemetry.io/otel/semconv/v1.39.0"
2021
"go.uber.org/zap"
@@ -64,6 +65,21 @@ const (
6465
K8sJobAnnotation = "k8s.job.annotation.%s"
6566
)
6667

68+
var (
69+
EnableStableAttributes = featuregate.GlobalRegistry().MustRegister(
70+
"semconv.k8s.k8sattributes.enableStable",
71+
featuregate.StageAlpha,
72+
featuregate.WithRegisterDescription("When enabled, semconv stable attributes are enabled."),
73+
featuregate.WithRegisterFromVersion("v0.144.0"),
74+
)
75+
DisableLegacyAttributes = featuregate.GlobalRegistry().MustRegister(
76+
"semconv.k8s.k8sattributes.disableLegacy",
77+
featuregate.StageAlpha,
78+
featuregate.WithRegisterDescription("When enabled, semconv legacy attributes are disabled."),
79+
featuregate.WithRegisterFromVersion("v0.144.0"),
80+
)
81+
)
82+
6783
// WatchClient is the main interface provided by this package to a kubernetes cluster.
6884
type WatchClient struct {
6985
m sync.RWMutex
@@ -918,18 +934,25 @@ func (c *WatchClient) extractPodAttributes(pod *api_v1.Pod) map[string]string {
918934
}
919935
}
920936

921-
formatterLabel := K8sPodLabelsKey
922-
if metadata.K8sattrLabelsAnnotationsSingularAllowFeatureGate.IsEnabled() {
923-
formatterLabel = K8sPodLabelKey
924-
}
937+
enableStable := EnableStableAttributes.IsEnabled()
938+
disableLegacy := DisableLegacyAttributes.IsEnabled()
925939

926940
for _, r := range c.Rules.Labels {
927-
r.extractFromPodMetadata(pod.Labels, tags, formatterLabel)
941+
if !disableLegacy {
942+
r.extractFromPodMetadata(pod.Labels, tags, K8sPodLabelsKey)
943+
}
944+
if enableStable {
945+
r.extractFromPodMetadata(pod.Labels, tags, K8sPodLabelKey)
946+
}
928947
}
929948

930-
formatterAnnotation := K8sPodAnnotationsKey
931-
if metadata.K8sattrLabelsAnnotationsSingularAllowFeatureGate.IsEnabled() {
932-
formatterAnnotation = K8sPodAnnotationKey
949+
for _, r := range c.Rules.Annotations {
950+
if !disableLegacy {
951+
r.extractFromPodMetadata(pod.Annotations, tags, K8sPodAnnotationsKey)
952+
}
953+
if enableStable {
954+
r.extractFromPodMetadata(pod.Annotations, tags, K8sPodAnnotationKey)
955+
}
933956
}
934957

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

945-
for _, r := range c.Rules.Annotations {
946-
r.extractFromPodMetadata(pod.Annotations, tags, formatterAnnotation)
947-
}
948968
return tags
949969
}
950970

@@ -1163,22 +1183,25 @@ func (c *WatchClient) extractPodContainersAttributes(pod *api_v1.Pod) PodContain
11631183
func (c *WatchClient) extractNamespaceAttributes(namespace *api_v1.Namespace) map[string]string {
11641184
tags := map[string]string{}
11651185

1166-
formatterLabel := K8sNamespaceLabelsKey
1167-
if metadata.K8sattrLabelsAnnotationsSingularAllowFeatureGate.IsEnabled() {
1168-
formatterLabel = K8sNamespaceLabelKey
1169-
}
1186+
enableStable := EnableStableAttributes.IsEnabled()
1187+
disableLegacy := DisableLegacyAttributes.IsEnabled()
11701188

11711189
for _, r := range c.Rules.Labels {
1172-
r.extractFromNamespaceMetadata(namespace.Labels, tags, formatterLabel)
1173-
}
1174-
1175-
formatterAnnotation := K8sNamespaceAnnotationsKey
1176-
if metadata.K8sattrLabelsAnnotationsSingularAllowFeatureGate.IsEnabled() {
1177-
formatterAnnotation = K8sNamespaceAnnotationKey
1190+
if !disableLegacy {
1191+
r.extractFromNamespaceMetadata(namespace.Labels, tags, K8sNamespaceLabelsKey)
1192+
}
1193+
if enableStable {
1194+
r.extractFromNamespaceMetadata(namespace.Labels, tags, K8sNamespaceLabelKey)
1195+
}
11781196
}
11791197

11801198
for _, r := range c.Rules.Annotations {
1181-
r.extractFromNamespaceMetadata(namespace.Annotations, tags, formatterAnnotation)
1199+
if !disableLegacy {
1200+
r.extractFromNamespaceMetadata(namespace.Annotations, tags, K8sNamespaceAnnotationsKey)
1201+
}
1202+
if enableStable {
1203+
r.extractFromNamespaceMetadata(namespace.Annotations, tags, K8sNamespaceAnnotationKey)
1204+
}
11821205
}
11831206

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

1190-
formatterLabel := K8sNodeLabelsKey
1191-
if metadata.K8sattrLabelsAnnotationsSingularAllowFeatureGate.IsEnabled() {
1192-
formatterLabel = K8sNodeLabelKey
1193-
}
1213+
enableStable := EnableStableAttributes.IsEnabled()
1214+
disableLegacy := DisableLegacyAttributes.IsEnabled()
11941215

11951216
for _, r := range c.Rules.Labels {
1196-
r.extractFromNodeMetadata(node.Labels, tags, formatterLabel)
1197-
}
1198-
1199-
formatterAnnotation := K8sNodeAnnotationsKey
1200-
if metadata.K8sattrLabelsAnnotationsSingularAllowFeatureGate.IsEnabled() {
1201-
formatterAnnotation = K8sNodeAnnotationKey
1217+
if !disableLegacy {
1218+
r.extractFromNodeMetadata(node.Labels, tags, K8sNodeLabelsKey)
1219+
}
1220+
if enableStable {
1221+
r.extractFromNodeMetadata(node.Labels, tags, K8sNodeLabelKey)
1222+
}
12021223
}
12031224

12041225
for _, r := range c.Rules.Annotations {
1205-
r.extractFromNodeMetadata(node.Annotations, tags, formatterAnnotation)
1226+
if !disableLegacy {
1227+
r.extractFromNodeMetadata(node.Annotations, tags, K8sNodeAnnotationsKey)
1228+
}
1229+
if enableStable {
1230+
r.extractFromNodeMetadata(node.Annotations, tags, K8sNodeAnnotationKey)
1231+
}
12061232
}
12071233
return tags
12081234
}

processor/k8sattributesprocessor/internal/kube/client_test.go

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ import (
3131
"k8s.io/client-go/tools/cache"
3232

3333
"github.com/open-telemetry/opentelemetry-collector-contrib/internal/k8sconfig"
34-
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/k8sattributesprocessor/internal/metadata"
3534
)
3635

3736
func newFakeAPIClientset(_ k8sconfig.APIConfig) (kubernetes.Interface, error) {
@@ -1074,9 +1073,11 @@ func TestExtractionRules(t *testing.T) {
10741073
for _, tc := range testCases {
10751074
t.Run(tc.name, func(t *testing.T) {
10761075
if tc.singularFeatureGate {
1077-
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.K8sattrLabelsAnnotationsSingularAllowFeatureGate.ID(), true))
1076+
require.NoError(t, featuregate.GlobalRegistry().Set(EnableStableAttributes.ID(), true))
1077+
require.NoError(t, featuregate.GlobalRegistry().Set(DisableLegacyAttributes.ID(), true))
10781078
defer func() {
1079-
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.K8sattrLabelsAnnotationsSingularAllowFeatureGate.ID(), false))
1079+
require.NoError(t, featuregate.GlobalRegistry().Set(EnableStableAttributes.ID(), false))
1080+
require.NoError(t, featuregate.GlobalRegistry().Set(DisableLegacyAttributes.ID(), false))
10801081
}()
10811082
}
10821083

@@ -1378,9 +1379,11 @@ func TestNamespaceExtractionRules(t *testing.T) {
13781379
for _, tc := range testCases {
13791380
t.Run(tc.name, func(t *testing.T) {
13801381
if tc.singularFeatureGate {
1381-
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.K8sattrLabelsAnnotationsSingularAllowFeatureGate.ID(), true))
1382+
require.NoError(t, featuregate.GlobalRegistry().Set(EnableStableAttributes.ID(), true))
1383+
require.NoError(t, featuregate.GlobalRegistry().Set(DisableLegacyAttributes.ID(), true))
13821384
defer func() {
1383-
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.K8sattrLabelsAnnotationsSingularAllowFeatureGate.ID(), false))
1385+
require.NoError(t, featuregate.GlobalRegistry().Set(EnableStableAttributes.ID(), false))
1386+
require.NoError(t, featuregate.GlobalRegistry().Set(DisableLegacyAttributes.ID(), false))
13841387
}()
13851388
}
13861389

@@ -1636,9 +1639,11 @@ func TestNodeExtractionRules(t *testing.T) {
16361639
for _, tc := range testCases {
16371640
t.Run(tc.name, func(t *testing.T) {
16381641
if tc.singularFeatureGate {
1639-
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.K8sattrLabelsAnnotationsSingularAllowFeatureGate.ID(), true))
1642+
require.NoError(t, featuregate.GlobalRegistry().Set(EnableStableAttributes.ID(), true))
1643+
require.NoError(t, featuregate.GlobalRegistry().Set(DisableLegacyAttributes.ID(), true))
16401644
defer func() {
1641-
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.K8sattrLabelsAnnotationsSingularAllowFeatureGate.ID(), false))
1645+
require.NoError(t, featuregate.GlobalRegistry().Set(EnableStableAttributes.ID(), false))
1646+
require.NoError(t, featuregate.GlobalRegistry().Set(DisableLegacyAttributes.ID(), false))
16421647
}()
16431648
}
16441649

processor/k8sattributesprocessor/options.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -272,9 +272,11 @@ func extractFieldRules(fieldType string, fields ...FieldExtractConfig) ([]kube.F
272272

273273
if name == "" && a.Key != "" {
274274
// name for KeyRegex case is set at extraction time/runtime, skipped here
275-
// Use singular form when feature gate is enabled
275+
// Use singular form when stable attributes are enabled and legacy attributes are disabled
276276
fieldTypeName := fieldType
277-
if metadata.K8sattrLabelsAnnotationsSingularAllowFeatureGate.IsEnabled() {
277+
enableStable := kube.EnableStableAttributes.IsEnabled()
278+
disableLegacy := kube.DisableLegacyAttributes.IsEnabled()
279+
if enableStable && disableLegacy {
278280
fieldTypeName = strings.TrimSuffix(fieldType, "s")
279281
}
280282
name = fmt.Sprintf("k8s.%v.%v.%v", a.From, fieldTypeName, a.Key)

processor/k8sattributesprocessor/options_test.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import (
1414

1515
"github.com/open-telemetry/opentelemetry-collector-contrib/internal/k8sconfig"
1616
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/k8sattributesprocessor/internal/kube"
17-
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/k8sattributesprocessor/internal/metadata"
1817
)
1918

2019
func TestWithAPIConfig(t *testing.T) {
@@ -736,11 +735,15 @@ func Test_extractFieldRules_FeatureGate(t *testing.T) {
736735

737736
for _, tt := range tests {
738737
t.Run(tt.name, func(t *testing.T) {
739-
// Set feature gate state
740-
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.K8sattrLabelsAnnotationsSingularAllowFeatureGate.ID(), tt.featureGateValue))
738+
// Set feature gate state for stable and legacy attributes
739+
if tt.featureGateValue {
740+
require.NoError(t, featuregate.GlobalRegistry().Set(kube.EnableStableAttributes.ID(), true))
741+
require.NoError(t, featuregate.GlobalRegistry().Set(kube.DisableLegacyAttributes.ID(), true))
742+
}
741743
defer func() {
742744
// Reset to default
743-
require.NoError(t, featuregate.GlobalRegistry().Set(metadata.K8sattrLabelsAnnotationsSingularAllowFeatureGate.ID(), false))
745+
require.NoError(t, featuregate.GlobalRegistry().Set(kube.EnableStableAttributes.ID(), false))
746+
require.NoError(t, featuregate.GlobalRegistry().Set(kube.DisableLegacyAttributes.ID(), false))
744747
}()
745748

746749
got, err := extractFieldRules(tt.fieldType, tt.fields...)

processor/k8sattributesprocessor/processor.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package k8sattributesprocessor // import "github.com/open-telemetry/opentelemetr
55

66
import (
77
"context"
8+
"errors"
89
"fmt"
910
"strconv"
1011
"time"
@@ -58,6 +59,12 @@ func (kp *kubernetesprocessor) initKubeClient(set component.TelemetrySettings, k
5859
}
5960

6061
func (kp *kubernetesprocessor) Start(_ context.Context, host component.Host) error {
62+
if kube.DisableLegacyAttributes.IsEnabled() && !kube.EnableStableAttributes.IsEnabled() {
63+
err := errors.New("cannot disable legacy attributes without enabling stable attributes")
64+
componentstatus.ReportStatus(host, componentstatus.NewFatalErrorEvent(err))
65+
return err
66+
}
67+
6168
allOptions := append(createProcessorOpts(kp.cfg), kp.options...)
6269

6370
for _, opt := range allOptions {

0 commit comments

Comments
 (0)