Skip to content

Commit

Permalink
remove network.allowed_attributes and use attributes.allow.<metric name>
Browse files Browse the repository at this point in the history
  • Loading branch information
mariomac committed Apr 23, 2024
1 parent 1455880 commit 8281bcb
Show file tree
Hide file tree
Showing 21 changed files with 382 additions and 237 deletions.
24 changes: 0 additions & 24 deletions docs/sources/network/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -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) |
Expand Down
17 changes: 9 additions & 8 deletions docs/sources/network/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Check warning on line 223 in docs/sources/network/quickstart.md

View workflow job for this annotation

GitHub Actions / vale

[vale] reported by reviewdog 🐶 [Grafana.AllowsTo] Did you mean 'allows you to' or 'makes it possible to' instead of 'allows to'? For more information, refer to https://grafana.com/docs/writers-toolkit/word-list#allows-to. If the rule is incorrect or needs improving, [report an issue](https://github.com/grafana/writers-toolkit/issues/new?title=Grafana.AllowsTo%20%3A%20%3CISSUE%3E). If you have reason to diverge from the style guidance, to skip a rule, refer to [Skip rules](https://grafana.com/docs/writers-toolkit/review/lint-prose/#skip-rules). Raw Output: {"message": "[Grafana.AllowsTo] Did you mean 'allows you to' or 'makes it possible to' instead of 'allows to'?\n\nFor more information, refer to https://grafana.com/docs/writers-toolkit/word-list#allows-to.\n\nIf the rule is incorrect or needs improving, [report an issue](https://github.com/grafana/writers-toolkit/issues/new?title=Grafana.AllowsTo%20%3A%20%3CISSUE%3E).\n\nIf you have reason to diverge from the style guidance, to skip a rule, refer to [Skip rules](https://grafana.com/docs/writers-toolkit/review/lint-prose/#skip-rules).", "location": {"path": "docs/sources/network/quickstart.md", "range": {"start": {"line": 223, "column": 40}}}, "severity": "WARNING"}

```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
Expand All @@ -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
Expand Down
10 changes: 4 additions & 6 deletions pkg/beyla/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
}

Expand Down
39 changes: 9 additions & 30 deletions pkg/beyla/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -42,6 +43,8 @@ attributes:
informers_sync_timeout: 30s
instance_id:
dns: true
allow:
global: ["foo", "bar"]
network:
enable: true
cidrs:
Expand Down Expand Up @@ -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{
Expand Down Expand Up @@ -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) {
Expand Down
42 changes: 0 additions & 42 deletions pkg/beyla/network_cfg.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,6 @@
package beyla

import (
"errors"
"log/slog"
"strings"
"time"

"github.com/grafana/beyla/pkg/internal/netolly/flow"
Expand Down Expand Up @@ -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.
Expand All @@ -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
}
83 changes: 83 additions & 0 deletions pkg/internal/export/attr/allowed.go
Original file line number Diff line number Diff line change
@@ -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
}
Loading

0 comments on commit 8281bcb

Please sign in to comment.