Skip to content

Commit

Permalink
Add resource attributes to collector sidecar (open-telemetry#832)
Browse files Browse the repository at this point in the history
* Add resource attributes to collector sidecar

Signed-off-by: Ruben Vargas <[email protected]>

* Update getAttributesEnv comments.

Co-authored-by: Ben B. <[email protected]>

* Use semconv package for attribute names

Signed-off-by: Ruben Vargas <[email protected]>

* Make env var names consistent

Signed-off-by: Ruben Vargas <[email protected]>

* Improve comments

Signed-off-by: Ruben Vargas <[email protected]>

* Rename attributes function

Signed-off-by: Ruben Vargas <[email protected]>

* fix import order

Signed-off-by: Ruben Vargas <[email protected]>

* Verify env vars on sidecar e2e test

Signed-off-by: Ruben Vargas <[email protected]>

* Update readme mentioning OTEL_RESOURCE_ATTRIBUTES env var on sidecar mode

Signed-off-by: Ruben Vargas <[email protected]>

* README Improvments

Signed-off-by: Ruben Vargas <[email protected]>

Co-authored-by: Ben B. <[email protected]>
  • Loading branch information
rubenvp8510 and frzifus authored Apr 28, 2022
1 parent ef89e83 commit 5be4cdf
Show file tree
Hide file tree
Showing 9 changed files with 459 additions and 35 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,8 @@ spec:
EOF
```

When using sidecar mode the OpenTelemetry collector container will have the environment variable `OTEL_RESOURCE_ATTRIBUTES`set with Kubernetes resource attributes, ready to be consumed by the [resourcedetection](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/processor/resourcedetectionprocessor) processor.

### OpenTelemetry auto-instrumentation injection

The operator can inject and configure OpenTelemetry auto-instrumentation libraries. Currently Java, NodeJS and Python are supported.
Expand Down
28 changes: 28 additions & 0 deletions pkg/constants/env.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package constants

const (
EnvOTELServiceName = "OTEL_SERVICE_NAME"
EnvOTELExporterOTLPEndpoint = "OTEL_EXPORTER_OTLP_ENDPOINT"
EnvOTELResourceAttrs = "OTEL_RESOURCE_ATTRIBUTES"
EnvOTELPropagators = "OTEL_PROPAGATORS"
EnvOTELTracesSampler = "OTEL_TRACES_SAMPLER"
EnvOTELTracesSamplerArg = "OTEL_TRACES_SAMPLER_ARG"

EnvPodName = "OTEL_RESOURCE_ATTRIBUTES_POD_NAME"
EnvPodUID = "OTEL_RESOURCE_ATTRIBUTES_POD_UID"
EnvNodeName = "OTEL_RESOURCE_ATTRIBUTES_NODE_NAME"
)
50 changes: 20 additions & 30 deletions pkg/instrumentation/sdk.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,22 +31,12 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"

"github.com/open-telemetry/opentelemetry-operator/apis/v1alpha1"
"github.com/open-telemetry/opentelemetry-operator/pkg/constants"
)

const (
volumeName = "opentelemetry-auto-instrumentation"
initContainerName = "opentelemetry-auto-instrumentation"

envOTELServiceName = "OTEL_SERVICE_NAME"
envOTELExporterOTLPEndpoint = "OTEL_EXPORTER_OTLP_ENDPOINT"
envOTELResourceAttrs = "OTEL_RESOURCE_ATTRIBUTES"
envOTELPropagators = "OTEL_PROPAGATORS"
envOTELTracesSampler = "OTEL_TRACES_SAMPLER"
envOTELTracesSamplerArg = "OTEL_TRACES_SAMPLER_ARG"

envPodName = "OTEL_RESOURCE_ATTRIBUTES_POD_NAME"
envPodUID = "OTEL_RESOURCE_ATTRIBUTES_POD_UID"
envNodeName = "OTEL_RESOURCE_ATTRIBUTES_NODE_NAME"
)

// inject a new sidecar container to the given pod, based on the given OpenTelemetryCollector.
Expand Down Expand Up @@ -101,18 +91,18 @@ func (i *sdkInjector) injectCommonEnvVar(otelinst v1alpha1.Instrumentation, pod
func (i *sdkInjector) injectCommonSDKConfig(ctx context.Context, otelinst v1alpha1.Instrumentation, ns corev1.Namespace, pod corev1.Pod) corev1.Pod {
container := &pod.Spec.Containers[0]
resourceMap := i.createResourceMap(ctx, otelinst, ns, pod)
idx := getIndexOfEnv(container.Env, envOTELServiceName)
idx := getIndexOfEnv(container.Env, constants.EnvOTELServiceName)
if idx == -1 {
container.Env = append(container.Env, corev1.EnvVar{
Name: envOTELServiceName,
Name: constants.EnvOTELServiceName,
Value: chooseServiceName(pod, resourceMap),
})
}
if otelinst.Spec.Exporter.Endpoint != "" {
idx = getIndexOfEnv(container.Env, envOTELExporterOTLPEndpoint)
idx = getIndexOfEnv(container.Env, constants.EnvOTELExporterOTLPEndpoint)
if idx == -1 {
container.Env = append(container.Env, corev1.EnvVar{
Name: envOTELExporterOTLPEndpoint,
Name: constants.EnvOTELExporterOTLPEndpoint,
Value: otelinst.Spec.Endpoint,
})
}
Expand All @@ -121,45 +111,45 @@ func (i *sdkInjector) injectCommonSDKConfig(ctx context.Context, otelinst v1alph
// Some attributes might be empty, we should get them via k8s downward API
if resourceMap[string(semconv.K8SPodNameKey)] == "" {
container.Env = append(container.Env, corev1.EnvVar{
Name: envPodName,
Name: constants.EnvPodName,
ValueFrom: &corev1.EnvVarSource{
FieldRef: &corev1.ObjectFieldSelector{
FieldPath: "metadata.name",
},
},
})
resourceMap[string(semconv.K8SPodNameKey)] = fmt.Sprintf("$(%s)", envPodName)
resourceMap[string(semconv.K8SPodNameKey)] = fmt.Sprintf("$(%s)", constants.EnvPodName)
}
if otelinst.Spec.Resource.AddK8sUIDAttributes {
if resourceMap[string(semconv.K8SPodUIDKey)] == "" {
container.Env = append(container.Env, corev1.EnvVar{
Name: envPodUID,
Name: constants.EnvPodUID,
ValueFrom: &corev1.EnvVarSource{
FieldRef: &corev1.ObjectFieldSelector{
FieldPath: "metadata.uid",
},
},
})
resourceMap[string(semconv.K8SPodUIDKey)] = fmt.Sprintf("$(%s)", envPodUID)
resourceMap[string(semconv.K8SPodUIDKey)] = fmt.Sprintf("$(%s)", constants.EnvPodUID)
}
}
if resourceMap[string(semconv.K8SNodeNameKey)] == "" {
container.Env = append(container.Env, corev1.EnvVar{
Name: envNodeName,
Name: constants.EnvNodeName,
ValueFrom: &corev1.EnvVarSource{
FieldRef: &corev1.ObjectFieldSelector{
FieldPath: "spec.nodeName",
},
},
})
resourceMap[string(semconv.K8SNodeNameKey)] = fmt.Sprintf("$(%s)", envNodeName)
resourceMap[string(semconv.K8SNodeNameKey)] = fmt.Sprintf("$(%s)", constants.EnvNodeName)
}

idx = getIndexOfEnv(container.Env, envOTELResourceAttrs)
idx = getIndexOfEnv(container.Env, constants.EnvOTELResourceAttrs)
resStr := resourceMapToStr(resourceMap)
if idx == -1 {
container.Env = append(container.Env, corev1.EnvVar{
Name: envOTELResourceAttrs,
Name: constants.EnvOTELResourceAttrs,
Value: resStr,
})
} else {
Expand All @@ -169,27 +159,27 @@ func (i *sdkInjector) injectCommonSDKConfig(ctx context.Context, otelinst v1alph
container.Env[idx].Value += resStr
}

idx = getIndexOfEnv(container.Env, envOTELPropagators)
idx = getIndexOfEnv(container.Env, constants.EnvOTELPropagators)
if idx == -1 && len(otelinst.Spec.Propagators) > 0 {
propagators := *(*[]string)((unsafe.Pointer(&otelinst.Spec.Propagators)))
container.Env = append(container.Env, corev1.EnvVar{
Name: envOTELPropagators,
Name: constants.EnvOTELPropagators,
Value: strings.Join(propagators, ","),
})
}

idx = getIndexOfEnv(container.Env, envOTELTracesSampler)
idx = getIndexOfEnv(container.Env, constants.EnvOTELTracesSampler)
// configure sampler only if it is configured in the CR
if idx == -1 && otelinst.Spec.Sampler.Type != "" {
idxSamplerArg := getIndexOfEnv(container.Env, envOTELTracesSamplerArg)
idxSamplerArg := getIndexOfEnv(container.Env, constants.EnvOTELTracesSamplerArg)
if idxSamplerArg == -1 {
container.Env = append(container.Env, corev1.EnvVar{
Name: envOTELTracesSampler,
Name: constants.EnvOTELTracesSampler,
Value: string(otelinst.Spec.Sampler.Type),
})
if otelinst.Spec.Sampler.Argument != "" {
container.Env = append(container.Env, corev1.EnvVar{
Name: envOTELTracesSamplerArg,
Name: constants.EnvOTELTracesSamplerArg,
Value: otelinst.Spec.Sampler.Argument,
})
}
Expand Down Expand Up @@ -223,7 +213,7 @@ func chooseServiceName(pod corev1.Pod, resources map[string]string) string {
func (i *sdkInjector) createResourceMap(ctx context.Context, otelinst v1alpha1.Instrumentation, ns corev1.Namespace, pod corev1.Pod) map[string]string {
// get existing resources env var and parse it into a map
existingRes := map[string]bool{}
existingResourceEnvIdx := getIndexOfEnv(pod.Spec.Containers[0].Env, envOTELResourceAttrs)
existingResourceEnvIdx := getIndexOfEnv(pod.Spec.Containers[0].Env, constants.EnvOTELResourceAttrs)
if existingResourceEnvIdx > -1 {
existingResArr := strings.Split(pod.Spec.Containers[0].Env[existingResourceEnvIdx].Value, ",")
for _, kv := range existingResArr {
Expand Down
120 changes: 120 additions & 0 deletions pkg/sidecar/attributes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package sidecar contains operations related to sidecar manipulation (Add, update, remove).
package sidecar

import (
"fmt"
"sort"
"strings"

"go.opentelemetry.io/otel/attribute"
semconv "go.opentelemetry.io/otel/semconv/v1.7.0"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"

"github.com/open-telemetry/opentelemetry-operator/pkg/constants"
)

const resourceAttributesEnvName = "OTEL_RESOURCE_ATTRIBUTES"

type podReferences struct {
replicaset *appsv1.ReplicaSet
deployment *appsv1.Deployment
}

// getResourceAttributesEnv returns a list of environment variables. The list contains OTEL_RESOURCE_ATTRIBUTES and additional environment variables that use Kubernetes downward API to read pod specification.
// see: https://kubernetes.io/docs/tasks/inject-data-application/environment-variable-expose-pod-information/
func getResourceAttributesEnv(ns corev1.Namespace, podReferences podReferences) []corev1.EnvVar {

var envvars []corev1.EnvVar

attributes := map[attribute.Key]string{
semconv.K8SPodNameKey: fmt.Sprintf("$(%s)", constants.EnvPodName),
semconv.K8SPodUIDKey: fmt.Sprintf("$(%s)", constants.EnvPodUID),
semconv.K8SNodeNameKey: fmt.Sprintf("$(%s)", constants.EnvNodeName),
semconv.K8SNamespaceNameKey: ns.Name,
}

if podReferences.deployment != nil {
attributes[semconv.K8SDeploymentUIDKey] = string(podReferences.deployment.UID)
attributes[semconv.K8SDeploymentNameKey] = string(podReferences.deployment.Name)
}

if podReferences.replicaset != nil {
attributes[semconv.K8SReplicaSetUIDKey] = string(podReferences.replicaset.UID)
attributes[semconv.K8SReplicaSetNameKey] = string(podReferences.replicaset.Name)
}

envvars = append(envvars, corev1.EnvVar{
Name: constants.EnvPodName,
ValueFrom: &corev1.EnvVarSource{
FieldRef: &corev1.ObjectFieldSelector{
FieldPath: "metadata.name",
},
},
})

envvars = append(envvars, corev1.EnvVar{
Name: constants.EnvPodUID,
ValueFrom: &corev1.EnvVarSource{
FieldRef: &corev1.ObjectFieldSelector{
FieldPath: "metadata.uid",
},
},
})

envvars = append(envvars, corev1.EnvVar{
Name: constants.EnvNodeName,
ValueFrom: &corev1.EnvVarSource{
FieldRef: &corev1.ObjectFieldSelector{
FieldPath: "spec.nodeName",
},
},
})

envvars = append(envvars, corev1.EnvVar{
Name: resourceAttributesEnvName,
Value: mapToValue(attributes),
})

return envvars
}

func mapToValue(attributesMap map[attribute.Key]string) string {
var parts []string

// Sort it to make it predictable
keys := make([]string, 0, len(attributesMap))
for k := range attributesMap {
keys = append(keys, string(k))
}
sort.Strings(keys)

for _, key := range keys {
parts = append(parts, fmt.Sprintf("%s=%s", key, attributesMap[attribute.Key(key)]))
}
return strings.Join(parts, ",")
}

// check if container doesn't have already the OTEL_RESOURCE_ATTRIBUTES, we don't want to override it if it's already specified.
func hasResourceAttributeEnvVar(envvars []corev1.EnvVar) bool {
for _, env := range envvars {
if env.Name == resourceAttributesEnvName {
return true
}
}
return false
}
Loading

0 comments on commit 5be4cdf

Please sign in to comment.