Skip to content

Commit

Permalink
Add exclusion lists to discovery section (#1052)
Browse files Browse the repository at this point in the history
  • Loading branch information
mariomac authored Jul 24, 2024
1 parent aff44d7 commit 3c72c27
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 10 deletions.
3 changes: 3 additions & 0 deletions pkg/beyla/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,9 @@ func (c *Config) Validate() error {
if err := c.Discovery.Services.Validate(); err != nil {
return ConfigError(fmt.Sprintf("error in services YAML property: %s", err.Error()))
}
if err := c.Discovery.ExcludeServices.Validate(); err != nil {
return ConfigError(fmt.Sprintf("error in exclude_services YAML property: %s", err.Error()))
}
if !c.Enabled(FeatureNetO11y) && !c.Enabled(FeatureAppO11y) {
return ConfigError("missing at least one of BEYLA_NETWORK_METRICS, BEYLA_EXECUTABLE_NAME or BEYLA_OPEN_PORT property")
}
Expand Down
25 changes: 18 additions & 7 deletions pkg/internal/discover/matcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,19 @@ import (
func CriteriaMatcherProvider(cfg *beyla.Config) pipe.MiddleProvider[[]Event[processAttrs], []Event[ProcessMatch]] {
return func() (pipe.MiddleFunc[[]Event[processAttrs], []Event[ProcessMatch]], error) {
m := &matcher{
log: slog.With("component", "discover.CriteriaMatcher"),
criteria: FindingCriteria(cfg),
processHistory: map[PID]*services.ProcessInfo{},
log: slog.With("component", "discover.CriteriaMatcher"),
criteria: FindingCriteria(cfg),
excludeCriteria: cfg.Discovery.ExcludeServices,
processHistory: map[PID]*services.ProcessInfo{},
}
return m.run, nil
}
}

type matcher struct {
log *slog.Logger
criteria services.DefinitionCriteria
log *slog.Logger
criteria services.DefinitionCriteria
excludeCriteria services.DefinitionCriteria
// processHistory keeps track of the processes that have been already matched and submitted for
// instrumentation.
// This avoids keep inspecting again and again client processes each time they open a new connection port
Expand Down Expand Up @@ -81,7 +83,7 @@ func (m *matcher) filterCreated(obj processAttrs) (Event[ProcessMatch], bool) {
return Event[ProcessMatch]{}, false
}
for i := range m.criteria {
if m.matchProcess(&obj, proc, &m.criteria[i]) {
if m.matchProcess(&obj, proc, &m.criteria[i]) && !m.isExcluded(&obj, proc) {
m.log.Debug("found process", "pid", proc.Pid, "comm", proc.ExePath, "metadata", obj.metadata, "podLabels", obj.podLabels)
m.processHistory[obj.pid] = proc
return Event[ProcessMatch]{
Expand Down Expand Up @@ -118,8 +120,17 @@ func (m *matcher) filterDeleted(obj processAttrs) (Event[ProcessMatch], bool) {
}, true
}

func (m *matcher) isExcluded(obj *processAttrs, proc *services.ProcessInfo) bool {
for i := range m.excludeCriteria {
if m.matchProcess(obj, proc, &m.excludeCriteria[i]) {
return true
}
}
return false
}

func (m *matcher) matchProcess(obj *processAttrs, p *services.ProcessInfo, a *services.Attributes) bool {
if !a.Path.IsSet() && a.OpenPorts.Len() == 0 {
if !a.Path.IsSet() && a.OpenPorts.Len() == 0 && len(obj.metadata) == 0 && len(obj.metadata) == 0 {
return false
}
if (a.Path.IsSet() || a.PathRegexp.IsSet()) && !m.matchByExecutable(p, a) {
Expand Down
97 changes: 97 additions & 0 deletions pkg/internal/discover/matcher_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,103 @@ func TestCriteriaMatcher(t *testing.T) {
assert.Equal(t, services.ProcessInfo{Pid: 6, ExePath: "/bin/clientweird99"}, *m.Obj.Process)
}

func TestCriteriaMatcher_Exclude(t *testing.T) {
pipeConfig := beyla.Config{}
require.NoError(t, yaml.Unmarshal([]byte(`discovery:
services:
- name: port-only
namespace: foo
open_ports: 80,8080-8089
- name: exec-only
exe_path: weird\d
- name: both
open_ports: 443
exe_path_regexp: "server"
exclude_services:
- exe_path: s
`), &pipeConfig))

matcherFunc, err := CriteriaMatcherProvider(&pipeConfig)()
require.NoError(t, err)
discoveredProcesses := make(chan []Event[processAttrs], 10)
filteredProcesses := make(chan []Event[ProcessMatch], 10)
go matcherFunc(discoveredProcesses, filteredProcesses)
defer close(discoveredProcesses)

// it will filter unmatching processes and return a ProcessMatch for these that match
processInfo = func(pp processAttrs) (*services.ProcessInfo, error) {
exePath := map[PID]string{
1: "/bin/weird33", 2: "/bin/weird33", 3: "server",
4: "/bin/something", 5: "server", 6: "/bin/clientweird99"}[pp.pid]
return &services.ProcessInfo{Pid: int32(pp.pid), ExePath: exePath, OpenPorts: pp.openPorts}, nil
}
discoveredProcesses <- []Event[processAttrs]{
{Type: EventCreated, Obj: processAttrs{pid: 1, openPorts: []uint32{1, 2, 3}}}, // pass
{Type: EventDeleted, Obj: processAttrs{pid: 2, openPorts: []uint32{4}}}, // filter
{Type: EventCreated, Obj: processAttrs{pid: 3, openPorts: []uint32{8433}}}, // filter
{Type: EventCreated, Obj: processAttrs{pid: 4, openPorts: []uint32{8083}}}, // filter (in exclude)
{Type: EventCreated, Obj: processAttrs{pid: 5, openPorts: []uint32{443}}}, // filter (in exclude)
{Type: EventCreated, Obj: processAttrs{pid: 6}}, // pass
}

matches := testutil.ReadChannel(t, filteredProcesses, testTimeout)
require.Len(t, matches, 2)
m := matches[0]
assert.Equal(t, EventCreated, m.Type)
assert.Equal(t, "exec-only", m.Obj.Criteria.Name)
assert.Equal(t, "", m.Obj.Criteria.Namespace)
assert.Equal(t, services.ProcessInfo{Pid: 1, ExePath: "/bin/weird33", OpenPorts: []uint32{1, 2, 3}}, *m.Obj.Process)
m = matches[1]
assert.Equal(t, EventCreated, m.Type)
assert.Equal(t, "exec-only", m.Obj.Criteria.Name)
assert.Equal(t, "", m.Obj.Criteria.Namespace)
assert.Equal(t, services.ProcessInfo{Pid: 6, ExePath: "/bin/clientweird99"}, *m.Obj.Process)
}

func TestCriteriaMatcher_Exclude_Metadata(t *testing.T) {
pipeConfig := beyla.Config{}
require.NoError(t, yaml.Unmarshal([]byte(`discovery:
services:
- k8s_node_name: .
exclude_services:
- k8s_node_name: bar
`), &pipeConfig))

matcherFunc, err := CriteriaMatcherProvider(&pipeConfig)()
require.NoError(t, err)
discoveredProcesses := make(chan []Event[processAttrs], 10)
filteredProcesses := make(chan []Event[ProcessMatch], 10)
go matcherFunc(discoveredProcesses, filteredProcesses)
defer close(discoveredProcesses)

// it will filter unmatching processes and return a ProcessMatch for these that match
processInfo = func(pp processAttrs) (*services.ProcessInfo, error) {
exePath := map[PID]string{
1: "/bin/weird33", 2: "/bin/weird33", 3: "server",
4: "/bin/something", 5: "server", 6: "/bin/clientweird99"}[pp.pid]
return &services.ProcessInfo{Pid: int32(pp.pid), ExePath: exePath, OpenPorts: pp.openPorts}, nil
}
nodeFoo := map[string]string{"k8s_node_name": "foo"}
nodeBar := map[string]string{"k8s_node_name": "bar"}
discoveredProcesses <- []Event[processAttrs]{
{Type: EventCreated, Obj: processAttrs{pid: 1, metadata: nodeFoo}}, // pass
{Type: EventDeleted, Obj: processAttrs{pid: 2, metadata: nodeFoo}}, // filter
{Type: EventCreated, Obj: processAttrs{pid: 3, metadata: nodeFoo}}, // pass
{Type: EventCreated, Obj: processAttrs{pid: 4, metadata: nodeBar}}, // filter (in exclude)
{Type: EventDeleted, Obj: processAttrs{pid: 5, metadata: nodeFoo}}, // filter
{Type: EventCreated, Obj: processAttrs{pid: 6, metadata: nodeBar}}, // filter (in exclude)
}

matches := testutil.ReadChannel(t, filteredProcesses, 1000*testTimeout)
require.Len(t, matches, 2)
m := matches[0]
assert.Equal(t, EventCreated, m.Type)
assert.Equal(t, services.ProcessInfo{Pid: 1, ExePath: "/bin/weird33"}, *m.Obj.Process)
m = matches[1]
assert.Equal(t, EventCreated, m.Type)
assert.Equal(t, services.ProcessInfo{Pid: 3, ExePath: "server"}, *m.Obj.Process)
}

func TestCriteriaMatcher_MustMatchAllAttributes(t *testing.T) {
pipeConfig := beyla.Config{}
require.NoError(t, yaml.Unmarshal([]byte(`discovery:
Expand Down
4 changes: 4 additions & 0 deletions pkg/services/criteria.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ type DiscoveryConfig struct {
// added to the services definition criteria, with the lowest preference.
Services DefinitionCriteria `yaml:"services"`

// ExcludeServices works analogously to Services, but the applications matching this section won't be instrumented
// even if they match the Services selection.
ExcludeServices DefinitionCriteria `yaml:"exclude_services"`

// PollInterval specifies, for the poll service watcher, the interval time between
// process inspections
PollInterval time.Duration `yaml:"poll_interval" env:"BEYLA_DISCOVERY_POLL_INTERVAL"`
Expand Down
8 changes: 5 additions & 3 deletions test/integration/k8s/manifests/06-beyla-daemonset.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@ data:
log_level: debug
discovery:
services:
# testing that K8s discovery only picks the "otherinstance"
# deployment (ignoring the "testserver" in the other pod)
# name and namespace will be automatically set from the K8s metadata
- k8s_deployment_name: otherinstance
- k8s_deployment_name: (otherinstance|testserver)
# used in the k8s_owners_*_test.go
- k8s_statefulset_name: statefulservice
- k8s_daemonset_name: dsservice
exclude_services:
# testing service exclusion, by expecting that K8s discovery only picks the "otherinstance"
# deployment (ignoring the "testserver" in the other pod)
- k8s_deployment_name: testserver
routes:
patterns:
- /pingpong
Expand Down

0 comments on commit 3c72c27

Please sign in to comment.