Skip to content

Commit

Permalink
Change the way resource attributes are populated (unbreaks a 2.0 chan…
Browse files Browse the repository at this point in the history
…ge) (#1565)

* Change the way resource attributes is populated

* fix integration tests

* fixed tests
  • Loading branch information
mariomac authored Jan 23, 2025
1 parent 285162b commit cbb6303
Show file tree
Hide file tree
Showing 15 changed files with 87 additions and 107 deletions.
39 changes: 10 additions & 29 deletions docs/sources/configure/service-discovery.md
Original file line number Diff line number Diff line change
Expand Up @@ -321,39 +321,20 @@ Beyla uses the following criteria in this order to automatically set the service
- `k8s.container.name`
5. The executable name of the instrumented process.

The Kubernetes annotations and labels from the previous bullets 2 and 3 can be overridden and via
configuration.
The Kubernetes labels from the previous bullet 3 can be overridden via configuration.

In YAML:
```yaml
kubernetes:
meta_naming_sources:
annotations:
service_name:
# gets service name from the first existing Pod annotation
- my.domain.com/override-service-name
- resource.opentelemetry.io/service.name
service_namespace:
# gets service namespace from the first existing Pod annotation
- my.domain.com/override-service-namespace
- resource.opentelemetry.io/service.namespace
labels:
service_name:
# gets service name from the first existing Pod label
- override-svc-name
- app.kubernetes.io/name
service_namespace:
# gets service namespace from the first existing Pod label
- override-svc-ns
- app.kubernetes.io/part-of
resource_labels:
service.name:
# gets service name from the first existing Pod label
- override-svc-name
- app.kubernetes.io/name
service.namespace:
# gets service namespace from the first existing Pod label
- override-svc-ns
- app.kubernetes.io/part-of
```

The equivalent environment variables for the labels and annotation overriding
properties are:

* `BEYLA_KUBE_ANNOTATIONS_SERVICE_NAME`
* `BEYLA_KUBE_ANNOTATIONS_SERVICE_NAMESPACE`
* `BEYLA_KUBE_LABELS_SERVICE_NAME`
* `BEYLA_KUBE_LABELS_SERVICE_NAMESPACE`

They accept a comma-separated list of annotation and label names.
2 changes: 1 addition & 1 deletion pkg/beyla/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ var DefaultConfig = Config{
Enable: kubeflags.EnabledDefault,
InformersSyncTimeout: 30 * time.Second,
InformersResyncPeriod: 30 * time.Minute,
MetadataSources: kube.DefaultMetadataSources,
ResourceLabels: kube.DefaultResourceLabels,
},
HostID: HostIDConfig{
FetchTimeout: 500 * time.Millisecond,
Expand Down
15 changes: 6 additions & 9 deletions pkg/beyla/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"io"
"log/slog"
"maps"
"os"
"regexp"
"strings"
Expand Down Expand Up @@ -55,11 +56,8 @@ attributes:
kubeconfig_path: /foo/bar
enable: true
informers_sync_timeout: 30s
meta_naming_sources:
annotations:
service_namespace: ["huha.com/yeah"]
labels:
service_name: ["titi.com/lala"]
resource_labels:
service.namespace: ["huha.com/yeah"]
instance_id:
dns: true
host_id:
Expand Down Expand Up @@ -108,9 +106,8 @@ network:
nc.AgentIP = "1.2.3.4"
nc.CIDRs = cidr.Definitions{"10.244.0.0/16"}

metaSources := kube.DefaultMetadataSources
metaSources.Annotations.ServiceNamespace = []string{"huha.com/yeah"}
metaSources.Labels.ServiceName = []string{"titi.com/lala"}
metaSources := maps.Clone(kube.DefaultResourceLabels)
metaSources["service.namespace"] = []string{"huha.com/yeah"}

assert.Equal(t, &Config{
Exec: cfg.Exec,
Expand Down Expand Up @@ -190,7 +187,7 @@ network:
Enable: kubeflags.EnabledTrue,
InformersSyncTimeout: 30 * time.Second,
InformersResyncPeriod: 30 * time.Minute,
MetadataSources: metaSources,
ResourceLabels: metaSources,
},
HostID: HostIDConfig{
Override: "the-host-id",
Expand Down
22 changes: 21 additions & 1 deletion pkg/components/beyla.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,26 @@ func mustSkip(cfg *beyla.Config) string {
func buildCommonContextInfo(
ctx context.Context, config *beyla.Config,
) (*global.ContextInfo, error) {

// merging deprecated resource labels definition for backwards compatibility
resourceLabels := config.Attributes.Kubernetes.ResourceLabels
if resourceLabels == nil {
resourceLabels = map[string][]string{}
}
showDeprecation := sync.OnceFunc(func() {
slog.Warn("The meta_source_labels (BEYLA_KUBE_META_SOURCE_LABEL_* environment variables) is deprecated." +
" Check the documentation for more information about replacing it by the resource_labels kubernetes" +
" YAML property")
})
if svc := config.Attributes.Kubernetes.MetaSourceLabels.ServiceName; svc != "" {
resourceLabels["service.name"] = append([]string{svc}, resourceLabels["service.name"]...)
showDeprecation()
}
if ns := config.Attributes.Kubernetes.MetaSourceLabels.ServiceNamespace; ns != "" {
resourceLabels["service.namespace"] = append([]string{ns}, resourceLabels["service.namespace"]...)
showDeprecation()
}

promMgr := &connector.PrometheusManager{}
ctxInfo := &global.ContextInfo{
Prometheus: promMgr,
Expand All @@ -126,7 +146,7 @@ func buildCommonContextInfo(
ResyncPeriod: config.Attributes.Kubernetes.InformersResyncPeriod,
DisabledInformers: config.Attributes.Kubernetes.DisableInformers,
MetaCacheAddr: config.Attributes.Kubernetes.MetaCacheAddress,
MetadataSources: config.Attributes.Kubernetes.MetadataSources,
ResourceLabels: resourceLabels,
RestrictLocalNode: config.Attributes.Kubernetes.MetaRestrictLocalNode,
}),
}
Expand Down
4 changes: 2 additions & 2 deletions pkg/internal/discover/watcher_kube_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ func TestWatcherKubeEnricher(t *testing.T) {

// Setup a fake K8s API connected to the watcherKubeEnricher
fInformer := &fakeInformer{}
store := kube.NewStore(fInformer, kube.MetadataSources{})
store := kube.NewStore(fInformer, kube.ResourceLabels{})
wkeNodeFunc, err := WatcherKubeEnricherProvider(context.TODO(), &fakeMetadataProvider{store: store})()
require.NoError(t, err)
inputCh, outputCh := make(chan []Event[processAttrs], 10), make(chan []Event[processAttrs], 10)
Expand Down Expand Up @@ -107,7 +107,7 @@ func TestWatcherKubeEnricherWithMatcher(t *testing.T) {
processInfo = fakeProcessInfo
// Setup a fake K8s API connected to the watcherKubeEnricher
fInformer := &fakeInformer{}
store := kube.NewStore(fInformer, kube.MetadataSources{})
store := kube.NewStore(fInformer, kube.ResourceLabels{})
wkeNodeFunc, err := WatcherKubeEnricherProvider(context.TODO(), &fakeMetadataProvider{store: store})()
require.NoError(t, err)
pipeConfig := beyla.Config{}
Expand Down
4 changes: 2 additions & 2 deletions pkg/internal/kube/informer_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ type MetadataConfig struct {
SyncTimeout time.Duration
ResyncPeriod time.Duration
MetaCacheAddr string
MetadataSources MetadataSources
ResourceLabels ResourceLabels
RestrictLocalNode bool
}

Expand Down Expand Up @@ -107,7 +107,7 @@ func (mp *MetadataProvider) Get(ctx context.Context) (*Store, error) {
return nil, err
}

mp.metadata = NewStore(informer, mp.cfg.MetadataSources)
mp.metadata = NewStore(informer, mp.cfg.ResourceLabels)

return mp.metadata, nil
}
Expand Down
59 changes: 25 additions & 34 deletions pkg/internal/kube/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,33 +36,26 @@ func qName(om *informer.ObjectMeta) qualifiedName {
return qualifiedName{name: om.Name, namespace: om.Namespace, kind: om.Kind}
}

// MetadataSources allow overriding some metadata from kubernetes Pod labels and annotations
type MetadataSources struct {
Annotations AnnotationSources `yaml:"annotations"`
Labels LabelSources `yaml:"labels"`
// MetaSourceLabels allow overriding some metadata from kubernetes labels
// Deprecated. Left here for backwards-compatibility.
type MetaSourceLabels struct {
ServiceName string `yaml:"service_name" env:"BEYLA_KUBE_META_SOURCE_LABEL_SERVICE_NAME"`
ServiceNamespace string `yaml:"service_namespace" env:"BEYLA_KUBE_META_SOURCE_LABEL_SERVICE_NAMESPACE"`
}

type LabelSources struct {
ServiceName []string `yaml:"service_name" env:"BEYLA_KUBE_LABELS_SERVICE_NAME" envSeparator:","`
ServiceNamespace []string `yaml:"service_namespace" env:"BEYLA_KUBE_LABELS_SERVICE_NAMESPACE" envSeparator:","`
}
type ResourceLabels map[string][]string

type AnnotationSources struct {
ServiceName []string `yaml:"service_name" env:"BEYLA_KUBE_ANNOTATIONS_SERVICE_NAME" envSeparator:","`
ServiceNamespace []string `yaml:"service_namespace" env:"BEYLA_KUBE_ANNOTATIONS_SERVICE_NAMESPACE" envSeparator:","`
}
const (
ResourceAttributesPrefix = "resource.opentelemetry.io/"
ServiceNameAnnotation = ResourceAttributesPrefix + "service.name"
ServiceNamespaceAnnotation = ResourceAttributesPrefix + "service.namespace"
)

var DefaultMetadataSources = MetadataSources{
Annotations: AnnotationSources{
ServiceName: []string{"resource.opentelemetry.io/service.name"},
ServiceNamespace: []string{"resource.opentelemetry.io/service.namespace"},
},
// If a user sets useLabelsForResourceAttributes: false it its OTEL operator, is the task of the
var DefaultResourceLabels = ResourceLabels{
// If a user sets useLabelsForResourceAttributes: false in its OTEL operator config, is the task of the
// OTEL operator to provide empty values for this.
Labels: LabelSources{
ServiceName: []string{"app.kubernetes.io/name"},
ServiceNamespace: []string{"app.kubernetes.io/part-of"},
},
"service.name": []string{"app.kubernetes.io/name"},
"service.namespace": []string{"app.kubernetes.io/part-of"},
}

// Store aggregates Kubernetes information from multiple sources:
Expand Down Expand Up @@ -102,10 +95,10 @@ type Store struct {
// they receive is already present in the store
meta.BaseNotifier

metadataSources MetadataSources
resourceLabels ResourceLabels
}

func NewStore(kubeMetadata meta.Notifier, metadataSources MetadataSources) *Store {
func NewStore(kubeMetadata meta.Notifier, resourceLabels ResourceLabels) *Store {
log := dblog()
db := &Store{
log: log,
Expand All @@ -119,7 +112,7 @@ func NewStore(kubeMetadata meta.Notifier, metadataSources MetadataSources) *Stor
otelServiceInfoByIP: map[string]OTelServiceNamePair{},
metadataNotifier: kubeMetadata,
BaseNotifier: meta.NewBaseNotifier(log),
metadataSources: metadataSources,
resourceLabels: resourceLabels,
}
kubeMetadata.Subscribe(db)
return db
Expand Down Expand Up @@ -318,15 +311,13 @@ func (s *Store) serviceNameNamespaceForMetadata(om *informer.ObjectMeta) (string
// function implemented to provide consistent service metadata naming across multiple
// OTEL implementations: OTEL operator, Loki and Beyla
// https://github.com/grafana/k8s-monitoring-helm/issues/942
func (s *Store) valueFromMetadata(om *informer.ObjectMeta, annotationNames, labelNames []string) string {
func (s *Store) valueFromMetadata(om *informer.ObjectMeta, annotationName string, labelNames []string) string {
// if this object meta is not a pod, we ignore the metadata
if om.Pod == nil {
return ""
}
for _, key := range annotationNames {
if val, ok := om.Annotations[key]; ok {
return val
}
if val, ok := om.Annotations[annotationName]; ok {
return val
}
for _, key := range labelNames {
if val, ok := om.Labels[key]; ok {
Expand Down Expand Up @@ -376,16 +367,16 @@ func (s *Store) serviceNameNamespaceOwnerID(om *informer.ObjectMeta, ownerName s
if envName, ok := s.serviceNameFromEnv(ownerKey); ok {
serviceName = envName
} else if nameFromMeta := s.valueFromMetadata(om,
s.metadataSources.Annotations.ServiceName,
s.metadataSources.Labels.ServiceName,
ServiceNameAnnotation,
s.resourceLabels["service.name"],
); nameFromMeta != "" {
serviceName = nameFromMeta
}
if envName, ok := s.serviceNamespaceFromEnv(ownerKey); ok {
serviceNamespace = envName
} else if nsFromMeta := s.valueFromMetadata(om,
s.metadataSources.Annotations.ServiceNamespace,
s.metadataSources.Labels.ServiceNamespace,
ServiceNamespaceAnnotation,
s.resourceLabels["service.namespace"],
); nsFromMeta != "" {
serviceNamespace = nsFromMeta
}
Expand Down
8 changes: 4 additions & 4 deletions pkg/internal/kube/store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ func TestContainerInfo(t *testing.T) {

fInformer := &fakeInformer{}

store := NewStore(fInformer, MetadataSources{})
store := NewStore(fInformer, ResourceLabels{})

_ = store.On(&informer.Event{Type: informer.EventType_CREATED, Resource: &service})
_ = store.On(&informer.Event{Type: informer.EventType_CREATED, Resource: &podMetaA})
Expand Down Expand Up @@ -257,7 +257,7 @@ func TestMemoryCleanedUp(t *testing.T) {

fInformer := &fakeInformer{}

store := NewStore(fInformer, MetadataSources{})
store := NewStore(fInformer, ResourceLabels{})

_ = store.On(&informer.Event{Type: informer.EventType_CREATED, Resource: &service})
_ = store.On(&informer.Event{Type: informer.EventType_CREATED, Resource: &podMetaA})
Expand All @@ -281,7 +281,7 @@ func TestMemoryCleanedUp(t *testing.T) {
// Fixes a memory leak in the store where the objectMetaByIP map was not cleaned up
func TestMetaByIPEntryRemovedIfIPGroupChanges(t *testing.T) {
// GIVEN a store with
store := NewStore(&fakeInformer{}, MetadataSources{})
store := NewStore(&fakeInformer{}, ResourceLabels{})
// WHEN an object is created with several IPs
_ = store.On(&informer.Event{
Type: informer.EventType_CREATED,
Expand Down Expand Up @@ -326,7 +326,7 @@ func TestMetaByIPEntryRemovedIfIPGroupChanges(t *testing.T) {
}

func TestNoLeakOnUpdateOrDeletion(t *testing.T) {
store := NewStore(&fakeInformer{}, MetadataSources{})
store := NewStore(&fakeInformer{}, ResourceLabels{})
topOwner := &informer.Owner{Name: "foo", Kind: "Deployment"}
require.NoError(t, store.On(&informer.Event{
Type: informer.EventType_CREATED,
Expand Down
8 changes: 6 additions & 2 deletions pkg/transform/k8s.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,13 @@ type KubernetesDecorator struct {
// node as the Beyla instance. It will also restrict the Node information to the local node.
MetaRestrictLocalNode bool `yaml:"meta_restrict_local_node" env:"BEYLA_KUBE_META_RESTRICT_LOCAL_NODE"`

// MetadataSources allows Beyla overriding the service name and namespace of an application from
// MetaSourceLabels allows Beyla overriding the service name and namespace of an application from
// the given labels.
MetadataSources kube.MetadataSources `yaml:"meta_naming_sources"`
// Deprecated: kept for backwards-compatibility with Beyla 1.9
MetaSourceLabels kube.MetaSourceLabels `yaml:"meta_source_labels"`

// ResourceLabels allows Beyla overriding the OTEL Resource attributes from a map of user-defined labels.
ResourceLabels kube.ResourceLabels `yaml:"resource_labels"`
}

const (
Expand Down
12 changes: 3 additions & 9 deletions pkg/transform/k8s_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,9 @@ const timeout = 5 * time.Second

func TestDecoration(t *testing.T) {
inf := &fakeInformer{}
store := kube.NewStore(inf, kube.MetadataSources{
Annotations: kube.AnnotationSources{
ServiceName: []string{"resource.opentelemetry.io/service.name"},
ServiceNamespace: []string{"resource.opentelemetry.io/service.namespace"},
},
Labels: kube.LabelSources{
ServiceName: []string{"app.kubernetes.io/name"},
ServiceNamespace: []string{"app.kubernetes.io/part-of"},
},
store := kube.NewStore(inf, kube.ResourceLabels{
"service.name": []string{"app.kubernetes.io/name"},
"service.namespace": []string{"app.kubernetes.io/part-of"},
})
// pre-populated kubernetes metadata database
inf.Notify(&informer.Event{Type: informer.EventType_CREATED, Resource: &informer.ObjectMeta{
Expand Down
6 changes: 3 additions & 3 deletions pkg/transform/name_resolver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func TestSuffixPrefix(t *testing.T) {

func TestResolvePodsFromK8s(t *testing.T) {
inf := &fakeInformer{}
db := kube2.NewStore(inf, kube2.MetadataSources{})
db := kube2.NewStore(inf, kube2.ResourceLabels{})
pod1 := &informer.ObjectMeta{Name: "pod1", Kind: "Pod", Ips: []string{"10.0.0.1", "10.1.0.1"}}
pod2 := &informer.ObjectMeta{Name: "pod2", Namespace: "something", Kind: "Pod", Ips: []string{"10.0.0.2", "10.1.0.2"}}
pod3 := &informer.ObjectMeta{Name: "pod3", Kind: "Pod", Ips: []string{"10.0.0.3", "10.1.0.3"}}
Expand Down Expand Up @@ -104,7 +104,7 @@ func TestResolvePodsFromK8s(t *testing.T) {

func TestResolveServiceFromK8s(t *testing.T) {
inf := &fakeInformer{}
db := kube2.NewStore(inf, kube2.MetadataSources{})
db := kube2.NewStore(inf, kube2.ResourceLabels{})
pod1 := &informer.ObjectMeta{Name: "pod1", Kind: "Service", Ips: []string{"10.0.0.1", "10.1.0.1"}}
pod2 := &informer.ObjectMeta{Name: "pod2", Namespace: "something", Kind: "Service", Ips: []string{"10.0.0.2", "10.1.0.2"}}
pod3 := &informer.ObjectMeta{Name: "pod3", Kind: "Service", Ips: []string{"10.0.0.3", "10.1.0.3"}}
Expand Down Expand Up @@ -196,7 +196,7 @@ func TestCleanName(t *testing.T) {

func TestResolveNodesFromK8s(t *testing.T) {
inf := &fakeInformer{}
db := kube2.NewStore(inf, kube2.MetadataSources{})
db := kube2.NewStore(inf, kube2.ResourceLabels{})
node1 := &informer.ObjectMeta{Name: "node1", Kind: "Node", Ips: []string{"10.0.0.1", "10.1.0.1"}}
node2 := &informer.ObjectMeta{Name: "node2", Namespace: "something", Kind: "Node", Ips: []string{"10.0.0.2", "10.1.0.2"}}
node3 := &informer.ObjectMeta{Name: "node3", Kind: "Node", Ips: []string{"10.0.0.3", "10.1.0.3"}}
Expand Down
Loading

0 comments on commit cbb6303

Please sign in to comment.