Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ssi): add support for trace configs #33959

Merged
merged 2 commits into from
Feb 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions pkg/clusteragent/admission/mutate/autoinstrumentation/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,10 @@ type Target struct {
// name and the value is the version to inject. Full config key:
// apm_config.instrumentation.targets[].ddTraceVersions
TracerVersions map[string]string `mapstructure:"ddTraceVersions"`
// TracerConfigs is a list of configuration options to use for the installed tracers. These options will be added
// as environment variables in addition to the injected tracer. Full config key:
// apm_config.instrumentation.targets[].ddTraceConfigs
TracerConfigs []TracerConfig `mapstructure:"ddTraceConfigs" json:"ddTraceConfigs"`
}

// PodSelector is a reconstruction of the metav1.LabelSelector struct to be able to unmarshal the configuration. It
Expand Down Expand Up @@ -295,6 +299,23 @@ func (n NamespaceSelector) AsLabelSelector() (labels.Selector, error) {
return metav1.LabelSelectorAsSelector(labelSelector)
}

// TracerConfig is a struct that stores configuration options for a tracer. These will be injected as environment
// variables to the workload that matches targeting.
type TracerConfig struct {
// Name is the name of the environment variable.
Name string `mapstructure:"name" json:"name"`
// Value is the value to use.
Value string `mapstructure:"value" json:"value"`
}

// AsEnvVar converts the TracerConfig to a corev1.EnvVar.
func (c *TracerConfig) AsEnvVar() corev1.EnvVar {
return corev1.EnvVar{
Name: c.Name,
Value: c.Value,
}
}

var (
minimumCPULimit resource.Quantity = resource.MustParse("0.05") // 0.05 core, otherwise copying + library initialization is going to take forever
minimumMemoryLimit resource.Quantity = resource.MustParse("100Mi") // 100 MB (recommended minimum by Alpine)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,16 @@ func TestNewInstrumentationConfig(t *testing.T) {
TracerVersions: map[string]string{
"java": "default",
},
TracerConfigs: []TracerConfig{
{
Name: "DD_PROFILING_ENABLED",
Value: "true",
},
{
Name: "DD_DATA_JOBS_ENABLED",
Value: "true",
},
},
},
},
},
Expand Down Expand Up @@ -133,6 +143,16 @@ func TestNewInstrumentationConfig(t *testing.T) {
TracerVersions: map[string]string{
"java": "default",
},
TracerConfigs: []TracerConfig{
{
Name: "DD_PROFILING_ENABLED",
Value: "true",
},
{
Name: "DD_DATA_JOBS_ENABLED",
Value: "true",
},
},
},
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ package autoinstrumentation
import (
"errors"
"fmt"
"strings"

corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/labels"
Expand Down Expand Up @@ -85,6 +86,17 @@ func NewTargetMutator(config *Config, wmeta workloadmeta.Component) (*TargetMuta
libVersions = getPinnedLibraries(t.TracerVersions, config.containerRegistry)
}

// Convert the tracer configs to env vars. We check that the env var names start with the DD_ prefix to avoid
// this from being used as a generic env var injector. If there is a product requirement to allow arbitrary env
// vars in the future, we could relax this requirement.
envVars := make([]corev1.EnvVar, len(t.TracerConfigs))
for i, tc := range t.TracerConfigs {
if !strings.HasPrefix(tc.Name, "DD_") {
return nil, fmt.Errorf("tracer config %q does not start with DD_", tc.Name)
}
envVars[i] = tc.AsEnvVar()
}

// Store the target in the internal format.
internalTargets[i] = targetInternal{
name: t.Name,
Expand All @@ -94,6 +106,7 @@ func NewTargetMutator(config *Config, wmeta workloadmeta.Component) (*TargetMuta
wmeta: wmeta,
enabledNamespaces: enabledNamespaces,
libVersions: libVersions,
envVars: envVars,
}
}

Expand Down Expand Up @@ -135,12 +148,12 @@ func (m *TargetMutator) MutatePod(pod *corev1.Pod, ns string, _ dynamic.Interfac
}
}

