Skip to content

Commit

Permalink
Create the config and namespace filterer (#457)
Browse files Browse the repository at this point in the history
* feat: add namespaceSelector config

* feat update values.yaml with namespaceSelector

* feat: add namespace filterer

* chore: bump chart to v.3.5.4

* feat: add namespace cache
  • Loading branch information
marcsanmi committed Jun 8, 2022
1 parent 3d25447 commit a42ee4f
Show file tree
Hide file tree
Showing 13 changed files with 724 additions and 16 deletions.
2 changes: 1 addition & 1 deletion charts/newrelic-infrastructure/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ sources:
- https://github.com/newrelic/nri-kubernetes/tree/master/charts/newrelic-infrastructure
- https://github.com/newrelic/infrastructure-agent/

version: 3.5.3
version: 3.5.4
appVersion: 3.2.0

dependencies:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ tests:
path: data.nri-kubernetes\.yml
value: |-
interval: 15s
namespaceSelector: {}
controlPlane:
retries: 3
timeout: 10s
Expand All @@ -38,6 +39,7 @@ tests:
path: data.nri-kubernetes\.yml
value: |-
interval: 15s
namespaceSelector: {}
controlPlane:
retries: 3
timeout: 10s
Expand Down Expand Up @@ -71,6 +73,7 @@ tests:
path: data.nri-kubernetes\.yml
value: |-
interval: 15s
namespaceSelector: {}
controlPlane:
retries: 3
timeout: 10s
Expand Down Expand Up @@ -142,6 +145,7 @@ tests:
path: data.nri-kubernetes\.yml
value: |-
interval: 15s
namespaceSelector: {}
controlPlane:
retries: 3
timeout: 10s
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ tests:
path: data.nri-kubernetes\.yml
value: |-
interval: 15s
namespaceSelector: {}
ksm:
enabled: true
port: 22
Expand All @@ -52,6 +53,7 @@ tests:
path: data.nri-kubernetes\.yml
value: |-
interval: 15s
namespaceSelector: {}
ksm:
enabled: true
retries: 3
Expand All @@ -70,6 +72,7 @@ tests:
path: data.nri-kubernetes\.yml
value: |-
interval: 15s
namespaceSelector: {}
ksm:
enabled: true
port: 22
Expand All @@ -94,6 +97,7 @@ tests:
path: data.nri-kubernetes\.yml
value: |-
interval: 15s
namespaceSelector: {}
ksm:
distributed: true
enabled: true
Expand Down
10 changes: 10 additions & 0 deletions charts/newrelic-infrastructure/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,16 @@ common:
# behave properly. Any non-nil value will override the `lowDataMode` default.
# @default -- `15s` (See [Low data mode](README.md#low-data-mode))
interval:
# -- Config for filtering ksm and kubelet metrics by namespace.
namespaceSelector: {}
# If you want to include only namespaces with a given label you could do so by adding:
# matchLabels:
# newrelic.com/scrape: true
# Otherwise you can build more complex filters and include or exclude certain namespaces by adding one or multiple
# expressions that are anded, for instance:
# matchExpressions:
# - {key: newrelic.com/scrape, operator: NotIn, values: [false]}

# -- Config for the Infrastructure agent.
# Will be used by the forwarder sidecars and the agent running integrations.
# See: https://docs.newrelic.com/docs/infrastructure/install-infrastructure-agent/configuration/infrastructure-agent-configuration-settings/
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ require (
github.com/spf13/cast v1.4.1 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/stretchr/objx v0.1.1 // indirect
github.com/subosito/gotenv v1.2.0 // indirect
golang.org/x/net v0.0.0-20220412020605-290c469a71a5 // indirect
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 // indirect
Expand Down
1 change: 1 addition & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,7 @@ github.com/spf13/viper v1.11.0 h1:7OX/1FS6n7jHD1zGrZTM7WtY13ZELRyosK4k93oPr44=
github.com/spf13/viper v1.11.0/go.mod h1:djo0X/bA5+tYVoCn+C7cAYJGcVn/qYLFTG8gdUsX7Zk=
github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
Expand Down
79 changes: 64 additions & 15 deletions internal/config/config.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package config

import (
"fmt"
"strconv"
"strings"
"time"

Expand Down Expand Up @@ -53,6 +55,9 @@ type Config struct {
Kubelet `mapstructure:"kubelet"`
// KSM defines config options for the kube-state-metrics scraper.
KSM `mapstructure:"ksm"`

// NamespaceSelector defines custom monitoring filtering for namespaces.
NamespaceSelector *NamespaceSelector `mapstructure:"namespaceSelector"`
}

// HTTPSink stores the configuration for the HTTP sink.
Expand Down Expand Up @@ -218,38 +223,82 @@ type MTLS struct {
TLSSecretNamespace string `mapstructure:"secretNamespace"`
}

// NamespaceSelector contains config options for filtering namespaces.
type NamespaceSelector struct {
// MatchLabels is a list of labels to filter namespaces with.
MatchLabels map[string]string `mapstructure:"matchLabels"`
// MatchExpressions is a list of namespaces selector requirements.
MatchExpressions []Expression `mapstructure:"matchExpressions"`
}

// Expression hold the values to generate an expression to filter namespaces by MatchExpressions.
type Expression struct {
// Key it the key of the label.
Key string `mapstructure:"key"`
// Operator holds either an inclusion (NotIn) or exclusion (In) value.
Operator string `mapstructure:"operator"`
// Values is a slice of values related to the key.
Values []interface{} `mapstructure:"values"`
}

func (e *Expression) String() (string, error) {
var values []string

for _, val := range e.Values {
var str string
switch v := val.(type) {
case string:
str = v
case bool:
str = strconv.FormatBool(v)
case int:
str = strconv.FormatInt(int64(v), 10)
case float32, float64:
str = fmt.Sprintf("%f", v)
default:
return "", fmt.Errorf("parsing expression invalid value: %v, type: %T", val, v)
}

values = append(values, str)
}

return fmt.Sprintf("%s %s (%s)", e.Key, strings.ToLower(e.Operator), strings.Join(values, ",")), nil
}

func LoadConfig(filePath string, fileName string) (*Config, error) {
v := viper.New()
// Update default delimiter as with the new namespaceSelector config, some labels may come in the form of
// newrelic.com/scrape, so the key was split in a sub-map on a "." basis.
v := viper.NewWithOptions(viper.KeyDelimiter("|"))

// We need to assure that defaults have been set in order to bind env variables.
// https://github.com/spf13/viper/issues/584
v.SetDefault("clusterName", "cluster")
v.SetDefault("verbose", false)
v.SetDefault("kubelet.networkRouteFile", DefaultNetworkRouteFile)
v.SetDefault("kubelet|networkRouteFile", DefaultNetworkRouteFile)
v.SetDefault("nodeName", "node")
v.SetDefault("nodeIP", "node")

// Sane connection defaults
v.SetDefault("sink.type", SinkTypeHTTP)
v.SetDefault("sink.http.port", 0)
v.SetDefault("sink.http.timeout", DefaultAgentTimeout)
v.SetDefault("sink.http.retries", DefaultRetries)
v.SetDefault("sink|type", SinkTypeHTTP)
v.SetDefault("sink|http|port", 0)
v.SetDefault("sink|http|timeout", DefaultAgentTimeout)
v.SetDefault("sink|http|retries", DefaultRetries)

v.SetDefault("kubelet.timeout", DefaultTimeout)
v.SetDefault("kubelet.retries", DefaultRetries)
v.SetDefault("kubelet|timeout", DefaultTimeout)
v.SetDefault("kubelet|retries", DefaultRetries)

v.SetDefault("controlPlane.timeout", DefaultTimeout)
v.SetDefault("controlPlane.retries", DefaultRetries)
v.SetDefault("controlPlane|timeout", DefaultTimeout)
v.SetDefault("controlPlane|retries", DefaultRetries)

v.SetDefault("ksm.timeout", DefaultTimeout)
v.SetDefault("ksm.retries", DefaultRetries)
v.SetDefault("ksm|timeout", DefaultTimeout)
v.SetDefault("ksm|retries", DefaultRetries)

v.SetDefault("ksm.discovery.backoffDelay", 7*time.Second)
v.SetDefault("ksm.discovery.timeout", 60*time.Second)
v.SetDefault("ksm|discovery|backoffDelay", 7*time.Second)
v.SetDefault("ksm|discovery|timeout", 60*time.Second)

v.SetEnvPrefix("NRI_KUBERNETES")
v.AutomaticEnv()
v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
v.SetEnvKeyReplacer(strings.NewReplacer("|", "_"))

// Config File
v.AddConfigPath(filePath)
Expand Down
9 changes: 9 additions & 0 deletions internal/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,15 @@ func TestLoadConfig(t *testing.T) {
require.Equal(t, "fake-node", c.NodeName)
})
})
// This test checks that viper custom key delimiter is working as expected by using the old default dot delimiter
// as key.
t.Run("succeeds_when_dot_character_in_key", func(t *testing.T) {
t.Parallel()

c, err := config.LoadConfig(fakeDataDir, workingData)
require.NoError(t, err)
require.Equal(t, "1", c.NamespaceSelector.MatchLabels["newrelic.com/scrape"])
})
t.Run("fail_due_to_unexpected_data", func(t *testing.T) {
t.Parallel()

Expand Down
4 changes: 4 additions & 0 deletions internal/config/testdata/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ clusterName: dummy_cluster
interval: 15
verbose: true

namespaceSelector:
matchLabels:
newrelic.com/scrape: true

sink:
http:
port: 8081
Expand Down
87 changes: 87 additions & 0 deletions internal/discovery/namespace_cache.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package discovery

import (
"sync"
"time"

"github.com/sirupsen/logrus"
)

const (
// DefaultInterval is default interval to execute the "garbage collection" of the cache.
DefaultInterval = 15 * time.Minute
)

type cachedData map[string]bool

// NamespaceCache provides an interface to add and retrieve namespaces from the cache.
type NamespaceCache interface {
Put(namespace string, match bool)
Match(namespace string) (bool, bool)
}

type NamespaceInMemoryStore struct {
cache cachedData
locker *sync.RWMutex
logger *logrus.Logger
lastVacuum time.Time
ticker *time.Ticker
stopChannel chan struct{}
}

func NewNamespaceInMemoryStore(interval time.Duration, logger *logrus.Logger) *NamespaceInMemoryStore {
cm := &NamespaceInMemoryStore{
cache: make(cachedData),
locker: &sync.RWMutex{},
logger: logger,
// ticker interval should be slightly smaller than the integration interval.
ticker: time.NewTicker(interval),
stopChannel: make(chan struct{}),
}

go func() {
for {
select {
case <-cm.ticker.C:
cm.vacuum()
case <-cm.stopChannel:
return
}
}
}()

return cm
}

func (m *NamespaceInMemoryStore) Put(namespace string, match bool) {
m.locker.Lock()
defer m.locker.Unlock()

m.cache[namespace] = match
}

func (m *NamespaceInMemoryStore) Match(namespace string) (bool, bool) {
m.locker.Lock()
defer m.locker.Unlock()

match, found := m.cache[namespace]

return match, found
}

// StopVacuum Stops the goroutine in charge of the vacuum of the cache.
func (m *NamespaceInMemoryStore) StopVacuum() {
m.logger.Debugf("stopping namespace cache vacuum goroutine")
m.ticker.Stop()
close(m.stopChannel)
}

// vacuum removes the cached data entries on each interval.
func (m *NamespaceInMemoryStore) vacuum() {
m.locker.Lock()
defer m.locker.Unlock()

m.logger.Debugf("cleaning cache: len %d ...", len(m.cache))
m.cache = make(cachedData)
m.logger.Debugf("cache cleaned: len %d ...", len(m.cache))
}
Loading

0 comments on commit a42ee4f

Please sign in to comment.