Skip to content

Commit

Permalink
Add autoinstrumentation of NodeJS
Browse files Browse the repository at this point in the history
  • Loading branch information
Anuraag Agrawal committed Nov 5, 2021
1 parent 7adf261 commit ba0bede
Show file tree
Hide file tree
Showing 12 changed files with 553 additions and 35 deletions.
13 changes: 13 additions & 0 deletions api/instrumentation/v1alpha1/instrumentation_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ type InstrumentationSpec struct {
// +optional
// +operator-sdk:gen-csv:customresourcedefinitions.specDescriptors=true
Java JavaSpec `json:"java,omitempty"`

// NodeJS defines configuration for nodejs auto-instrumentation.
// +optional
// +operator-sdk:gen-csv:customresourcedefinitions.specDescriptors=true
NodeJS NodeJSSpec `json:"nodejs,omitempty"`
}

// JavaSpec defines Java SDK and instrumentation configuration.
Expand All @@ -37,6 +42,14 @@ type JavaSpec struct {
Image string `json:"image,omitempty"`
}

// NodeJSSpec defines NodeJS SDK and instrumentation configuration.
type NodeJSSpec struct {
// Image is a container image with NodeJS SDK and autoinstrumentation.
// +optional
// +operator-sdk:gen-csv:customresourcedefinitions.specDescriptors=true
Image string `json:"image,omitempty"`
}

