Skip to content

Commit

Permalink
Inject K8S_NODE_NAME environment variable when using the kubeletstats…
Browse files Browse the repository at this point in the history
… receiver (#3389)

* Add automatic RBAC creation for kubeletstats receiver

Signed-off-by: Israel Blancas <[email protected]>

* Inject K8S_NODE_NAME environment variable when using the kubeletstats receiver

Signed-off-by: Israel Blancas <[email protected]>

* Revert change

Signed-off-by: Israel Blancas <[email protected]>

* Fix lint

Signed-off-by: Israel Blancas <[email protected]>

* Add missing tests

Signed-off-by: Israel Blancas <[email protected]>

* Remove debug statement

Signed-off-by: Israel Blancas <[email protected]>

---------

Signed-off-by: Israel Blancas <[email protected]>
  • Loading branch information
iblancasa authored Nov 1, 2024
1 parent b563a6e commit fa0f54a
Show file tree
Hide file tree
Showing 11 changed files with 184 additions and 1 deletion.
16 changes: 16 additions & 0 deletions .chloggen/2779-kubeletstatsreiver-inject-en-vars.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
change_type: enhancement

# The name of the component, or a single word describing the area of concern, (e.g. collector, target allocator, auto-instrumentation, opamp, github action)
component: collector

# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
note: Inject environment K8S_NODE_NAME environment variable for the Kubelet Stats Receiver.

# One or more tracking issues related to the change
issues: [2779]

# (Optional) One or more lines of additional information to render under the primary note.
# These lines will be padded with 2 spaces and then inserted directly into the document.
# Use pipe (|) for multiline entries.
subtext:
40 changes: 40 additions & 0 deletions apis/v1beta1/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,42 @@ func (c *Config) getPortsForComponentKinds(logger logr.Logger, componentKinds ..
return ports, nil
}

// getEnvironmentVariablesForComponentKinds gets the environment variables for the given ComponentKind(s).
func (c *Config) getEnvironmentVariablesForComponentKinds(logger logr.Logger, componentKinds ...ComponentKind) ([]corev1.EnvVar, error) {
var envVars []corev1.EnvVar = []corev1.EnvVar{}
enabledComponents := c.GetEnabledComponents()
for _, componentKind := range componentKinds {
var retriever components.ParserRetriever
var cfg AnyConfig

switch componentKind {
case KindReceiver:
retriever = receivers.ReceiverFor
cfg = c.Receivers
case KindExporter:
continue
case KindProcessor:
continue
case KindExtension:
continue
}
for componentName := range enabledComponents[componentKind] {
parser := retriever(componentName)
if parsedEnvVars, err := parser.GetEnvironmentVariables(logger, cfg.Object[componentName]); err != nil {
return nil, err
} else {
envVars = append(envVars, parsedEnvVars...)
}
}
}

sort.Slice(envVars, func(i, j int) bool {
return envVars[i].Name < envVars[j].Name
})

return envVars, nil
}

// applyDefaultForComponentKinds applies defaults to the endpoints for the given ComponentKind(s).
func (c *Config) applyDefaultForComponentKinds(logger logr.Logger, componentKinds ...ComponentKind) error {
if err := c.Service.ApplyDefaults(); err != nil {
Expand Down Expand Up @@ -286,6 +322,10 @@ func (c *Config) GetAllPorts(logger logr.Logger) ([]corev1.ServicePort, error) {
return c.getPortsForComponentKinds(logger, KindReceiver, KindExporter)
}

func (c *Config) GetEnvironmentVariables(logger logr.Logger) ([]corev1.EnvVar, error) {
return c.getEnvironmentVariablesForComponentKinds(logger, KindReceiver)
}

func (c *Config) GetAllRbacRules(logger logr.Logger) ([]rbacv1.PolicyRule, error) {
return c.getRbacRulesForComponentKinds(logger, KindReceiver, KindExporter, KindProcessor)
}
Expand Down
60 changes: 60 additions & 0 deletions apis/v1beta1/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,66 @@ func TestConfig_GetEnabledComponents(t *testing.T) {
}
}

func TestConfig_getEnvironmentVariablesForComponentKinds(t *testing.T) {
tests := []struct {
name string
config *Config
componentKinds []ComponentKind
envVarsLen int
}{
{
name: "no env vars",
config: &Config{
Receivers: AnyConfig{
Object: map[string]interface{}{
"myreceiver": map[string]interface{}{
"env": "test",
},
},
},
Service: Service{
Pipelines: map[string]*Pipeline{
"test": {
Receivers: []string{"myreceiver"},
},
},
},
},
componentKinds: []ComponentKind{KindReceiver},
envVarsLen: 0,
},
{
name: "kubeletstats env vars",
config: &Config{
Receivers: AnyConfig{
Object: map[string]interface{}{
"kubeletstats": map[string]interface{}{},
},
},
Service: Service{
Pipelines: map[string]*Pipeline{
"test": {
Receivers: []string{"kubeletstats"},
},
},
},
},
componentKinds: []ComponentKind{KindReceiver},
envVarsLen: 1,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
logger := logr.Discard()
envVars, err := tt.config.GetEnvironmentVariables(logger)

assert.NoError(t, err)
assert.Len(t, envVars, tt.envVarsLen)
})
}
}

func TestConfig_GetReceiverPorts(t *testing.T) {
tests := []struct {
name string
Expand Down
8 changes: 7 additions & 1 deletion internal/components/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ type Settings[ComponentConfigType any] struct {
livenessGen ProbeGenerator[ComponentConfigType]
readinessGen ProbeGenerator[ComponentConfigType]
defaultsApplier Defaulter[ComponentConfigType]
envVarGen EnvVarGenerator[ComponentConfigType]
}

func NewEmptySettings[ComponentConfigType any]() *Settings[ComponentConfigType] {
Expand Down Expand Up @@ -124,7 +125,11 @@ func (b Builder[ComponentConfigType]) WithReadinessGen(readinessGen ProbeGenerat
o.readinessGen = readinessGen
})
}

func (b Builder[ComponentConfigType]) WithEnvVarGen(envVarGen EnvVarGenerator[ComponentConfigType]) Builder[ComponentConfigType] {
return append(b, func(o *Settings[ComponentConfigType]) {
o.envVarGen = envVarGen
})
}
func (b Builder[ComponentConfigType]) WithDefaultsApplier(defaultsApplier Defaulter[ComponentConfigType]) Builder[ComponentConfigType] {
return append(b, func(o *Settings[ComponentConfigType]) {
o.defaultsApplier = defaultsApplier
Expand All @@ -141,6 +146,7 @@ func (b Builder[ComponentConfigType]) Build() (*GenericParser[ComponentConfigTyp
name: o.name,
portParser: o.portParser,
rbacGen: o.rbacGen,
envVarGen: o.envVarGen,
livenessGen: o.livenessGen,
readinessGen: o.readinessGen,
defaultsApplier: o.defaultsApplier,
Expand Down
7 changes: 7 additions & 0 deletions internal/components/component.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ type RBACRuleGenerator[ComponentConfigType any] func(logger logr.Logger, config
// It's expected that type Config is the configuration used by a parser.
type ProbeGenerator[ComponentConfigType any] func(logger logr.Logger, config ComponentConfigType) (*corev1.Probe, error)

// EnvVarGenerator is a function that generates a list of environment variables for a given config.
// It's expected that type Config is the configuration used by a parser.
type EnvVarGenerator[ComponentConfigType any] func(logger logr.Logger, config ComponentConfigType) ([]corev1.EnvVar, error)

// Defaulter is a function that applies given defaults to the passed Config.
// It's expected that type Config is the configuration used by a parser.
type Defaulter[ComponentConfigType any] func(logger logr.Logger, defaultAddr string, defaultPort int32, config ComponentConfigType) (map[string]interface{}, error)
Expand Down Expand Up @@ -105,6 +109,9 @@ type Parser interface {
// GetLivenessProbe returns a liveness probe set for the collector
GetLivenessProbe(logger logr.Logger, config interface{}) (*corev1.Probe, error)

// GetEnvironmentVariables returns a list of environment variables for the collector
GetEnvironmentVariables(logger logr.Logger, config interface{}) ([]corev1.EnvVar, error)

// GetReadinessProbe returns a readiness probe set for the collector
GetReadinessProbe(logger logr.Logger, config interface{}) (*corev1.Probe, error)

Expand Down
12 changes: 12 additions & 0 deletions internal/components/generic_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ type GenericParser[T any] struct {
settings *Settings[T]
portParser PortParser[T]
rbacGen RBACRuleGenerator[T]
envVarGen EnvVarGenerator[T]
livenessGen ProbeGenerator[T]
readinessGen ProbeGenerator[T]
defaultsApplier Defaulter[T]
Expand Down Expand Up @@ -88,6 +89,17 @@ func (g *GenericParser[T]) GetRBACRules(logger logr.Logger, config interface{})
return g.rbacGen(logger, parsed)
}

func (g *GenericParser[T]) GetEnvironmentVariables(logger logr.Logger, config interface{}) ([]corev1.EnvVar, error) {
if g.envVarGen == nil {
return nil, nil
}
var parsed T
if err := mapstructure.Decode(config, &parsed); err != nil {
return nil, err
}
return g.envVarGen(logger, parsed)
}

func (g *GenericParser[T]) Ports(logger logr.Logger, name string, config interface{}) ([]corev1.ServicePort, error) {
if g.portParser == nil {
return nil, nil
Expand Down
4 changes: 4 additions & 0 deletions internal/components/multi_endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,10 @@ func (m *MultiPortReceiver) GetRBACRules(logr.Logger, interface{}) ([]rbacv1.Pol
return nil, nil
}

func (m *MultiPortReceiver) GetEnvironmentVariables(logger logr.Logger, config interface{}) ([]corev1.EnvVar, error) {
return nil, nil
}

type MultiPortBuilder[ComponentConfigType any] []Builder[ComponentConfigType]

func NewMultiPortReceiverBuilder(name string) MultiPortBuilder[*MultiProtocolEndpointConfig] {
Expand Down
1 change: 1 addition & 0 deletions internal/components/receivers/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ var (
MustBuild(),
components.NewBuilder[kubeletStatsConfig]().WithName("kubeletstats").
WithRbacGen(generateKubeletStatsRbacRules).
WithEnvVarGen(generateKubeletStatsEnvVars).
MustBuild(),
NewScraperParser("prometheus"),
NewScraperParser("sshcheck"),
Expand Down
10 changes: 10 additions & 0 deletions internal/components/receivers/kubeletstats.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package receivers

import (
"github.com/go-logr/logr"
corev1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
)

Expand All @@ -42,6 +43,15 @@ type kubeletStatsConfig struct {
AuthType string `mapstructure:"auth_type"`
}

func generateKubeletStatsEnvVars(_ logr.Logger, config kubeletStatsConfig) ([]corev1.EnvVar, error) {
// The documentation mentions that the K8S_NODE_NAME environment variable is required when using the serviceAccount auth type.
// Also, it mentions that it is a good idea to use it for the Read Only Endpoint. Added always to make it easier for users.
// https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/receiver/kubeletstatsreceiver/README.md
return []corev1.EnvVar{
{Name: "K8S_NODE_NAME", ValueFrom: &corev1.EnvVarSource{FieldRef: &corev1.ObjectFieldSelector{FieldPath: "spec.nodeName"}}},
}, nil
}

func generateKubeletStatsRbacRules(_ logr.Logger, config kubeletStatsConfig) ([]rbacv1.PolicyRule, error) {
// The Kubelet Stats Receiver needs get permissions on the nodes/stats resources always.
prs := []rbacv1.PolicyRule{
Expand Down
6 changes: 6 additions & 0 deletions internal/manifests/collector/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,12 @@ func Container(cfg config.Config, logger logr.Logger, otelcol v1beta1.OpenTeleme
)
}

if configEnvVars, err := otelcol.Spec.Config.GetEnvironmentVariables(logger); err != nil {
logger.Error(err, "could not get the environment variables from the config")
} else {
envVars = append(envVars, configEnvVars...)
}

envVars = append(envVars, proxy.ReadProxyVarsFromEnv()...)
return corev1.Container{
Name: naming.Container(),
Expand Down
21 changes: 21 additions & 0 deletions tests/e2e-automatic-rbac/receiver-kubeletstats/01-assert.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,24 @@ subjects:
- kind: ServiceAccount
name: simplest-collector
namespace: chainsaw-kubeletstats
---
apiVersion: v1
kind: Pod
metadata:
labels:
app.kubernetes.io/component: opentelemetry-collector
app.kubernetes.io/instance: chainsaw-kubeletstats.simplest
app.kubernetes.io/managed-by: opentelemetry-operator
app.kubernetes.io/name: simplest-collector
app.kubernetes.io/part-of: opentelemetry
app.kubernetes.io/version: latest
namespace: chainsaw-kubeletstats
spec:
containers:
- name: otc-container
env:
- name: POD_NAME
- name: K8S_NODE_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName

0 comments on commit fa0f54a

Please sign in to comment.