@@ -12,7 +12,9 @@ import (
12
12
"sync"
13
13
"time"
14
14
15
+ "github.com/distribution/reference"
15
16
"go.opentelemetry.io/collector/component"
17
+ "go.opentelemetry.io/otel/attribute"
16
18
conventions "go.opentelemetry.io/otel/semconv/v1.6.1"
17
19
"go.uber.org/zap"
18
20
apps_v1 "k8s.io/api/apps/v1"
@@ -77,6 +79,8 @@ var rRegex = regexp.MustCompile(`^(.*)-[0-9a-zA-Z]+$`)
77
79
// format: [cronjob-name]-[time-hash-int]
78
80
var cronJobRegex = regexp .MustCompile (`^(.*)-[0-9]+$` )
79
81
82
+ var errCannotRetrieveImage = errors .New ("cannot retrieve image name" )
83
+
80
84
// New initializes a new k8s Client.
81
85
func New (
82
86
set component.TelemetrySettings ,
@@ -453,6 +457,9 @@ func (c *WatchClient) extractPodAttributes(pod *api_v1.Pod) map[string]string {
453
457
if c .Rules .PodName {
454
458
tags [string (conventions .K8SPodNameKey )] = pod .Name
455
459
}
460
+ if c .Rules .ServiceName {
461
+ tags [string (conventions .ServiceNameKey )] = pod .Name
462
+ }
456
463
457
464
if c .Rules .PodHostName {
458
465
tags [tagHostName ] = pod .Spec .Hostname
@@ -487,7 +494,7 @@ func (c *WatchClient) extractPodAttributes(pod *api_v1.Pod) map[string]string {
487
494
c .Rules .JobUID || c .Rules .JobName ||
488
495
c .Rules .StatefulSetUID || c .Rules .StatefulSetName ||
489
496
c .Rules .DeploymentName || c .Rules .DeploymentUID ||
490
- c .Rules .CronJobName {
497
+ c .Rules .CronJobName || c . Rules . ServiceName {
491
498
for _ , ref := range pod .OwnerReferences {
492
499
switch ref .Kind {
493
500
case "ReplicaSet" :
@@ -497,10 +504,20 @@ func (c *WatchClient) extractPodAttributes(pod *api_v1.Pod) map[string]string {
497
504
if c .Rules .ReplicaSetName {
498
505
tags [string (conventions .K8SReplicaSetNameKey )] = ref .Name
499
506
}
500
- if c .Rules .DeploymentName {
507
+ if c .Rules .ServiceName {
508
+ tags [string (conventions .ServiceNameKey )] = ref .Name
509
+ }
510
+ if c .Rules .DeploymentName || c .Rules .ServiceName {
501
511
if replicaset , ok := c .getReplicaSet (string (ref .UID )); ok {
502
- if replicaset .Deployment .Name != "" {
503
- tags [string (conventions .K8SDeploymentNameKey )] = replicaset .Deployment .Name
512
+ name := replicaset .Deployment .Name
513
+ if name != "" {
514
+ if c .Rules .DeploymentName {
515
+ tags [string (conventions .K8SDeploymentNameKey )] = name
516
+ }
517
+ if c .Rules .ServiceName {
518
+ // deployment name wins over replicaset name
519
+ tags [string (conventions .ServiceNameKey )] = name
520
+ }
504
521
}
505
522
}
506
523
}
@@ -518,26 +535,42 @@ func (c *WatchClient) extractPodAttributes(pod *api_v1.Pod) map[string]string {
518
535
if c .Rules .DaemonSetName {
519
536
tags [string (conventions .K8SDaemonSetNameKey )] = ref .Name
520
537
}
538
+ if c .Rules .ServiceName {
539
+ tags [string (conventions .ServiceNameKey )] = ref .Name
540
+ }
521
541
case "StatefulSet" :
522
542
if c .Rules .StatefulSetUID {
523
543
tags [string (conventions .K8SStatefulSetUIDKey )] = string (ref .UID )
524
544
}
525
545
if c .Rules .StatefulSetName {
526
546
tags [string (conventions .K8SStatefulSetNameKey )] = ref .Name
527
547
}
528
- case "Job" :
529
- if c .Rules .CronJobName {
530
- parts := c .cronJobRegex .FindStringSubmatch (ref .Name )
531
- if len (parts ) == 2 {
532
- tags [string (conventions .K8SCronJobNameKey )] = parts [1 ]
533
- }
548
+ if c .Rules .ServiceName {
549
+ tags [string (conventions .ServiceNameKey )] = ref .Name
534
550
}
551
+ case "Job" :
535
552
if c .Rules .JobUID {
536
553
tags [string (conventions .K8SJobUIDKey )] = string (ref .UID )
537
554
}
538
555
if c .Rules .JobName {
539
556
tags [string (conventions .K8SJobNameKey )] = ref .Name
540
557
}
558
+ if c .Rules .ServiceName {
559
+ tags [string (conventions .ServiceNameKey )] = ref .Name
560
+ }
561
+ if c .Rules .CronJobName || c .Rules .ServiceName {
562
+ parts := c .cronJobRegex .FindStringSubmatch (ref .Name )
563
+ if len (parts ) == 2 {
564
+ name := parts [1 ]
565
+ if c .Rules .CronJobName {
566
+ tags [string (conventions .K8SCronJobNameKey )] = name
567
+ }
568
+ if c .Rules .ServiceName {
569
+ // cronjob name wins over job name
570
+ tags [string (conventions .ServiceNameKey )] = name
571
+ }
572
+ }
573
+ }
541
574
}
542
575
}
543
576
}
@@ -558,12 +591,28 @@ func (c *WatchClient) extractPodAttributes(pod *api_v1.Pod) map[string]string {
558
591
r .extractFromPodMetadata (pod .Labels , tags , "k8s.pod.labels.%s" )
559
592
}
560
593
594
+ if c .Rules .ServiceName {
595
+ copyLabel (pod , tags , "app.kubernetes.io/name" , conventions .ServiceNameKey )
596
+ // app.kubernetes.io/instance has a higher precedence than app.kubernetes.io/name
597
+ copyLabel (pod , tags , "app.kubernetes.io/instance" , conventions .ServiceNameKey )
598
+ }
599
+
600
+ if c .Rules .ServiceVersion {
601
+ copyLabel (pod , tags , "app.kubernetes.io/version" , conventions .ServiceVersionKey )
602
+ }
603
+
561
604
for _ , r := range c .Rules .Annotations {
562
605
r .extractFromPodMetadata (pod .Annotations , tags , "k8s.pod.annotations.%s" )
563
606
}
564
607
return tags
565
608
}
566
609
610
+ func copyLabel (pod * api_v1.Pod , tags map [string ]string , labelKey string , key attribute.Key ) {
611
+ if val , ok := pod .Labels [labelKey ]; ok {
612
+ tags [string (key )] = val
613
+ }
614
+ }
615
+
567
616
// This function removes all data from the Pod except what is required by extraction rules and pod association
568
617
func removeUnnecessaryPodData (pod * api_v1.Pod , rules ExtractionRules ) * api_v1.Pod {
569
618
// name, namespace, uid, start time and ip are needed for identifying Pods
@@ -626,7 +675,7 @@ func removeUnnecessaryPodData(pod *api_v1.Pod, rules ExtractionRules) *api_v1.Po
626
675
removeUnnecessaryContainerData := func (c api_v1.Container ) api_v1.Container {
627
676
transformedContainer := api_v1.Container {}
628
677
transformedContainer .Name = c .Name // we always need the name, it's used for identification
629
- if rules .ContainerImageName || rules .ContainerImageTag {
678
+ if rules .ContainerImageName || rules .ContainerImageTag || rules . ServiceVersion {
630
679
transformedContainer .Image = c .Image
631
680
}
632
681
return transformedContainer
@@ -644,7 +693,7 @@ func removeUnnecessaryPodData(pod *api_v1.Pod, rules ExtractionRules) *api_v1.Po
644
693
}
645
694
}
646
695
647
- if len (rules .Labels ) > 0 {
696
+ if len (rules .Labels ) > 0 || rules . ServiceName || rules . ServiceVersion {
648
697
transformedPod .Labels = pod .Labels
649
698
}
650
699
@@ -659,6 +708,38 @@ func removeUnnecessaryPodData(pod *api_v1.Pod, rules ExtractionRules) *api_v1.Po
659
708
return & transformedPod
660
709
}
661
710
711
+ // parseServiceVersionFromImage parses the service version for differently-formatted image names
712
+ // according to https://github.com/open-telemetry/semantic-conventions/blob/main/docs/non-normative/k8s-attributes.md#how-serviceversion-should-be-calculated
713
+ func parseServiceVersionFromImage (image string ) (string , error ) {
714
+ ref , err := reference .Parse (image )
715
+ if err != nil {
716
+ return "" , err
717
+ }
718
+
719
+ namedRef , ok := ref .(reference.Named )
720
+ if ! ok {
721
+ return "" , errCannotRetrieveImage
722
+ }
723
+ var tag , digest string
724
+ if taggedRef , ok := namedRef .(reference.Tagged ); ok {
725
+ tag = taggedRef .Tag ()
726
+ }
727
+ if digestedRef , ok := namedRef .(reference.Digested ); ok {
728
+ digest = digestedRef .Digest ().String ()
729
+ }
730
+ if digest != "" {
731
+ if tag != "" {
732
+ return fmt .Sprintf ("%s@%s" , tag , digest ), nil
733
+ }
734
+ return digest , nil
735
+ }
736
+ if tag != "" {
737
+ return tag , nil
738
+ }
739
+
740
+ return "" , errCannotRetrieveImage
741
+ }
742
+
662
743
func (c * WatchClient ) extractPodContainersAttributes (pod * api_v1.Pod ) PodContainers {
663
744
containers := PodContainers {
664
745
ByID : map [string ]* Container {},
@@ -667,7 +748,8 @@ func (c *WatchClient) extractPodContainersAttributes(pod *api_v1.Pod) PodContain
667
748
if ! needContainerAttributes (c .Rules ) {
668
749
return containers
669
750
}
670
- if c .Rules .ContainerImageName || c .Rules .ContainerImageTag {
751
+ if c .Rules .ContainerImageName || c .Rules .ContainerImageTag ||
752
+ c .Rules .ServiceVersion || c .Rules .ServiceInstanceID {
671
753
for _ , spec := range append (pod .Spec .Containers , pod .Spec .InitContainers ... ) {
672
754
container := & Container {}
673
755
imageRef , err := dcommon .ParseImageName (spec .Image )
@@ -678,18 +760,28 @@ func (c *WatchClient) extractPodContainersAttributes(pod *api_v1.Pod) PodContain
678
760
if c .Rules .ContainerImageTag {
679
761
container .ImageTag = imageRef .Tag
680
762
}
763
+ if c .Rules .ServiceVersion {
764
+ serviceVersion , err := parseServiceVersionFromImage (spec .Image )
765
+ if err == nil {
766
+ container .ServiceVersion = serviceVersion
767
+ }
768
+ }
681
769
}
682
770
containers .ByName [spec .Name ] = container
683
771
}
684
772
}
685
773
for _ , apiStatus := range append (pod .Status .ContainerStatuses , pod .Status .InitContainerStatuses ... ) {
686
- container , ok := containers .ByName [apiStatus .Name ]
774
+ containerName := apiStatus .Name
775
+ container , ok := containers .ByName [containerName ]
687
776
if ! ok {
688
777
container = & Container {}
689
- containers .ByName [apiStatus . Name ] = container
778
+ containers .ByName [containerName ] = container
690
779
}
691
780
if c .Rules .ContainerName {
692
- container .Name = apiStatus .Name
781
+ container .Name = containerName
782
+ }
783
+ if c .Rules .ServiceInstanceID {
784
+ container .ServiceInstanceID = automaticServiceInstanceID (pod , containerName )
693
785
}
694
786
containerID := apiStatus .ContainerID
695
787
// Remove container runtime prefix
@@ -1022,7 +1114,9 @@ func needContainerAttributes(rules ExtractionRules) bool {
1022
1114
rules .ContainerName ||
1023
1115
rules .ContainerImageTag ||
1024
1116
rules .ContainerImageRepoDigests ||
1025
- rules .ContainerID
1117
+ rules .ContainerID ||
1118
+ rules .ServiceVersion ||
1119
+ rules .ServiceInstanceID
1026
1120
}
1027
1121
1028
1122
func (c * WatchClient ) handleReplicaSetAdd (obj any ) {
@@ -1127,3 +1221,8 @@ func ignoreDeletedFinalStateUnknown(obj any) any {
1127
1221
}
1128
1222
return obj
1129
1223
}
1224
+
1225
+ func automaticServiceInstanceID (pod * api_v1.Pod , containerName string ) string {
1226
+ resNames := []string {pod .Namespace , pod .Name , containerName }
1227
+ return strings .Join (resNames , "." )
1228
+ }
0 commit comments