// Exporter defines OTLP exporter configuration.
type Exporter struct {
// Endpoint is address of the collector with OTLP endpoint.
Expand Down
16 changes: 16 additions & 0 deletions api/instrumentation/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions bundle/manifests/opentelemetry.io_instrumentations.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,13 @@ spec:
description: Image is a container image with javaagent JAR.
type: string
type: object
nodejs:
description: NodeJS defines configuration for nodejs auto-instrumentation.
properties:
image:
description: Image is a container image with NodeJS SDK and autoinstrumentation.
type: string
type: object
type: object
status:
description: InstrumentationStatus defines status of the instrumentation.
Expand Down
7 changes: 7 additions & 0 deletions config/crd/bases/opentelemetry.io_instrumentations.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,13 @@ spec:
description: Image is a container image with javaagent JAR.
type: string
type: object
nodejs:
description: NodeJS defines configuration for nodejs auto-instrumentation.
properties:
image:
description: Image is a container image with NodeJS SDK and autoinstrumentation.
type: string
type: object
type: object
status:
description: InstrumentationStatus defines status of the instrumentation.
Expand Down
9 changes: 5 additions & 4 deletions pkg/instrumentation/annotation.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,16 @@ import (
const (
// annotationInjectJava indicates whether java auto-instrumentation should be injected or not.
// Possible values are "true", "false" or "<Instrumentation>" name.
annotationInjectJava = "instrumentation.opentelemetry.io/inject-java"
annotationInject = "instrumentation.opentelemetry.io/inject"
annotationLanguage = "instrumentation.opentelemetry.io/language"
)

// annotationValue returns the effective annotationInjectJava value, based on the annotations from the pod and namespace.
func annotationValue(ns metav1.ObjectMeta, pod metav1.ObjectMeta) string {
func annotationValue(ns metav1.ObjectMeta, pod metav1.ObjectMeta, annotation string) string {
// is the pod annotated with instructions to inject sidecars? is the namespace annotated?
// if any of those is true, a sidecar might be desired.
podAnnValue := pod.Annotations[annotationInjectJava]
nsAnnValue := ns.Annotations[annotationInjectJava]
podAnnValue := pod.Annotations[annotation]
nsAnnValue := ns.Annotations[annotation]

// if the namespace value is empty, the pod annotation should be used, whatever it is
if len(nsAnnValue) == 0 {
Expand Down
22 changes: 11 additions & 11 deletions pkg/instrumentation/annotation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,14 @@ func TestEffectiveAnnotationValue(t *testing.T) {
corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{
annotationInjectJava: "true",
annotationInject: "true",
},
},
},
corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{
annotationInjectJava: "false",
annotationInject: "false",
},
},
},
Expand All @@ -54,14 +54,14 @@ func TestEffectiveAnnotationValue(t *testing.T) {
corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{
annotationInjectJava: "true",
annotationInject: "true",
},
},
},
corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{
annotationInjectJava: "some-instance",
annotationInject: "some-instance",
},
},
},
Expand All @@ -73,14 +73,14 @@ func TestEffectiveAnnotationValue(t *testing.T) {
corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{
annotationInjectJava: "some-instance-from-pod",
annotationInject: "some-instance-from-pod",
},
},
},
corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{
annotationInjectJava: "some-instance",
annotationInject: "some-instance",
},
},
},
Expand All @@ -92,14 +92,14 @@ func TestEffectiveAnnotationValue(t *testing.T) {
corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{
annotationInjectJava: "false",
annotationInject: "false",
},
},
},
corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{
annotationInjectJava: "some-instance",
annotationInject: "some-instance",
},
},
},
Expand All @@ -112,7 +112,7 @@ func TestEffectiveAnnotationValue(t *testing.T) {
corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{
annotationInjectJava: "some-instance",
annotationInject: "some-instance",
},
},
},
Expand All @@ -124,7 +124,7 @@ func TestEffectiveAnnotationValue(t *testing.T) {
corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{
annotationInjectJava: "true",
annotationInject: "true",
},
},
},
Expand All @@ -133,7 +133,7 @@ func TestEffectiveAnnotationValue(t *testing.T) {
} {
t.Run(tt.desc, func(t *testing.T) {
// test
annValue := annotationValue(tt.ns.ObjectMeta, tt.pod.ObjectMeta)
annValue := annotationValue(tt.ns.ObjectMeta, tt.pod.ObjectMeta, annotationInject)

// verify
assert.Equal(t, tt.expected, annValue)
Expand Down
68 changes: 68 additions & 0 deletions pkg/instrumentation/nodejs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// 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 instrumentation

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

"github.com/open-telemetry/opentelemetry-operator/api/instrumentation/v1alpha1"
)

const (
envNodeOptions = "NODE_OPTIONS"
nodeRequireArgument = " --require /otel-auto-instrumentation/autoinstrumentation.js"
)

func injectNodeJSSDK(logger logr.Logger, nodeJSSpec v1alpha1.NodeJSSpec, pod corev1.Pod) corev1.Pod {
// caller checks if there is at least one container
container := &pod.Spec.Containers[0]
idx := getIndexOfEnv(container.Env, envNodeOptions)
if idx == -1 {
container.Env = append(container.Env, corev1.EnvVar{
Name: envNodeOptions,
Value: nodeRequireArgument,
})
} else if idx > -1 {
if container.Env[idx].ValueFrom != nil {
// TODO add to status object or submit it as an event
logger.Info("Skipping NodeJS SDK injection, the container defines NODE_OPTIONS env var value via ValueFrom", "container", container.Name)
return pod
}
container.Env[idx].Value = container.Env[idx].Value + nodeRequireArgument
}
container.VolumeMounts = append(container.VolumeMounts, corev1.VolumeMount{
Name: volumeName,
MountPath: "/otel-auto-instrumentation",
})

pod.Spec.Volumes = append(pod.Spec.Volumes, corev1.Volume{
Name: volumeName,
VolumeSource: corev1.VolumeSource{
EmptyDir: &corev1.EmptyDirVolumeSource{},
}})

pod.Spec.InitContainers = append(pod.Spec.InitContainers, corev1.Container{
Name: initContainerName,
Image: nodeJSSpec.Image,
Command: []string{"cp", "-a", "/autoinstrumentation/.", "/otel-auto-instrumentation/"},
VolumeMounts: []corev1.VolumeMount{{
Name: volumeName,
MountPath: "/otel-auto-instrumentation",
}},
})

return pod
}
Loading

0 comments on commit ba0bede

Please sign in to comment.