// Get the libraries to inject. If there are no libraries to inject, we should not mutate the pod.
libraries := m.getLibraries(pod)
if len(libraries) == 0 {
// Get the target to inject. If there is not target, we should not mutate the pod.
target := m.getTarget(pod)
if target == nil {
return false, nil
}
extracted := m.core.initExtractedLibInfo(pod).withLibs(libraries)
extracted := m.core.initExtractedLibInfo(pod).withLibs(target.libVersions)

// Add the configuration for the security client library.
for _, mutator := range m.securityClientLibraryPodMutators {
Expand All @@ -162,12 +175,18 @@ func (m *TargetMutator) MutatePod(pod *corev1.Pod, ns string, _ dynamic.Interfac
return false, fmt.Errorf("error injecting libraries: %w", err)
}

// Inject the tracer configs.
for _, envVar := range target.envVars {
mutatecommon.InjectEnv(pod, envVar)
}

return true, nil
}

// ShouldMutatePod checks if a pod is mutable. This is used by other mutators to determine if they should mutate a pod.
// ShouldMutatePod determines if a pod would be mutated by the target mutator. It is used by other webhook mutators as
// a filter.
func (m *TargetMutator) ShouldMutatePod(pod *corev1.Pod) bool {
return len(m.getLibraries(pod)) > 0
return m.getTarget(pod) != nil
}

// IsNamespaceEligible returns true if a namespace is eligible for injection/mutation.
Expand Down Expand Up @@ -195,39 +214,45 @@ func (m *TargetMutator) IsNamespaceEligible(namespace string) bool {
return false
}

// targetInternal is the struct we use to convert the config based target into
// something more performant to check against.
// targetInternal is the struct we use to convert the config based target into something more performant.
type targetInternal struct {
name string
podSelector labels.Selector
nameSpaceSelector labels.Selector
useNamespaceSelector bool
enabledNamespaces map[string]bool
libVersions []libInfo
envVars []corev1.EnvVar
wmeta workloadmeta.Component
}

// getLibraries determines which tracing libraries to use given a pod. It returns the list of tracing libraries to
// inject.
func (m *TargetMutator) getLibraries(pod *corev1.Pod) []libInfo {
// getTarget determines which target to use for a given a pod, which includes the set of tracing libraries to inject.
func (m *TargetMutator) getTarget(pod *corev1.Pod) *targetInternal {
// If the pod has explicit tracer libraries defined as annotations, they take precedence.
libraries := m.getAnnotationLibraries(pod)
if len(libraries) > 0 {
return libraries
target := m.getTargetFromAnnotation(pod)
if target != nil {
return target
}

// If there are no annotations, check if the pod matches any of the targets.
return m.getTargetLibraries(pod)
return m.getMatchingTarget(pod)
}

// getAnnotationLibraries determines which tracing libraries to use given a pod's annotations. It returns the list of
// getTargetFromAnnotation determines which tracing libraries to use given a pod's annotations. It returns the list of
// tracing libraries to inject.
func (m *TargetMutator) getAnnotationLibraries(pod *corev1.Pod) []libInfo {
return extractLibrariesFromAnnotations(pod, m.containerRegistry)
func (m *TargetMutator) getTargetFromAnnotation(pod *corev1.Pod) *targetInternal {
libVersions := extractLibrariesFromAnnotations(pod, m.containerRegistry)
if len(libVersions) == 0 {
return nil
}

return &targetInternal{
libVersions: libVersions,
}
}

// getTargetLibraries filters a pod based on the targets. It returns the list of libraries to inject.
func (m *TargetMutator) getTargetLibraries(pod *corev1.Pod) []libInfo {
// getMatchingTarget filters a pod based on the targets. It returns the target to inject.
func (m *TargetMutator) getMatchingTarget(pod *corev1.Pod) *targetInternal {
// If the namespace is disabled, we don't need to check the targets.
if _, ok := m.disabledNamespaces[pod.Namespace]; ok {
return nil
Expand All @@ -254,7 +279,7 @@ func (m *TargetMutator) getTargetLibraries(pod *corev1.Pod) []libInfo {
log.Debugf("Pod %q matched target %q", mutatecommon.PodString(pod), target.name)

// If the namespace and pod selector match, return the libraries to inject.
return target.libVersions
return &target
}

// No target matched.
Expand Down
Loading
Loading