From 8281bcb8b8904dfc15533a7ddbde3f44972b059c Mon Sep 17 00:00:00 2001 From: Mario Macias Date: Tue, 23 Apr 2024 16:57:26 +0200 Subject: [PATCH] remove network.allowed_attributes and use attributes.allow. --- docs/sources/network/config.md | 24 ------ docs/sources/network/quickstart.md | 17 ++-- pkg/beyla/config.go | 10 +-- pkg/beyla/config_test.go | 39 ++------- pkg/beyla/network_cfg.go | 42 --------- pkg/internal/export/attr/allowed.go | 83 ++++++++++++++++++ pkg/internal/export/attr/allowed_test.go | 86 +++++++++++++++++++ pkg/internal/export/attr/attr.go | 23 +++-- pkg/internal/metricname/metricname.go | 20 +++++ pkg/internal/netolly/agent/pipeline.go | 6 +- pkg/internal/netolly/export/attributes.go | 4 +- pkg/internal/netolly/export/otel/metrics.go | 11 ++- pkg/internal/netolly/export/prom/prom.go | 17 +++- ...trumenter-config-netolly-disallowattrs.yml | 6 ++ .../configs/instrumenter-config-netolly.yml | 14 +++ test/integration/docker-compose-netolly.yml | 2 +- .../06-beyla-netolly-dropexternal.yml | 12 +-- .../manifests/06-beyla-netolly-promexport.yml | 63 +++++++------- .../06-beyla-netolly-sk-promexport.yml | 63 +++++++------- .../k8s/manifests/06-beyla-netolly.yml | 63 +++++++------- test/integration/suites_network_test.go | 14 ++- 21 files changed, 382 insertions(+), 237 deletions(-) create mode 100644 pkg/internal/export/attr/allowed.go create mode 100644 pkg/internal/export/attr/allowed_test.go create mode 100644 pkg/internal/metricname/metricname.go create mode 100644 test/integration/configs/instrumenter-config-netolly-disallowattrs.yml create mode 100644 test/integration/configs/instrumenter-config-netolly.yml diff --git a/docs/sources/network/config.md b/docs/sources/network/config.md index f2a051ee1..d561be80f 100644 --- a/docs/sources/network/config.md +++ b/docs/sources/network/config.md @@ -68,30 +68,6 @@ When `socket_filter` is used as an event source, Beyla installs an eBPF Linux so capture the network events. This mode doesn't conflict with Cilium CNI or other eBPF programs, which use the Linux Traffic Control egress and ingress filters. - -| YAML | Environment variable | Type | Default | -| -------------------- | ---------------------------------- | -------- | -------------------------------------------------------------------------------------------------------- | -| `allowed_attributes` | `BEYLA_NETWORK_ALLOWED_ATTRIBUTES` | []string | `k8s.src.owner.name`, `k8s.src.namespace`, `k8s.dst.owner.name`, `k8s.dst.namespace`, `k8s.cluster.name` | - -Specifies which attributes are visible in the metrics. -Beyla aggregates the metrics by their common visible attributes. -For example, hiding the `k8s.src.name` and allowing `k8s.src.owner.name` would aggregate the metrics of all the pods under the same owner. - -This property won't filter some meta-attributes such as `instance`, `job`, `service.instance.id`, `service_name`, `telemetry.sdk.*`, etc. - -See the [network metrics documentation]({{< relref "./_index.md" >}}) for a detailed list of all the available attributes. - -{{% admonition type="note" %}} -Select carefully the reported attributes, as some attributes might greatly increase the cardinality of your metrics. -Setting this value to list only the attributes you really need is highly recommended. -{{% /admonition %}} - -If you set this property via environment variable each entry must be separated by a comma, for example: - -```sh -BEYLA_NETWORK_ALLOWED_ATTRIBUTES=src.name,dst.name -``` - | YAML | Environment variable | Type | Default | | ------- | --------------------- | -------- | ------- | | `cidrs` | `BEYLA_NETWORK_CIDRS` | []string | (empty) | diff --git a/docs/sources/network/quickstart.md b/docs/sources/network/quickstart.md index c55c56914..4f41fcbb8 100644 --- a/docs/sources/network/quickstart.md +++ b/docs/sources/network/quickstart.md @@ -220,17 +220,18 @@ Beyla only includes a subset of the available attributes to avoid leading to a [cardinality explosion](/blog/2022/02/15/what-are-cardinality-spikes-and-why-do-they-matter/) in the metrics storage, especially if some attributes like `src.address` or `dst.address` capture the IP addresses of the external traffic. -The `allowed_attributes` YAML subsection under `network` (or the `BEYLA_NETWORK_ALLOWED_ATTRIBUTES` environment variable) -lets to select the attributes to report: +The `attributes.allow` YAML subsection allows to select the attributes to report: ```yaml network: enable: true - allowed_attributes: - - k8s.src.owner.name - - k8s.src.namespace - - k8s.dst.owner.name - - k8s.dst.namespace +attributes: + allow: + beyla.network.flow.bytes: + - k8s.src.owner.name + - k8s.src.namespace + - k8s.dst.owner.name + - k8s.dst.namespace ``` The previous example would aggregate the `beyla.network.flow.bytes` value by source and destination Kubernetes owner @@ -245,7 +246,7 @@ The `cidrs` YAML subsection in `network` (or the `BEYLA_NETWORK_CIDRS` environme subnets in [CIDR notation](https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing), in both IPv4 and IPv6 format. The existence of the `cidrs` section leaves the `src.address` and `dst.address` fields untouched, -and adds the `src.cidr` and `dst.cidr` attributes. Don't forget to add them to the `allowed_attributes` +and adds the `src.cidr` and `dst.cidr` attributes. Don't forget to add them to the `attributes.allow` section: ```yaml diff --git a/pkg/beyla/config.go b/pkg/beyla/config.go index 60e613c6e..f4b6c5340 100644 --- a/pkg/beyla/config.go +++ b/pkg/beyla/config.go @@ -11,6 +11,7 @@ import ( "gopkg.in/yaml.v3" ebpfcommon "github.com/grafana/beyla/pkg/internal/ebpf/common" + "github.com/grafana/beyla/pkg/internal/export/attr" "github.com/grafana/beyla/pkg/internal/export/debug" "github.com/grafana/beyla/pkg/internal/export/otel" "github.com/grafana/beyla/pkg/internal/export/prom" @@ -163,8 +164,9 @@ func (t TracesReceiverConfig) Enabled() bool { // Attributes configures the decoration of some extra attributes that will be // added to each span type Attributes struct { - Kubernetes transform.KubernetesDecorator `yaml:"kubernetes"` - InstanceID traces.InstanceIDConfig `yaml:"instance_id"` + Kubernetes transform.KubernetesDecorator `yaml:"kubernetes"` + InstanceID traces.InstanceIDConfig `yaml:"instance_id"` + Allow attr.AllowedAttributesDefinition `yaml:"allow"` } type ConfigError string @@ -204,10 +206,6 @@ func (c *Config) Validate() error { " grafana, otel_metrics_export, otel_traces_export or prometheus_export") } - if c.Enabled(FeatureNetO11y) { - return c.NetworkFlows.Validate(c.Attributes.Kubernetes.Enabled()) - } - return nil } diff --git a/pkg/beyla/config_test.go b/pkg/beyla/config_test.go index 9e072a8ee..7718bfcc6 100644 --- a/pkg/beyla/config_test.go +++ b/pkg/beyla/config_test.go @@ -15,6 +15,7 @@ import ( "github.com/grafana/beyla/pkg/internal/export/otel" "github.com/grafana/beyla/pkg/internal/export/prom" "github.com/grafana/beyla/pkg/internal/imetrics" + "github.com/grafana/beyla/pkg/internal/metricname" "github.com/grafana/beyla/pkg/internal/netolly/transform/cidr" "github.com/grafana/beyla/pkg/internal/traces" "github.com/grafana/beyla/pkg/transform" @@ -42,6 +43,8 @@ attributes: informers_sync_timeout: 30s instance_id: dns: true + allow: + global: ["foo", "bar"] network: enable: true cidrs: @@ -147,6 +150,9 @@ network: Enable: transform.EnabledTrue, InformersSyncTimeout: 30 * time.Second, }, + Allow: map[metricname.Normal][]string{ + "global": {"foo", "bar"}, + }, }, Routes: &transform.RoutesConfig{}, NameResolver: &transform.NameResolverConfig{ @@ -236,43 +242,16 @@ otel_metrics_export: attributes: kubernetes: enable: true -network: - enable: true - allowed_attributes: + allow: + beyla_network_flow_bytes: - k8s.src.name - k8s.dst.name -`) - cfg, err := LoadConfig(userConfig) - require.NoError(t, err) - require.NoError(t, cfg.Validate()) -} - -func TestConfigValidate_Network_Empty_Attrs(t *testing.T) { - userConfig := bytes.NewBufferString(` -otel_metrics_export: - endpoint: http://otelcol:4318 network: enable: true - allowed_attributes: [] `) cfg, err := LoadConfig(userConfig) require.NoError(t, err) - require.Error(t, cfg.Validate()) -} - -func TestConfigValidate_Network_NotKube(t *testing.T) { - userConfig := bytes.NewBufferString(` -otel_metrics_export: - endpoint: http://otelcol:4318 -network: - enable: true -allowed_attributes: - - k8s.src.name - - k8s.dst.name -`) - cfg, err := LoadConfig(userConfig) - require.NoError(t, err) - require.Error(t, cfg.Validate()) + require.NoError(t, cfg.Validate()) } func TestConfig_OtelGoAutoEnv(t *testing.T) { diff --git a/pkg/beyla/network_cfg.go b/pkg/beyla/network_cfg.go index ce1e936a6..f42f4124d 100644 --- a/pkg/beyla/network_cfg.go +++ b/pkg/beyla/network_cfg.go @@ -19,9 +19,6 @@ package beyla import ( - "errors" - "log/slog" - "strings" "time" "github.com/grafana/beyla/pkg/internal/netolly/flow" @@ -113,13 +110,6 @@ type NetworkConfig struct { // Print the network flows in the Standard Output, if true Print bool `yaml:"print_flows" env:"BEYLA_NETWORK_PRINT_FLOWS"` - // AllowedAttributes is a hidden/unstable/incomplete/epxerimental feature. This configuration API - // could change and be moved to other part, if we decide to extend this functionality also - // to AppO11y and Prometheus exporter. - // This won't filter some meta-attributes such as - // instance, job, service_instance_id, service_name, telemetry_sdk_*, etc... - AllowedAttributes []string `yaml:"allowed_attributes" env:"BEYLA_NETWORK_ALLOWED_ATTRIBUTES" envSeparator:","` - // CIDRs list, to be set as the "src.cidr" and "dst.cidr" // attribute as a function of the source and destination IP addresses. // If an IP does not match any address here, the attributes won't be set. @@ -140,41 +130,9 @@ var defaultNetworkConfig = NetworkConfig{ Direction: "both", ListenInterfaces: "watch", ListenPollPeriod: 10 * time.Second, - AllowedAttributes: []string{ - "k8s.src.owner.name", - "k8s.src.namespace", - "k8s.dst.owner.name", - "k8s.dst.namespace", - "k8s.cluster.name", - }, ReverseDNS: flow.ReverseDNS{ Type: flow.ReverseDNSNone, CacheLen: 256, CacheTTL: time.Hour, }, } - -func (nc *NetworkConfig) Validate(isKubeEnabled bool) error { - if len(nc.AllowedAttributes) == 0 { - return errors.New("you must define some attributes in the allowed_attributes section. Please check documentation") - } - if isKubeEnabled { - return nil - } - - actualAllowed := 0 - for _, attr := range nc.AllowedAttributes { - if !strings.HasPrefix(attr, "k8s.") { - actualAllowed++ - } - } - if actualAllowed == 0 { - return errors.New("allowed_attributes section (or its default) is only allowing Kubernetes metric attributes. " + - " You must define non-Kubernetes attributes there, or set BEYLA_KUBE_METADATA_ENABLE to true. Please check documentation") - } - if actualAllowed < len(nc.AllowedAttributes) { - slog.Warn("Network configuration allowed_attributes section is defining some Kubernetes attributes but " + - " Kubernetes metadata is disabled. Maybe you forgot to set BEYLA_KUBE_METADATA_ENABLE to true?") - } - return nil -} diff --git a/pkg/internal/export/attr/allowed.go b/pkg/internal/export/attr/allowed.go new file mode 100644 index 000000000..1cfa740dd --- /dev/null +++ b/pkg/internal/export/attr/allowed.go @@ -0,0 +1,83 @@ +package attr + +import ( + "strings" + + "golang.org/x/exp/maps" + + "github.com/grafana/beyla/pkg/internal/metricname" +) + +const globalKey = "global" + +// AllowedAttributesDefinition specifies which attributes are allowed for each metric. +// The key is the name of the metric (either in Prometheus or OpenTelemetry format) +// The value is the enumeration of allowed attributes +type AllowedAttributesDefinition map[metricname.Normal][]string + +var defaultAllowedAttributes = AllowedAttributesDefinition{ + metricname.NormalBeylaNetworkFlows: []string{ + "k8s.src.owner.name", + "k8s.src.namespace", + "k8s.dst.owner.name", + "k8s.dst.namespace", + "k8s.cluster.name", + }, +} + +// Normalize the user-provided input (error-prone, allowing multiple formats) for unified access +// from the code: +// - Convert underscores (prom-like) to dots (OTEL-like) +// - Remove metric suffixes such as .sum, .total, .bucket, etc... +// Only normalize the metric names, as the attribute names are already normalized in the +// PrometheusGetters and OpenTelemetryGetters function +func (aad AllowedAttributesDefinition) Normalize() { + if aad == nil { + return + } + normalized := map[metricname.Normal][]string{} + for metricName, allowedAttrs := range aad { + normalized[normalizeMetric(metricName)] = allowedAttrs + } + maps.Clear(aad) + maps.Copy(aad, normalized) +} + +func normalizeMetric(name metricname.Normal) metricname.Normal { + nameStr := strings.ReplaceAll(string(name), "_", ".") + for _, suffix := range []string{".bucket", ".sum", ".count", ".total"} { + if strings.HasSuffix(nameStr, suffix) { + nameStr = nameStr[:len(nameStr)-len(suffix)] + break + } + } + return metricname.Normal(nameStr) +} + +// For a given metric name, returns the allowed attributes from the following sources +// - If the "global" section is provided, returns its defined list of attribute names. +// - If the metric name section is provided, returns its defined list of attribute names. +// - If both the "global" and metric name sections are provided, merges both and returns +// a deduplicated list of attributes. +// - If none of the above exists, returns the value from the defaultAllowedAttributes, if any. +func (aad AllowedAttributesDefinition) For(metricName metricname.Normal) []string { + var deduped map[string]struct{} + if aad != nil { + deduped = map[string]struct{}{} + for _, attr := range aad[globalKey] { + deduped[attr] = struct{}{} + } + for _, attr := range aad[metricName] { + deduped[attr] = struct{}{} + } + } + // if no attributes are defined for a given metric, let's return the default attributes + if len(deduped) == 0 { + return defaultAllowedAttributes[metricName] + } + allowed := make([]string, 0, len(deduped)) + for attr := range deduped { + allowed = append(allowed, attr) + } + return allowed +} diff --git a/pkg/internal/export/attr/allowed_test.go b/pkg/internal/export/attr/allowed_test.go new file mode 100644 index 000000000..de07b4486 --- /dev/null +++ b/pkg/internal/export/attr/allowed_test.go @@ -0,0 +1,86 @@ +package attr + +import ( + "slices" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/grafana/beyla/pkg/internal/metricname" +) + +func TestNormalize(t *testing.T) { + aad := AllowedAttributesDefinition{ + "beyla_network_flow_bytes": []string{"foo", "bar"}, + "some.other.metric_sum": []string{"attr", "other"}, + "tralari.tralara.total": []string{"a1", "a2", "a3"}, + } + aad.Normalize() + assert.Equal(t, AllowedAttributesDefinition{ + "beyla.network.flow.bytes": []string{"foo", "bar"}, + "some.other.metric": []string{"attr", "other"}, + "tralari.tralara": []string{"a1", "a2", "a3"}, + }, aad) +} + +func TestFor(t *testing.T) { + aad := AllowedAttributesDefinition{ + "beyla.network.flow.bytes": []string{"foo", "bar"}, + "some.other.metric": []string{"attr", "other"}, + "tralari.tralara.total": []string{"a1", "a2", "a3"}, + } + aad.Normalize() + attrs := aad.For("beyla.network.flow.bytes") + slices.Sort(attrs) + assert.Equal(t, []string{"bar", "foo"}, attrs) + attrs = aad.For("some.other.metric") + slices.Sort(attrs) + assert.Equal(t, []string{"attr", "other"}, attrs) + attrs = aad.For("tralari.tralara") + slices.Sort(attrs) + assert.Equal(t, []string{"a1", "a2", "a3"}, attrs) + assert.Empty(t, aad.For("non.existing.metric")) +} + +func TestFor_GlobalDefinition(t *testing.T) { + aad := AllowedAttributesDefinition{ + "global": []string{"foo", "baz"}, + "beyla.network.flow.bytes": []string{"foo", "bar"}, + "some.other.metric": []string{"attr", "other"}, + "tralari.tralara.total": []string{"a1", "a2", "a3"}, + } + aad.Normalize() + + attrs := aad.For("beyla.network.flow.bytes") + slices.Sort(attrs) + assert.Equal(t, []string{"bar", "baz", "foo"}, attrs) + attrs = aad.For("some.other.metric") + slices.Sort(attrs) + assert.Equal(t, []string{"attr", "baz", "foo", "other"}, attrs) + attrs = aad.For("tralari.tralara") + slices.Sort(attrs) + assert.Equal(t, []string{"a1", "a2", "a3", "baz", "foo"}, attrs) + attrs = aad.For("not.defined.metric") + slices.Sort(attrs) + assert.Equal(t, []string{"baz", "foo"}, attrs) +} + +func TestNilDoesNotCrash(t *testing.T) { + var aad AllowedAttributesDefinition + assert.NotPanics(t, func() { + aad.Normalize() + assert.Empty(t, aad.For("some.metric")) + }) +} + +func TestDefault(t *testing.T) { + var aad AllowedAttributesDefinition + aad.Normalize() + assert.Equal(t, []string{ + "k8s.src.owner.name", + "k8s.src.namespace", + "k8s.dst.owner.name", + "k8s.dst.namespace", + "k8s.cluster.name", + }, aad.For(metricname.NormalBeylaNetworkFlows)) +} diff --git a/pkg/internal/export/attr/attr.go b/pkg/internal/export/attr/attr.go index 3e682f5d0..e4f043010 100644 --- a/pkg/internal/export/attr/attr.go +++ b/pkg/internal/export/attr/attr.go @@ -17,7 +17,8 @@ type Getter[T any] struct { } // NamedGetters returns the GetFunc for an attribute, given its internal name in dot.notation. -type NamedGetters[T any] func(internalName string) GetFunc[T] +// If the record does not provide any value for the given name, the second argument is false. +type NamedGetters[T any] func(internalName string) (GetFunc[T], bool) // PrometheusGetters builds a list of GetFunc getters for the names provided by the // user configuration, ready to be passed to a Prometheus exporter. @@ -31,10 +32,12 @@ func PrometheusGetters[T any](getter NamedGetters[T], names []string) []Getter[T for _, name := range names { exposedName := normalizeToUnderscore(name) internalName := strings.ReplaceAll(name, "_", ".") - attrs = append(attrs, Getter[T]{ - ExposedName: exposedName, - Get: getter(internalName), - }) + if get, ok := getter(internalName); ok { + attrs = append(attrs, Getter[T]{ + ExposedName: exposedName, + Get: get, + }) + } } return attrs } @@ -47,10 +50,12 @@ func OpenTelemetryGetters[T any](getter NamedGetters[T], names []string) []Gette attrs := make([]Getter[T], 0, len(names)) for _, name := range names { dotName := normalizeToDot(name) - attrs = append(attrs, Getter[T]{ - ExposedName: dotName, - Get: getter(dotName), - }) + if get, ok := getter(dotName); ok { + attrs = append(attrs, Getter[T]{ + ExposedName: dotName, + Get: get, + }) + } } return attrs } diff --git a/pkg/internal/metricname/metricname.go b/pkg/internal/metricname/metricname.go new file mode 100644 index 000000000..1e2ed9d31 --- /dev/null +++ b/pkg/internal/metricname/metricname.go @@ -0,0 +1,20 @@ +package metricname + +// Normal metric names use the dot.notation and suppress any .total .sum .count suffix +type Normal string + +const ( + NormalBeylaNetworkFlows = Normal("beyla.network.flow.bytes") +) + +// OTEL metrics define the names as being exposed by OpenTelemetry exporters + +const ( + OTELBeylaNetworkFlows = string(NormalBeylaNetworkFlows) + ".total" +) + +// Prom metrics define the names as being exposed by Prometheus exporter + +const ( + PromBeylaNetworkFlows = "beyla_network_flow_bytes_total" +) diff --git a/pkg/internal/netolly/agent/pipeline.go b/pkg/internal/netolly/agent/pipeline.go index ead0524ad..dd3657b1b 100644 --- a/pkg/internal/netolly/agent/pipeline.go +++ b/pkg/internal/netolly/agent/pipeline.go @@ -5,6 +5,7 @@ import ( "github.com/mariomac/pipes/pipe" + "github.com/grafana/beyla/pkg/internal/metricname" "github.com/grafana/beyla/pkg/internal/netolly/ebpf" "github.com/grafana/beyla/pkg/internal/netolly/export" "github.com/grafana/beyla/pkg/internal/netolly/export/otel" @@ -119,16 +120,17 @@ func (f *Flows) buildPipeline(ctx context.Context) (*pipe.Runner, error) { // Terminal nodes export the flow record information out of the pipeline: OTEL, Prom and printer. // Not all the nodes are mandatory here. Is the responsibility of each Provider function to decide // whether each node is going to be instantiated or just ignored. + f.cfg.Attributes.Allow.Normalize() pipe.AddFinalProvider(pb, otelExport, func() (pipe.FinalFunc[[]*ebpf.Record], error) { return otel.MetricsExporterProvider(&otel.MetricsConfig{ Metrics: &f.cfg.Metrics, - AllowedAttributes: f.cfg.NetworkFlows.AllowedAttributes, + AllowedAttributes: f.cfg.Attributes.Allow.For(metricname.NormalBeylaNetworkFlows), }) }) pipe.AddFinalProvider(pb, promExport, func() (pipe.FinalFunc[[]*ebpf.Record], error) { return prom.PrometheusEndpoint(ctx, &prom.PrometheusConfig{ Config: &f.cfg.Prometheus, - AllowedAttributes: f.cfg.NetworkFlows.AllowedAttributes, + AllowedAttributes: f.cfg.Attributes.Allow.For(metricname.NormalBeylaNetworkFlows), }, f.ctxInfo.Prometheus) }) pipe.AddFinalProvider(pb, printer, func() (pipe.FinalFunc[[]*ebpf.Record], error) { diff --git a/pkg/internal/netolly/export/attributes.go b/pkg/internal/netolly/export/attributes.go index 25eb7f223..8ad6e1da7 100644 --- a/pkg/internal/netolly/export/attributes.go +++ b/pkg/internal/netolly/export/attributes.go @@ -8,7 +8,7 @@ import ( "github.com/grafana/beyla/pkg/internal/netolly/flow/transport" ) -func NamedGetters(internalName string) attr.GetFunc[*ebpf.Record] { +func NamedGetters(internalName string) (attr.GetFunc[*ebpf.Record], bool) { var getter attr.GetFunc[*ebpf.Record] switch internalName { case "beyla.ip": @@ -34,7 +34,7 @@ func NamedGetters(internalName string) attr.GetFunc[*ebpf.Record] { default: getter = func(r *ebpf.Record) string { return r.Attrs.Metadata[internalName] } } - return getter + return getter, getter != nil } func directionStr(direction uint8) string { diff --git a/pkg/internal/netolly/export/otel/metrics.go b/pkg/internal/netolly/export/otel/metrics.go index 702aaadab..4c3f5d0e0 100644 --- a/pkg/internal/netolly/export/otel/metrics.go +++ b/pkg/internal/netolly/export/otel/metrics.go @@ -2,6 +2,7 @@ package otel import ( "context" + "fmt" "log/slog" "slices" "time" @@ -16,6 +17,7 @@ import ( "github.com/grafana/beyla/pkg/internal/export/attr" "github.com/grafana/beyla/pkg/internal/export/otel" + "github.com/grafana/beyla/pkg/internal/metricname" "github.com/grafana/beyla/pkg/internal/netolly/ebpf" "github.com/grafana/beyla/pkg/internal/netolly/export" ) @@ -81,11 +83,16 @@ func MetricsExporterProvider(cfg *MetricsConfig) (pipe.FinalFunc[[]*ebpf.Record] return nil, err } - expirer := NewExpirer(attr.OpenTelemetryGetters(export.NamedGetters, cfg.AllowedAttributes), cfg.Metrics.TTL) + attrs := attr.OpenTelemetryGetters(export.NamedGetters, cfg.AllowedAttributes) + if len(attrs) == 0 { + return nil, fmt.Errorf("network metrics OpenTelemetry exporter: no valid"+ + " attributes.allow defined for metric %s", metricname.PromBeylaNetworkFlows) + } + expirer := NewExpirer(attrs, cfg.Metrics.TTL) ebpfEvents := provider.Meter("network_ebpf_events") _, err = ebpfEvents.Int64ObservableCounter( - "beyla.network.flow.bytes.total", + metricname.OTELBeylaNetworkFlows, metric2.WithDescription("total bytes_sent value of network flows observed by probe since its launch"), metric2.WithUnit("{bytes}"), metric2.WithInt64Callback(expirer.Collect), diff --git a/pkg/internal/netolly/export/prom/prom.go b/pkg/internal/netolly/export/prom/prom.go index ba08ec3c5..c6e1a4685 100644 --- a/pkg/internal/netolly/export/prom/prom.go +++ b/pkg/internal/netolly/export/prom/prom.go @@ -2,6 +2,7 @@ package prom import ( "context" + "fmt" "slices" "github.com/mariomac/pipes/pipe" @@ -11,6 +12,7 @@ import ( "github.com/grafana/beyla/pkg/internal/export/attr" "github.com/grafana/beyla/pkg/internal/export/otel" "github.com/grafana/beyla/pkg/internal/export/prom" + "github.com/grafana/beyla/pkg/internal/metricname" "github.com/grafana/beyla/pkg/internal/netolly/ebpf" "github.com/grafana/beyla/pkg/internal/netolly/export" ) @@ -49,12 +51,19 @@ func PrometheusEndpoint(ctx context.Context, cfg *PrometheusConfig, promMgr *con // This node is not going to be instantiated. Let the pipes library just ignore it. return pipe.IgnoreFinal[[]*ebpf.Record](), nil } - reporter := newReporter(ctx, cfg, promMgr) + reporter, err := newReporter(ctx, cfg, promMgr) + if err != nil { + return nil, err + } return reporter.reportMetrics, nil } -func newReporter(ctx context.Context, cfg *PrometheusConfig, promMgr *connector.PrometheusManager) *metricsReporter { +func newReporter(ctx context.Context, cfg *PrometheusConfig, promMgr *connector.PrometheusManager) (*metricsReporter, error) { attrs := attr.PrometheusGetters(export.NamedGetters, cfg.AllowedAttributes) + if len(attrs) == 0 { + return nil, fmt.Errorf("network metrics Prometheus exporter: no valid"+ + " attributes.allow defined for metric %s", metricname.PromBeylaNetworkFlows) + } labelNames := make([]string, 0, len(attrs)) for _, label := range attrs { labelNames = append(labelNames, label.ExposedName) @@ -68,14 +77,14 @@ func newReporter(ctx context.Context, cfg *PrometheusConfig, promMgr *connector. promConnect: promMgr, attrs: attrs, flowBytes: NewExpirer(prometheus.NewCounterVec(prometheus.CounterOpts{ - Name: "beyla_network_flow_bytes_total", + Name: metricname.PromBeylaNetworkFlows, Help: "bytes submitted from a source network endpoint to a destination network endpoint", }, labelNames), cfg.Config.TTL), } mr.promConnect.Register(cfg.Config.Port, cfg.Config.Path, mr.flowBytes) - return mr + return mr, nil } func (r *metricsReporter) reportMetrics(input <-chan []*ebpf.Record) { diff --git a/test/integration/configs/instrumenter-config-netolly-disallowattrs.yml b/test/integration/configs/instrumenter-config-netolly-disallowattrs.yml new file mode 100644 index 000000000..36eb90d0e --- /dev/null +++ b/test/integration/configs/instrumenter-config-netolly-disallowattrs.yml @@ -0,0 +1,6 @@ +attributes: + allow: + beyla_network_flow_bytes: + - beyla.ip + - src.name + - dst.port diff --git a/test/integration/configs/instrumenter-config-netolly.yml b/test/integration/configs/instrumenter-config-netolly.yml new file mode 100644 index 000000000..65312be64 --- /dev/null +++ b/test/integration/configs/instrumenter-config-netolly.yml @@ -0,0 +1,14 @@ +attributes: + allow: + beyla_network_flow_bytes: + - beyla.ip + - src.address + - dst.address + - src.name + - dst.name + - src.namespace + - dst.namespace + - src.cidr + - dst.cidr + - iface + - direction diff --git a/test/integration/docker-compose-netolly.yml b/test/integration/docker-compose-netolly.yml index 28ffffedc..c616a4570 100644 --- a/test/integration/docker-compose-netolly.yml +++ b/test/integration/docker-compose-netolly.yml @@ -29,12 +29,12 @@ services: privileged: true network_mode: service:testserver environment: + BEYLA_CONFIG_PATH: /configs/instrumenter-config-netolly${BEYLA_CONFIG_SUFFIX}.yml GOCOVERDIR: "/coverage" BEYLA_NETWORK_SOURCE: ${BEYLA_NETWORK_SOURCE} BEYLA_NETWORK_METRICS: "true" BEYLA_NETWORK_PRINT_FLOWS: "true" BEYLA_NETWORK_DEDUPER: ${BEYLA_NETWORK_DEDUPER} - BEYLA_NETWORK_ALLOWED_ATTRIBUTES: ${BEYLA_NETWORK_ALLOWED_ATTRIBUTES} OTEL_EXPORTER_OTLP_ENDPOINT: http://otelcol:4318 BEYLA_LOG_LEVEL: "DEBUG" BEYLA_BPF_DEBUG: "TRUE" diff --git a/test/integration/k8s/manifests/06-beyla-netolly-dropexternal.yml b/test/integration/k8s/manifests/06-beyla-netolly-dropexternal.yml index 9ed17693c..f82b9612a 100644 --- a/test/integration/k8s/manifests/06-beyla-netolly-dropexternal.yml +++ b/test/integration/k8s/manifests/06-beyla-netolly-dropexternal.yml @@ -9,15 +9,15 @@ data: enable: true cluster_name: my-kube drop_external: true + allow: + beyla.network.flow.bytes: + - src.name + - dst.name + - k8s.src.owner.name + - k8s.dst.owner.name log_level: debug otel_metrics_export: endpoint: http://otelcol.default:4317 - network: - allowed_attributes: - - src.name - - dst.name - - k8s.src.owner.name - - k8s.dst.owner.name --- apiVersion: apps/v1 kind: DaemonSet diff --git a/test/integration/k8s/manifests/06-beyla-netolly-promexport.yml b/test/integration/k8s/manifests/06-beyla-netolly-promexport.yml index 73746857b..ba9b36e33 100644 --- a/test/integration/k8s/manifests/06-beyla-netolly-promexport.yml +++ b/test/integration/k8s/manifests/06-beyla-netolly-promexport.yml @@ -4,10 +4,6 @@ metadata: name: beyla-config data: beyla-config.yml: | - attributes: - kubernetes: - enable: true - cluster_name: my-kube log_level: debug prometheus_export: port: 8999 @@ -20,33 +16,38 @@ data: - fd00:10:244::/56 - 10.96.0.0/16 - fd00:10:96::/112 - allowed_attributes: - # assured cardinality explosion. Don't try in production! - - transport - - beyla.ip - - src.address - - dst.address - - src.name - - dst.name - - src.namespace - - dst.namespace - - src.cidr - - dst.cidr - - k8s.src.namespace - - k8s.dst.namespace - - k8s.src.name - - k8s.dst.name - - k8s.src.type - - k8s.dst.type - - k8s.src.owner.name - - k8s.dst.owner.name - - k8s.src.owner.type - - k8s.dst.owner.type - - k8s.src.node.ip - - k8s.dst.node.ip - - k8s.src.node.name - - k8s.dst.node.name - - k8s.cluster.name + attributes: + kubernetes: + enable: true + cluster_name: my-kube + allow: + beyla.network.flow.bytes: + # assured cardinality explosion. Don't try in production! + - transport + - beyla.ip + - src.address + - dst.address + - src.name + - dst.name + - src.namespace + - dst.namespace + - src.cidr + - dst.cidr + - k8s.src.namespace + - k8s.dst.namespace + - k8s.src.name + - k8s.dst.name + - k8s.src.type + - k8s.dst.type + - k8s.src.owner.name + - k8s.dst.owner.name + - k8s.src.owner.type + - k8s.dst.owner.type + - k8s.src.node.ip + - k8s.dst.node.ip + - k8s.src.node.name + - k8s.dst.node.name + - k8s.cluster.name --- kind: Service apiVersion: v1 diff --git a/test/integration/k8s/manifests/06-beyla-netolly-sk-promexport.yml b/test/integration/k8s/manifests/06-beyla-netolly-sk-promexport.yml index 3e7ca2703..6fedffda2 100644 --- a/test/integration/k8s/manifests/06-beyla-netolly-sk-promexport.yml +++ b/test/integration/k8s/manifests/06-beyla-netolly-sk-promexport.yml @@ -4,10 +4,6 @@ metadata: name: beyla-config data: beyla-config.yml: | - attributes: - kubernetes: - enable: true - cluster_name: my-kube log_level: debug prometheus_export: port: 8999 @@ -20,33 +16,38 @@ data: - fd00:10:244::/56 - 10.96.0.0/16 - fd00:10:96::/112 - allowed_attributes: - # assured cardinality explosion. Don't try in production! - - transport - - beyla.ip - - src.address - - dst.address - - src.name - - dst.name - - src.namespace - - dst.namespace - - src.cidr - - dst.cidr - - k8s.src.namespace - - k8s.dst.namespace - - k8s.src.name - - k8s.dst.name - - k8s.src.type - - k8s.dst.type - - k8s.src.owner.name - - k8s.dst.owner.name - - k8s.src.owner.type - - k8s.dst.owner.type - - k8s.src.node.ip - - k8s.dst.node.ip - - k8s.src.node.name - - k8s.dst.node.name - - k8s.cluster.name + attributes: + kubernetes: + enable: true + cluster_name: my-kube + allow: + beyla.network.flow.bytes: + # assured cardinality explosion. Don't try in production! + - transport + - beyla.ip + - src.address + - dst.address + - src.name + - dst.name + - src.namespace + - dst.namespace + - src.cidr + - dst.cidr + - k8s.src.namespace + - k8s.dst.namespace + - k8s.src.name + - k8s.dst.name + - k8s.src.type + - k8s.dst.type + - k8s.src.owner.name + - k8s.dst.owner.name + - k8s.src.owner.type + - k8s.dst.owner.type + - k8s.src.node.ip + - k8s.dst.node.ip + - k8s.src.node.name + - k8s.dst.node.name + - k8s.cluster.name --- kind: Service apiVersion: v1 diff --git a/test/integration/k8s/manifests/06-beyla-netolly.yml b/test/integration/k8s/manifests/06-beyla-netolly.yml index f2222ffbb..4ebcaef82 100644 --- a/test/integration/k8s/manifests/06-beyla-netolly.yml +++ b/test/integration/k8s/manifests/06-beyla-netolly.yml @@ -4,10 +4,6 @@ metadata: name: beyla-config data: beyla-config.yml: | - attributes: - kubernetes: - enable: true - cluster_name: my-kube log_level: debug otel_metrics_export: endpoint: http://otelcol.default:4317 @@ -20,33 +16,38 @@ data: - fd00:10:244::/56 - 10.96.0.0/16 - fd00:10:96::/112 - allowed_attributes: - # assured cardinality explosion. Don't try in production! - - transport - - beyla.ip - - src.address - - dst.address - - src.name - - dst.name - - src.namespace - - dst.namespace - - src.cidr - - dst.cidr - - k8s.src.namespace - - k8s.dst.namespace - - k8s.src.name - - k8s.dst.name - - k8s.src.type - - k8s.dst.type - - k8s.src.owner.name - - k8s.dst.owner.name - - k8s.src.owner.type - - k8s.dst.owner.type - - k8s.src.node.ip - - k8s.dst.node.ip - - k8s.src.node.name - - k8s.dst.node.name - - k8s.cluster.name + attributes: + kubernetes: + enable: true + cluster_name: my-kube + allow: + beyla.network.flow.bytes: + # assured cardinality explosion. Don't try in production! + - transport + - beyla.ip + - src.address + - dst.address + - src.name + - dst.name + - src.namespace + - dst.namespace + - src.cidr + - dst.cidr + - k8s.src.namespace + - k8s.dst.namespace + - k8s.src.name + - k8s.dst.name + - k8s.src.type + - k8s.dst.type + - k8s.src.owner.name + - k8s.dst.owner.name + - k8s.src.owner.type + - k8s.dst.owner.type + - k8s.src.node.ip + - k8s.dst.node.ip + - k8s.src.node.name + - k8s.dst.node.name + - k8s.cluster.name --- apiVersion: apps/v1 kind: DaemonSet diff --git a/test/integration/suites_network_test.go b/test/integration/suites_network_test.go index bfd8df0cb..6bf36f0db 100644 --- a/test/integration/suites_network_test.go +++ b/test/integration/suites_network_test.go @@ -17,12 +17,9 @@ import ( "github.com/grafana/beyla/test/integration/components/prom" ) -const allowAllAttrs = "BEYLA_NETWORK_ALLOWED_ATTRIBUTES=beyla.ip,src.address,dst.address,src.name,dst.name," + - "src.namespace,dst.namespace,src.cidr,dst.cidr,iface,direction" - func TestNetwork_Deduplication(t *testing.T) { compose, err := docker.ComposeSuite("docker-compose-netolly.yml", path.Join(pathOutput, "test-suite-netolly-dedupe.log")) - compose.Env = append(compose.Env, "BEYLA_NETWORK_DEDUPER=first_come", "BEYLA_EXECUTABLE_NAME=", allowAllAttrs) + compose.Env = append(compose.Env, "BEYLA_NETWORK_DEDUPER=first_come", "BEYLA_EXECUTABLE_NAME=") require.NoError(t, err) require.NoError(t, compose.Up()) @@ -37,7 +34,7 @@ func TestNetwork_Deduplication(t *testing.T) { func TestNetwork_Deduplication_Use_Socket_Filter(t *testing.T) { compose, err := docker.ComposeSuite("docker-compose-netolly.yml", path.Join(pathOutput, "test-suite-netolly-dedupe-no-tc.log")) - compose.Env = append(compose.Env, "BEYLA_NETWORK_DEDUPER=first_come", "BEYLA_EXECUTABLE_NAME=", "BEYLA_NETWORK_SOURCE=socket_filter", allowAllAttrs) + compose.Env = append(compose.Env, "BEYLA_NETWORK_DEDUPER=first_come", "BEYLA_EXECUTABLE_NAME=", "BEYLA_NETWORK_SOURCE=socket_filter") require.NoError(t, err) require.NoError(t, compose.Up()) @@ -52,7 +49,7 @@ func TestNetwork_Deduplication_Use_Socket_Filter(t *testing.T) { func TestNetwork_NoDeduplication(t *testing.T) { compose, err := docker.ComposeSuite("docker-compose-netolly.yml", path.Join(pathOutput, "test-suite-netolly-nodedupe.log")) - compose.Env = append(compose.Env, "BEYLA_NETWORK_DEDUPER=none", "BEYLA_EXECUTABLE_NAME=", allowAllAttrs) + compose.Env = append(compose.Env, "BEYLA_NETWORK_DEDUPER=none", "BEYLA_EXECUTABLE_NAME=") require.NoError(t, err) require.NoError(t, compose.Up()) @@ -70,11 +67,12 @@ func TestNetwork_NoDeduplication(t *testing.T) { func TestNetwork_AllowedAttributes(t *testing.T) { compose, err := docker.ComposeSuite("docker-compose-netolly.yml", path.Join(pathOutput, "test-suite-netolly-allowed-attrs.log")) - compose.Env = append(compose.Env, "BEYLA_EXECUTABLE_NAME=", `BEYLA_NETWORK_ALLOWED_ATTRIBUTES=beyla.ip,src.name,dst.port`) + compose.Env = append(compose.Env, "BEYLA_EXECUTABLE_NAME=", `BEYLA_CONFIG_SUFFIX=-disallowattrs`) require.NoError(t, err) require.NoError(t, compose.Up()) - // When there flow deduplication, results must only include the BEYLA_NETWORK_ALLOWED_ATTRIBUTES + // When there flow deduplication, results must only include + // the attributes under the attributes.allow section for _, f := range getNetFlows(t) { require.Contains(t, f.Metric, "beyla_ip") require.Contains(t, f.Metric, "src_name")