From 7ce297de5415486b568d68466a564fd65fe7d462 Mon Sep 17 00:00:00 2001 From: Avadhut Pisal Date: Tue, 26 Jul 2022 17:30:48 +0530 Subject: [PATCH] Support DotNet auto-instrumentation (#976) * Adds support for dotnet auto-instrumentation * changes default image for testing * adds dotnet auto-instrumentation to the ldflags in makefile * changes in Makefile * fixes issue in makefile * fixes issue in dockerfile * passes dotnet sample app image to e2e test * fixes version in the dot net autoinstrumentation version file * adds testing changes for dot-net-autoinstrumentation * fixes annotation * downloads dot-net release from github * removes git push condition check * removes git push condition check * changes repo to logicmonitor * adds dontnet auto-instrumentation related details in readme * adds docker image layer caching in github action * triggers action on pull request * adds e2e test cases for dotnet instrumentation * fixes version for dotnet instrumentation * version changes * restores clusterversion file * restores changes in Makefile * minor change * Adds configuration details to dockerfile * updates version of dotnet auto-instrumentation * changes env var name from OTEL_DOTNET_TRACER_INSTRUMENTATIONS to OTEL_DOTNET_AUTO_TRACES_ENABLED_INSTRUMENTATIONS * removes java related env variables * fixes e2e test cases * fixes e2e test case * deletes .DS_Store file * sets OTEL_DOTNET_AUTO_TRACES_ENABLED_INSTRUMENTATIONS to default value * fixes e2e test case * fixes e2e test case --- .../publish-autoinstrumentation-dotnet.yaml | 2 +- Dockerfile | 3 +- Makefile | 5 +- README.md | 12 +- apis/v1alpha1/instrumentation_types.go | 16 + apis/v1alpha1/instrumentation_webhook.go | 37 ++- apis/v1alpha1/instrumentation_webhook_test.go | 34 +++ apis/v1alpha1/zz_generated.deepcopy.go | 23 ++ .../opentelemetry.io_instrumentations.yaml | 121 ++++++++ .../opentelemetry.io_instrumentations.yaml | 121 ++++++++ docs/api.md | 287 ++++++++++++++++++ internal/config/main.go | 7 + internal/config/options.go | 7 + internal/version/main.go | 13 +- main.go | 6 + pkg/instrumentation/annotation.go | 1 + pkg/instrumentation/dotnet.go | 118 +++++++ pkg/instrumentation/dotnet_test.go | 274 +++++++++++++++++ pkg/instrumentation/podmutator.go | 10 +- pkg/instrumentation/podmutator_test.go | 165 ++++++++++ pkg/instrumentation/sdk.go | 7 + pkg/instrumentation/sdk_test.go | 106 +++++++ pkg/instrumentation/upgrade/upgrade.go | 9 + pkg/instrumentation/upgrade/upgrade_test.go | 5 + .../00-install-collector.yaml | 23 ++ .../00-install-instrumentation.yaml | 30 ++ .../01-assert.yaml | 84 +++++ .../01-install-app.yaml | 23 ++ .../02-assert.yaml | 52 ++++ .../02-install-app.yaml | 23 ++ .../00-install-collector.yaml | 23 ++ .../00-install-instrumentation.yaml | 30 ++ .../e2e/instrumentation-dotnet/01-assert.yaml | 48 +++ .../01-install-app.yaml | 24 ++ versions.txt | 4 + 35 files changed, 1741 insertions(+), 12 deletions(-) create mode 100644 pkg/instrumentation/dotnet.go create mode 100644 pkg/instrumentation/dotnet_test.go create mode 100644 tests/e2e/instrumentation-dotnet-multicontainer/00-install-collector.yaml create mode 100644 tests/e2e/instrumentation-dotnet-multicontainer/00-install-instrumentation.yaml create mode 100644 tests/e2e/instrumentation-dotnet-multicontainer/01-assert.yaml create mode 100644 tests/e2e/instrumentation-dotnet-multicontainer/01-install-app.yaml create mode 100644 tests/e2e/instrumentation-dotnet-multicontainer/02-assert.yaml create mode 100644 tests/e2e/instrumentation-dotnet-multicontainer/02-install-app.yaml create mode 100644 tests/e2e/instrumentation-dotnet/00-install-collector.yaml create mode 100644 tests/e2e/instrumentation-dotnet/00-install-instrumentation.yaml create mode 100644 tests/e2e/instrumentation-dotnet/01-assert.yaml create mode 100644 tests/e2e/instrumentation-dotnet/01-install-app.yaml diff --git a/.github/workflows/publish-autoinstrumentation-dotnet.yaml b/.github/workflows/publish-autoinstrumentation-dotnet.yaml index 8663e0e1da..9b6e235561 100644 --- a/.github/workflows/publish-autoinstrumentation-dotnet.yaml +++ b/.github/workflows/publish-autoinstrumentation-dotnet.yaml @@ -62,4 +62,4 @@ jobs: tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=local,src=/tmp/.buildx-cache - cache-to: type=local,dest=/tmp/.buildx-cache \ No newline at end of file + cache-to: type=local,dest=/tmp/.buildx-cache diff --git a/Dockerfile b/Dockerfile index a38643660e..f3f25cdfbc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -25,9 +25,10 @@ ARG TARGETALLOCATOR_VERSION ARG AUTO_INSTRUMENTATION_JAVA_VERSION ARG AUTO_INSTRUMENTATION_NODEJS_VERSION ARG AUTO_INSTRUMENTATION_PYTHON_VERSION +ARG AUTO_INSTRUMENTATION_DOTNET_VERSION # Build -RUN CGO_ENABLED=0 GOOS=linux GO111MODULE=on go build -ldflags="-X ${VERSION_PKG}.version=${VERSION} -X ${VERSION_PKG}.buildDate=${VERSION_DATE} -X ${VERSION_PKG}.otelCol=${OTELCOL_VERSION} -X ${VERSION_PKG}.targetAllocator=${TARGETALLOCATOR_VERSION} -X ${VERSION_PKG}.autoInstrumentationJava=${AUTO_INSTRUMENTATION_JAVA_VERSION} -X ${VERSION_PKG}.autoInstrumentationNodeJS=${AUTO_INSTRUMENTATION_NODEJS_VERSION} -X ${VERSION_PKG}.autoInstrumentationPython=${AUTO_INSTRUMENTATION_PYTHON_VERSION}" -a -o manager main.go +RUN CGO_ENABLED=0 GOOS=linux GO111MODULE=on go build -ldflags="-X ${VERSION_PKG}.version=${VERSION} -X ${VERSION_PKG}.buildDate=${VERSION_DATE} -X ${VERSION_PKG}.otelCol=${OTELCOL_VERSION} -X ${VERSION_PKG}.targetAllocator=${TARGETALLOCATOR_VERSION} -X ${VERSION_PKG}.autoInstrumentationJava=${AUTO_INSTRUMENTATION_JAVA_VERSION} -X ${VERSION_PKG}.autoInstrumentationNodeJS=${AUTO_INSTRUMENTATION_NODEJS_VERSION} -X ${VERSION_PKG}.autoInstrumentationPython=${AUTO_INSTRUMENTATION_PYTHON_VERSION} -X ${VERSION_PKG}.autoInstrumentationDotNet=${AUTO_INSTRUMENTATION_DOTNET_VERSION}" -a -o manager main.go # Use distroless as minimal base image to package the manager binary # Refer to https://github.com/GoogleContainerTools/distroless for more details diff --git a/Makefile b/Makefile index 562e54715d..02b4ebbbb4 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,8 @@ TARGETALLOCATOR_VERSION ?= "$(shell grep -v '\#' versions.txt | grep targetalloc AUTO_INSTRUMENTATION_JAVA_VERSION ?= "$(shell grep -v '\#' versions.txt | grep autoinstrumentation-java | awk -F= '{print $$2}')" AUTO_INSTRUMENTATION_NODEJS_VERSION ?= "$(shell grep -v '\#' versions.txt | grep autoinstrumentation-nodejs | awk -F= '{print $$2}')" AUTO_INSTRUMENTATION_PYTHON_VERSION ?= "$(shell grep -v '\#' versions.txt | grep autoinstrumentation-python | awk -F= '{print $$2}')" -LD_FLAGS ?= "-X ${VERSION_PKG}.version=${VERSION} -X ${VERSION_PKG}.buildDate=${VERSION_DATE} -X ${VERSION_PKG}.otelCol=${OTELCOL_VERSION} -X ${VERSION_PKG}.targetAllocator=${TARGETALLOCATOR_VERSION} -X ${VERSION_PKG}.autoInstrumentationJava=${AUTO_INSTRUMENTATION_JAVA_VERSION} -X ${VERSION_PKG}.autoInstrumentationNodeJS=${AUTO_INSTRUMENTATION_NODEJS_VERSION} -X ${VERSION_PKG}.autoInstrumentationPython=${AUTO_INSTRUMENTATION_PYTHON_VERSION}" +AUTO_INSTRUMENTATION_DOTNET_VERSION ?= "$(shell grep -v '\#' versions.txt | grep autoinstrumentation-dotnet | awk -F= '{print $$2}')" +LD_FLAGS ?= "-X ${VERSION_PKG}.version=${VERSION} -X ${VERSION_PKG}.buildDate=${VERSION_DATE} -X ${VERSION_PKG}.otelCol=${OTELCOL_VERSION} -X ${VERSION_PKG}.targetAllocator=${TARGETALLOCATOR_VERSION} -X ${VERSION_PKG}.autoInstrumentationJava=${AUTO_INSTRUMENTATION_JAVA_VERSION} -X ${VERSION_PKG}.autoInstrumentationNodeJS=${AUTO_INSTRUMENTATION_NODEJS_VERSION} -X ${VERSION_PKG}.autoInstrumentationPython=${AUTO_INSTRUMENTATION_PYTHON_VERSION} -X ${VERSION_PKG}.autoInstrumentationDotNet=${AUTO_INSTRUMENTATION_DOTNET_VERSION}" # Image URL to use all building/pushing image targets IMG_PREFIX ?= ghcr.io/${USER}/opentelemetry-operator @@ -174,7 +175,7 @@ set-test-image-vars: # Build the container image, used only for local dev purposes .PHONY: container container: - docker build -t ${IMG} --build-arg VERSION_PKG=${VERSION_PKG} --build-arg VERSION=${VERSION} --build-arg VERSION_DATE=${VERSION_DATE} --build-arg OTELCOL_VERSION=${OTELCOL_VERSION} --build-arg TARGETALLOCATOR_VERSION=${TARGETALLOCATOR_VERSION} --build-arg AUTO_INSTRUMENTATION_JAVA_VERSION=${AUTO_INSTRUMENTATION_JAVA_VERSION} --build-arg AUTO_INSTRUMENTATION_NODEJS_VERSION=${AUTO_INSTRUMENTATION_NODEJS_VERSION} --build-arg AUTO_INSTRUMENTATION_PYTHON_VERSION=${AUTO_INSTRUMENTATION_PYTHON_VERSION} . + docker build -t ${IMG} --build-arg VERSION_PKG=${VERSION_PKG} --build-arg VERSION=${VERSION} --build-arg VERSION_DATE=${VERSION_DATE} --build-arg OTELCOL_VERSION=${OTELCOL_VERSION} --build-arg TARGETALLOCATOR_VERSION=${TARGETALLOCATOR_VERSION} --build-arg AUTO_INSTRUMENTATION_JAVA_VERSION=${AUTO_INSTRUMENTATION_JAVA_VERSION} --build-arg AUTO_INSTRUMENTATION_NODEJS_VERSION=${AUTO_INSTRUMENTATION_NODEJS_VERSION} --build-arg AUTO_INSTRUMENTATION_PYTHON_VERSION=${AUTO_INSTRUMENTATION_PYTHON_VERSION} --build-arg AUTO_INSTRUMENTATION_DOTNET_VERSION=${AUTO_INSTRUMENTATION_DOTNET_VERSION} . # Push the container image, used only for local dev purposes .PHONY: container-push diff --git a/README.md b/README.md index de7c981837..3cc8628f71 100644 --- a/README.md +++ b/README.md @@ -166,7 +166,7 @@ When using sidecar mode the OpenTelemetry collector container will have the envi ### OpenTelemetry auto-instrumentation injection -The operator can inject and configure OpenTelemetry auto-instrumentation libraries. Currently Java, NodeJS and Python are supported. +The operator can inject and configure OpenTelemetry auto-instrumentation libraries. Currently DotNet, Java, NodeJS and Python are supported. To use auto-instrumentation, configure an `Instrumentation` resource with the configuration for the SDK and instrumentation. @@ -210,12 +210,20 @@ Python: instrumentation.opentelemetry.io/inject-python: "true" ``` +DotNet: +```bash +instrumentation.opentelemetry.io/inject-dotnet: "true" +``` + The possible values for the annotation can be * `"true"` - inject and `Instrumentation` resource from the namespace. * `"my-instrumentation"` - name of `Instrumentation` CR instance in the current namespace. * `"my-other-namespace/my-instrumentation"` - name and namespace of `Instrumentation` CR instance in another namespace. * `"false"` - do not inject + +>**Note:** For `DotNet` auto-instrumentation, by default, operator sets the `OTEL_DOTNET_AUTO_TRACES_ENABLED_INSTRUMENTATIONS` environment variable which specifies the list of traces source instrumentations you want to enable. The value that is set by default by the operator is all available instrumentations supported by the `openTelemery-dotnet-instrumentation` release consumed in the image, i.e. `AspNet,HttpClient,SqlClient`. This value can be overriden by configuring the environment variable explicitely. + #### Multi-container pods If nothing else is specified, instrumentation is performed on the first container available in the pod spec. @@ -271,6 +279,8 @@ spec: image: your-customized-auto-instrumentation-image:nodejs python: image: your-customized-auto-instrumentation-image:python + dotnet: + image: your-customized-auto-instrumentation-image:dotnet ``` The Dockerfiles for auto-instrumentation can be found in [autoinstrumentation directory](./autoinstrumentation). diff --git a/apis/v1alpha1/instrumentation_types.go b/apis/v1alpha1/instrumentation_types.go index 95a0a13e87..af6ef30a7c 100644 --- a/apis/v1alpha1/instrumentation_types.go +++ b/apis/v1alpha1/instrumentation_types.go @@ -54,6 +54,10 @@ type InstrumentationSpec struct { // Python defines configuration for python auto-instrumentation. // +optional Python Python `json:"python,omitempty"` + + // DotNet defines configuration for DotNet auto-instrumentation. + // +optional + DotNet DotNet `json:"dotnet,omitempty"` } // Resource defines the configuration for the resource attributes, as defined by the OpenTelemetry specification. @@ -129,6 +133,18 @@ type Python struct { Env []corev1.EnvVar `json:"env,omitempty"` } +type DotNet struct { + // Image is a container image with DotNet SDK and auto-instrumentation. + // +optional + Image string `json:"image,omitempty"` + + // Env defines DotNet specific env vars. There are four layers for env vars' definitions and + // the precedence order is: `original container env vars` > `language specific env vars` > `common env vars` > `instrument spec configs' vars`. + // If the former var had been defined, then the other vars would be ignored. + // +optional + Env []corev1.EnvVar `json:"env,omitempty"` +} + // InstrumentationStatus defines status of the instrumentation. type InstrumentationStatus struct { } diff --git a/apis/v1alpha1/instrumentation_webhook.go b/apis/v1alpha1/instrumentation_webhook.go index f2793a8640..3516802d78 100644 --- a/apis/v1alpha1/instrumentation_webhook.go +++ b/apis/v1alpha1/instrumentation_webhook.go @@ -27,11 +27,14 @@ import ( ) const ( - AnnotationDefaultAutoInstrumentationJava = "instrumentation.opentelemetry.io/default-auto-instrumentation-java-image" - AnnotationDefaultAutoInstrumentationNodeJS = "instrumentation.opentelemetry.io/default-auto-instrumentation-nodejs-image" - AnnotationDefaultAutoInstrumentationPython = "instrumentation.opentelemetry.io/default-auto-instrumentation-python-image" - envPrefix = "OTEL_" - envSplunkPrefix = "SPLUNK_" + AnnotationDefaultAutoInstrumentationJava = "instrumentation.opentelemetry.io/default-auto-instrumentation-java-image" + AnnotationDefaultAutoInstrumentationNodeJS = "instrumentation.opentelemetry.io/default-auto-instrumentation-nodejs-image" + AnnotationDefaultAutoInstrumentationPython = "instrumentation.opentelemetry.io/default-auto-instrumentation-python-image" + AnnotationDefaultAutoInstrumentationDotNet = "instrumentation.opentelemetry.io/default-auto-instrumentation-dotnet-image" + envPrefix = "OTEL_" + envSplunkPrefix = "SPLUNK_" + envOtelDotnetAutoTracesEnabledInstrumentations = "OTEL_DOTNET_AUTO_TRACES_ENABLED_INSTRUMENTATIONS" + defaultEnabledTracesInstrumentations = "AspNet,HttpClient,SqlClient" ) // log is for logging in this package. @@ -72,6 +75,18 @@ func (r *Instrumentation) Default() { r.Spec.Python.Image = val } } + if r.Spec.DotNet.Image == "" { + if val, ok := r.Annotations[AnnotationDefaultAutoInstrumentationDotNet]; ok { + r.Spec.DotNet.Image = val + } + } + // by default set all the available instrumentations for dotnet unless the env is already set + if !r.isEnvVarSet(envOtelDotnetAutoTracesEnabledInstrumentations) { + r.Spec.DotNet.Env = append(r.Spec.DotNet.Env, corev1.EnvVar{ + Name: envOtelDotnetAutoTracesEnabledInstrumentations, + Value: defaultEnabledTracesInstrumentations, + }) + } } // +kubebuilder:webhook:verbs=create;update,path=/validate-opentelemetry-io-v1alpha1-instrumentation,mutating=false,failurePolicy=fail,groups=opentelemetry.io,resources=instrumentations,versions=v1alpha1,name=vinstrumentationcreateupdate.kb.io,sideEffects=none,admissionReviewVersions=v1 @@ -125,6 +140,9 @@ func (in *Instrumentation) validate() error { if err := in.validateEnv(in.Spec.Python.Env); err != nil { return err } + if err := in.validateEnv(in.Spec.DotNet.Env); err != nil { + return err + } return nil } @@ -137,3 +155,12 @@ func (in *Instrumentation) validateEnv(envs []corev1.EnvVar) error { } return nil } + +func (in *Instrumentation) isEnvVarSet(name string) bool { + for _, env := range in.Spec.DotNet.Env { + if env.Name == name { + return true + } + } + return false +} diff --git a/apis/v1alpha1/instrumentation_webhook_test.go b/apis/v1alpha1/instrumentation_webhook_test.go index 622e3ce8e6..89ffb74cf0 100644 --- a/apis/v1alpha1/instrumentation_webhook_test.go +++ b/apis/v1alpha1/instrumentation_webhook_test.go @@ -18,6 +18,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -28,6 +29,7 @@ func TestInstrumentationDefaultingWebhook(t *testing.T) { AnnotationDefaultAutoInstrumentationJava: "java-img:1", AnnotationDefaultAutoInstrumentationNodeJS: "nodejs-img:1", AnnotationDefaultAutoInstrumentationPython: "python-img:1", + AnnotationDefaultAutoInstrumentationDotNet: "dotnet-img:1", }, }, } @@ -35,6 +37,38 @@ func TestInstrumentationDefaultingWebhook(t *testing.T) { assert.Equal(t, "java-img:1", inst.Spec.Java.Image) assert.Equal(t, "nodejs-img:1", inst.Spec.NodeJS.Image) assert.Equal(t, "python-img:1", inst.Spec.Python.Image) + assert.Equal(t, "dotnet-img:1", inst.Spec.DotNet.Image) + assert.Equal(t, true, inst.isEnvVarSet(envOtelDotnetAutoTracesEnabledInstrumentations)) +} + +func TestInstrumentationDefaultingWebhookOtelDotNetTracesEnabledInstruEnvSet(t *testing.T) { + inst := &Instrumentation{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + AnnotationDefaultAutoInstrumentationJava: "java-img:1", + AnnotationDefaultAutoInstrumentationNodeJS: "nodejs-img:1", + AnnotationDefaultAutoInstrumentationPython: "python-img:1", + AnnotationDefaultAutoInstrumentationDotNet: "dotnet-img:1", + }, + }, + Spec: InstrumentationSpec{ + DotNet: DotNet{ + Env: []v1.EnvVar{ + { + Name: envOtelDotnetAutoTracesEnabledInstrumentations, + Value: "AspNet,HttpClient", + }, + }, + }, + }, + } + inst.Default() + for _, env := range inst.Spec.DotNet.Env { + if env.Name == envOtelDotnetAutoTracesEnabledInstrumentations { + assert.Equal(t, "AspNet,HttpClient", env.Value) + break + } + } } func TestInstrumentationValidatingWebhook(t *testing.T) { diff --git a/apis/v1alpha1/zz_generated.deepcopy.go b/apis/v1alpha1/zz_generated.deepcopy.go index a1d95abb28..cb60f0ca6e 100644 --- a/apis/v1alpha1/zz_generated.deepcopy.go +++ b/apis/v1alpha1/zz_generated.deepcopy.go @@ -24,6 +24,28 @@ import ( "k8s.io/apimachinery/pkg/runtime" ) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DotNet) DeepCopyInto(out *DotNet) { + *out = *in + if in.Env != nil { + in, out := &in.Env, &out.Env + *out = make([]v1.EnvVar, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DotNet. +func (in *DotNet) DeepCopy() *DotNet { + if in == nil { + return nil + } + out := new(DotNet) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Exporter) DeepCopyInto(out *Exporter) { *out = *in @@ -119,6 +141,7 @@ func (in *InstrumentationSpec) DeepCopyInto(out *InstrumentationSpec) { in.Java.DeepCopyInto(&out.Java) in.NodeJS.DeepCopyInto(&out.NodeJS) in.Python.DeepCopyInto(&out.Python) + in.DotNet.DeepCopyInto(&out.DotNet) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InstrumentationSpec. diff --git a/bundle/manifests/opentelemetry.io_instrumentations.yaml b/bundle/manifests/opentelemetry.io_instrumentations.yaml index d00551d3a7..832f9bf5d2 100644 --- a/bundle/manifests/opentelemetry.io_instrumentations.yaml +++ b/bundle/manifests/opentelemetry.io_instrumentations.yaml @@ -53,6 +53,127 @@ spec: description: InstrumentationSpec defines the desired state of OpenTelemetry SDK and instrumentation. properties: + dotnet: + description: DotNet defines configuration for DotNet auto-instrumentation. + properties: + env: + description: 'Env defines DotNet specific env vars. There are + four layers for env vars'' definitions and the precedence order + is: `original container env vars` > `language specific env vars` + > `common env vars` > `instrument spec configs'' vars`. If the + former var had been defined, then the other vars would be ignored.' + items: + description: EnvVar represents an environment variable present + in a Container. + properties: + name: + description: Name of the environment variable. Must be a + C_IDENTIFIER. + type: string + value: + description: 'Variable references $(VAR_NAME) are expanded + using the previously defined environment variables in + the container and any service environment variables. If + a variable cannot be resolved, the reference in the input + string will be unchanged. Double $$ are reduced to a single + $, which allows for escaping the $(VAR_NAME) syntax: i.e. + "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". + Escaped references will never be expanded, regardless + of whether the variable exists or not. Defaults to "".' + type: string + valueFrom: + description: Source for the environment variable's value. + Cannot be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the ConfigMap or its + key must be defined + type: boolean + required: + - key + type: object + fieldRef: + description: 'Selects a field of the pod: supports metadata.name, + metadata.namespace, `metadata.labels['''']`, + `metadata.annotations['''']`, spec.nodeName, + spec.serviceAccountName, status.hostIP, status.podIP, + status.podIPs.' + properties: + apiVersion: + description: Version of the schema the FieldPath + is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in the + specified API version. + type: string + required: + - fieldPath + type: object + resourceFieldRef: + description: 'Selects a resource of the container: only + resources limits and requests (limits.cpu, limits.memory, + limits.ephemeral-storage, requests.cpu, requests.memory + and requests.ephemeral-storage) are currently supported.' + properties: + containerName: + description: 'Container name: required for volumes, + optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format of the + exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + secretKeyRef: + description: Selects a key of a secret in the pod's + namespace + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the Secret or its key + must be defined + type: boolean + required: + - key + type: object + type: object + required: + - name + type: object + type: array + image: + description: Image is a container image with DotNet SDK and auto-instrumentation. + type: string + type: object env: description: 'Env defines common env vars. There are four layers for env vars'' definitions and the precedence order is: `original container diff --git a/config/crd/bases/opentelemetry.io_instrumentations.yaml b/config/crd/bases/opentelemetry.io_instrumentations.yaml index b31e5fedc1..c1354e589d 100644 --- a/config/crd/bases/opentelemetry.io_instrumentations.yaml +++ b/config/crd/bases/opentelemetry.io_instrumentations.yaml @@ -52,6 +52,127 @@ spec: description: InstrumentationSpec defines the desired state of OpenTelemetry SDK and instrumentation. properties: + dotnet: + description: DotNet defines configuration for DotNet auto-instrumentation. + properties: + env: + description: 'Env defines DotNet specific env vars. There are + four layers for env vars'' definitions and the precedence order + is: `original container env vars` > `language specific env vars` + > `common env vars` > `instrument spec configs'' vars`. If the + former var had been defined, then the other vars would be ignored.' + items: + description: EnvVar represents an environment variable present + in a Container. + properties: + name: + description: Name of the environment variable. Must be a + C_IDENTIFIER. + type: string + value: + description: 'Variable references $(VAR_NAME) are expanded + using the previously defined environment variables in + the container and any service environment variables. If + a variable cannot be resolved, the reference in the input + string will be unchanged. Double $$ are reduced to a single + $, which allows for escaping the $(VAR_NAME) syntax: i.e. + "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". + Escaped references will never be expanded, regardless + of whether the variable exists or not. Defaults to "".' + type: string + valueFrom: + description: Source for the environment variable's value. + Cannot be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the ConfigMap or its + key must be defined + type: boolean + required: + - key + type: object + fieldRef: + description: 'Selects a field of the pod: supports metadata.name, + metadata.namespace, `metadata.labels['''']`, + `metadata.annotations['''']`, spec.nodeName, + spec.serviceAccountName, status.hostIP, status.podIP, + status.podIPs.' + properties: + apiVersion: + description: Version of the schema the FieldPath + is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in the + specified API version. + type: string + required: + - fieldPath + type: object + resourceFieldRef: + description: 'Selects a resource of the container: only + resources limits and requests (limits.cpu, limits.memory, + limits.ephemeral-storage, requests.cpu, requests.memory + and requests.ephemeral-storage) are currently supported.' + properties: + containerName: + description: 'Container name: required for volumes, + optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format of the + exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + secretKeyRef: + description: Selects a key of a secret in the pod's + namespace + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the Secret or its key + must be defined + type: boolean + required: + - key + type: object + type: object + required: + - name + type: object + type: array + image: + description: Image is a container image with DotNet SDK and auto-instrumentation. + type: string + type: object env: description: 'Env defines common env vars. There are four layers for env vars'' definitions and the precedence order is: `original container diff --git a/docs/api.md b/docs/api.md index 8802302ac4..57b8d78a9a 100644 --- a/docs/api.md +++ b/docs/api.md @@ -86,6 +86,13 @@ InstrumentationSpec defines the desired state of OpenTelemetry SDK and instrumen + dotnet + object + + DotNet defines configuration for DotNet auto-instrumentation.
+ + false + env []object @@ -145,6 +152,286 @@ InstrumentationSpec defines the desired state of OpenTelemetry SDK and instrumen +### Instrumentation.spec.dotnet +[↩ Parent](#instrumentationspec) + + + +DotNet defines configuration for DotNet auto-instrumentation. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
env[]object + Env defines DotNet specific env vars. There are four layers for env vars' definitions and the precedence order is: `original container env vars` > `language specific env vars` > `common env vars` > `instrument spec configs' vars`. If the former var had been defined, then the other vars would be ignored.
+
false
imagestring + Image is a container image with DotNet SDK and auto-instrumentation.
+
false
+ + +### Instrumentation.spec.dotnet.env[index] +[↩ Parent](#instrumentationspecdotnet) + + + +EnvVar represents an environment variable present in a Container. + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
namestring + Name of the environment variable. Must be a C_IDENTIFIER.
+
true
valuestring + Variable references $(VAR_NAME) are expanded using the previously defined environment variables in the container and any service environment variables. If a variable cannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". Escaped references will never be expanded, regardless of whether the variable exists or not. Defaults to "".
+
false
valueFromobject + Source for the environment variable's value. Cannot be used if value is not empty.
+
false
+ + +### Instrumentation.spec.dotnet.env[index].valueFrom +[↩ Parent](#instrumentationspecdotnetenvindex) + + + +Source for the environment variable's value. Cannot be used if value is not empty. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
configMapKeyRefobject + Selects a key of a ConfigMap.
+
false
fieldRefobject + Selects a field of the pod: supports metadata.name, metadata.namespace, `metadata.labels['']`, `metadata.annotations['']`, spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP, status.podIPs.
+
false
resourceFieldRefobject + Selects a resource of the container: only resources limits and requests (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported.
+
false
secretKeyRefobject + Selects a key of a secret in the pod's namespace
+
false
+ + +### Instrumentation.spec.dotnet.env[index].valueFrom.configMapKeyRef +[↩ Parent](#instrumentationspecdotnetenvindexvaluefrom) + + + +Selects a key of a ConfigMap. + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
keystring + The key to select.
+
true
namestring + Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?
+
false
optionalboolean + Specify whether the ConfigMap or its key must be defined
+
false
+ + +### Instrumentation.spec.dotnet.env[index].valueFrom.fieldRef +[↩ Parent](#instrumentationspecdotnetenvindexvaluefrom) + + + +Selects a field of the pod: supports metadata.name, metadata.namespace, `metadata.labels['']`, `metadata.annotations['']`, spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP, status.podIPs. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
fieldPathstring + Path of the field to select in the specified API version.
+
true
apiVersionstring + Version of the schema the FieldPath is written in terms of, defaults to "v1".
+
false
+ + +### Instrumentation.spec.dotnet.env[index].valueFrom.resourceFieldRef +[↩ Parent](#instrumentationspecdotnetenvindexvaluefrom) + + + +Selects a resource of the container: only resources limits and requests (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported. + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
resourcestring + Required: resource to select
+
true
containerNamestring + Container name: required for volumes, optional for env vars
+
false
divisorint or string + Specifies the output format of the exposed resources, defaults to "1"
+
false
+ + +### Instrumentation.spec.dotnet.env[index].valueFrom.secretKeyRef +[↩ Parent](#instrumentationspecdotnetenvindexvaluefrom) + + + +Selects a key of a secret in the pod's namespace + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
keystring + The key of the secret to select from. Must be a valid secret key.
+
true
namestring + Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?
+
false
optionalboolean + Specify whether the Secret or its key must be defined
+
false
+ + ### Instrumentation.spec.env[index] [↩ Parent](#instrumentationspec) diff --git a/internal/config/main.go b/internal/config/main.go index bed73be4da..f2821e8838 100644 --- a/internal/config/main.go +++ b/internal/config/main.go @@ -51,6 +51,7 @@ type Config struct { autoInstrumentationJavaImage string autoInstrumentationNodeJSImage string autoInstrumentationPythonImage string + autoInstrumentationDotNetImage string labelsFilter []string } @@ -82,6 +83,7 @@ func New(opts ...Option) Config { autoInstrumentationJavaImage: o.autoInstrumentationJavaImage, autoInstrumentationNodeJSImage: o.autoInstrumentationNodeJSImage, autoInstrumentationPythonImage: o.autoInstrumentationPythonImage, + autoInstrumentationDotNetImage: o.autoInstrumentationDotNetImage, labelsFilter: o.labelsFilter, } } @@ -177,6 +179,11 @@ func (c *Config) AutoInstrumentationPythonImage() string { return c.autoInstrumentationPythonImage } +// AutoInstrumentationDotNetImage returns OpenTelemetry DotNet auto-instrumentation container image. +func (c *Config) AutoInstrumentationDotNetImage() string { + return c.autoInstrumentationDotNetImage +} + // Returns the filters converted to regex strings used to filter out unwanted labels from propagations. func (c *Config) LabelsFilter() []string { return c.labelsFilter diff --git a/internal/config/options.go b/internal/config/options.go index 37fe630a37..c71e2e27d5 100644 --- a/internal/config/options.go +++ b/internal/config/options.go @@ -37,6 +37,7 @@ type options struct { autoInstrumentationJavaImage string autoInstrumentationNodeJSImage string autoInstrumentationPythonImage string + autoInstrumentationDotNetImage string collectorConfigMapEntry string targetAllocatorConfigMapEntry string logger logr.Logger @@ -119,6 +120,12 @@ func WithAutoInstrumentationPythonImage(s string) Option { } } +func WithAutoInstrumentationDotNetImage(s string) Option { + return func(o *options) { + o.autoInstrumentationDotNetImage = s + } +} + func WithLabelFilters(labelFilters []string) Option { return func(o *options) { diff --git a/internal/version/main.go b/internal/version/main.go index 5b2dcc5397..d6e7cf1db6 100644 --- a/internal/version/main.go +++ b/internal/version/main.go @@ -28,6 +28,7 @@ var ( autoInstrumentationJava string autoInstrumentationNodeJS string autoInstrumentationPython string + autoInstrumentationDotNet string ) // Version holds this Operator's version as well as the version of some of the components it uses. @@ -40,6 +41,7 @@ type Version struct { AutoInstrumentationJava string `json:"auto-instrumentation-java"` AutoInstrumentationNodeJS string `json:"auto-instrumentation-nodejs"` AutoInstrumentationPython string `json:"auto-instrumentation-python"` + AutoInstrumentationDotNet string `json:"auto-instrumentation-dotnet"` } // Get returns the Version object with the relevant information. @@ -53,12 +55,13 @@ func Get() Version { AutoInstrumentationJava: AutoInstrumentationJava(), AutoInstrumentationNodeJS: AutoInstrumentationNodeJS(), AutoInstrumentationPython: AutoInstrumentationPython(), + AutoInstrumentationDotNet: AutoInstrumentationDotNet(), } } func (v Version) String() string { return fmt.Sprintf( - "Version(Operator='%v', BuildDate='%v', OpenTelemetryCollector='%v', Go='%v', TargetAllocator='%v', AutoInstrumentationJava='%v', AutoInstrumentationNodeJS='%v', AutoInstrumentationPython='%v')", + "Version(Operator='%v', BuildDate='%v', OpenTelemetryCollector='%v', Go='%v', TargetAllocator='%v', AutoInstrumentationJava='%v', AutoInstrumentationNodeJS='%v', AutoInstrumentationPython='%v', AutoInstrumentationDotNet='%v')", v.Operator, v.BuildDate, v.OpenTelemetryCollector, @@ -67,6 +70,7 @@ func (v Version) String() string { v.AutoInstrumentationJava, v.AutoInstrumentationNodeJS, v.AutoInstrumentationPython, + v.AutoInstrumentationDotNet, ) } @@ -112,3 +116,10 @@ func AutoInstrumentationPython() string { } return "0.0.0" } + +func AutoInstrumentationDotNet() string { + if len(autoInstrumentationDotNet) > 0 { + return autoInstrumentationDotNet + } + return "0.0.0" +} diff --git a/main.go b/main.go index c7928d8555..70d24c869e 100644 --- a/main.go +++ b/main.go @@ -80,6 +80,7 @@ func main() { autoInstrumentationJava string autoInstrumentationNodeJS string autoInstrumentationPython string + autoInstrumentationDotNet string labelsFilter []string webhookPort int ) @@ -94,6 +95,7 @@ func main() { pflag.StringVar(&autoInstrumentationJava, "auto-instrumentation-java-image", fmt.Sprintf("ghcr.io/open-telemetry/opentelemetry-operator/autoinstrumentation-java:%s", v.AutoInstrumentationJava), "The default OpenTelemetry Java instrumentation image. This image is used when no image is specified in the CustomResource.") pflag.StringVar(&autoInstrumentationNodeJS, "auto-instrumentation-nodejs-image", fmt.Sprintf("ghcr.io/open-telemetry/opentelemetry-operator/autoinstrumentation-nodejs:%s", v.AutoInstrumentationNodeJS), "The default OpenTelemetry NodeJS instrumentation image. This image is used when no image is specified in the CustomResource.") pflag.StringVar(&autoInstrumentationPython, "auto-instrumentation-python-image", fmt.Sprintf("ghcr.io/open-telemetry/opentelemetry-operator/autoinstrumentation-python:%s", v.AutoInstrumentationPython), "The default OpenTelemetry Python instrumentation image. This image is used when no image is specified in the CustomResource.") + pflag.StringVar(&autoInstrumentationDotNet, "auto-instrumentation-dotnet-image", fmt.Sprintf("ghcr.io/open-telemetry/opentelemetry-operator/autoinstrumentation-dotnet:%s", v.AutoInstrumentationDotNet), "The default OpenTelemetry DotNet instrumentation image. This image is used when no image is specified in the CustomResource.") pflag.StringArrayVar(&labelsFilter, "labels", []string{}, "Labels to filter away from propagating onto deploys") pflag.IntVar(&webhookPort, "webhook-port", 9443, "The port the webhook endpoint binds to.") pflag.Parse() @@ -108,6 +110,7 @@ func main() { "auto-instrumentation-java", autoInstrumentationJava, "auto-instrumentation-nodejs", autoInstrumentationNodeJS, "auto-instrumentation-python", autoInstrumentationPython, + "auto-instrumentation-dotnet", autoInstrumentationDotNet, "build-date", v.BuildDate, "go-version", v.Go, "go-arch", runtime.GOARCH, @@ -132,6 +135,7 @@ func main() { config.WithAutoInstrumentationJavaImage(autoInstrumentationJava), config.WithAutoInstrumentationNodeJSImage(autoInstrumentationNodeJS), config.WithAutoInstrumentationPythonImage(autoInstrumentationPython), + config.WithAutoInstrumentationDotNetImage(autoInstrumentationDotNet), config.WithAutoDetect(ad), config.WithLabelFilters(labelsFilter), ) @@ -200,6 +204,7 @@ func main() { otelv1alpha1.AnnotationDefaultAutoInstrumentationJava: autoInstrumentationJava, otelv1alpha1.AnnotationDefaultAutoInstrumentationNodeJS: autoInstrumentationNodeJS, otelv1alpha1.AnnotationDefaultAutoInstrumentationPython: autoInstrumentationPython, + otelv1alpha1.AnnotationDefaultAutoInstrumentationDotNet: autoInstrumentationDotNet, }, }, }).SetupWebhookWithManager(mgr); err != nil { @@ -262,6 +267,7 @@ func addDependencies(_ context.Context, mgr ctrl.Manager, cfg config.Config, v v DefaultAutoInstJava: cfg.AutoInstrumentationJavaImage(), DefaultAutoInstNodeJS: cfg.AutoInstrumentationNodeJSImage(), DefaultAutoInstPython: cfg.AutoInstrumentationPythonImage(), + DefaultAutoInstDotNet: cfg.AutoInstrumentationDotNetImage(), Client: mgr.GetClient(), } return u.ManagedInstances(c) diff --git a/pkg/instrumentation/annotation.go b/pkg/instrumentation/annotation.go index 3f1519b2cb..89c1a7dc02 100644 --- a/pkg/instrumentation/annotation.go +++ b/pkg/instrumentation/annotation.go @@ -26,6 +26,7 @@ const ( annotationInjectJava = "instrumentation.opentelemetry.io/inject-java" annotationInjectNodeJS = "instrumentation.opentelemetry.io/inject-nodejs" annotationInjectPython = "instrumentation.opentelemetry.io/inject-python" + annotationInjectDotNet = "instrumentation.opentelemetry.io/inject-dotnet" annotationInjectContainerName = "instrumentation.opentelemetry.io/container-names" ) diff --git a/pkg/instrumentation/dotnet.go b/pkg/instrumentation/dotnet.go new file mode 100644 index 0000000000..ee933ef339 --- /dev/null +++ b/pkg/instrumentation/dotnet.go @@ -0,0 +1,118 @@ +// 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 ( + "fmt" + + "github.com/go-logr/logr" + corev1 "k8s.io/api/core/v1" + + "github.com/open-telemetry/opentelemetry-operator/apis/v1alpha1" +) + +const ( + envDotNetAdditionalDeps = "DOTNET_ADDITIONAL_DEPS" + envDotNetSharedStore = "DOTNET_SHARED_STORE" + envDotNetStartupHook = "DOTNET_STARTUP_HOOKS" + dotNetAdditionalDepsPath = "./otel-auto-instrumentation/AdditionalDeps" + dotNetSharedStorePath = "/otel-auto-instrumentation/store" + dotNetStartupHookPath = "/otel-auto-instrumentation/netcoreapp3.1/OpenTelemetry.AutoInstrumentation.StartupHook.dll" +) + +func injectDotNetSDK(logger logr.Logger, dotNetSpec v1alpha1.DotNet, pod corev1.Pod, index int) corev1.Pod { + // caller checks if there is at least one container + container := pod.Spec.Containers[index] + + // inject env vars + for _, env := range dotNetSpec.Env { + idx := getIndexOfEnv(container.Env, env.Name) + if idx == -1 { + container.Env = append(container.Env, env) + } + } + + idx := getIndexOfEnv(container.Env, envDotNetStartupHook) + if idx == -1 { + container.Env = append(container.Env, corev1.EnvVar{ + Name: envDotNetStartupHook, + Value: dotNetStartupHookPath, + }) + } else if idx > -1 { + if container.Env[idx].ValueFrom != nil { + // TODO add to status object or submit it as an event + logger.Info("Skipping DotNet SDK injection, the container defines DOTNET_STARTUP_HOOKS env var value via ValueFrom", "container", container.Name) + return pod + } + container.Env[idx].Value = fmt.Sprintf("%s:%s", container.Env[idx].Value, dotNetStartupHookPath) + } + + idx = getIndexOfEnv(container.Env, envDotNetAdditionalDeps) + if idx == -1 { + container.Env = append(container.Env, corev1.EnvVar{ + Name: envDotNetAdditionalDeps, + Value: dotNetAdditionalDepsPath, + }) + } else if idx > -1 { + if container.Env[idx].ValueFrom != nil { + // TODO add to status object or submit it as an event + logger.Info("Skipping DotNet SDK injection, the container defines DOTNET_ADDITIONAL_DEPS env var value via ValueFrom", "container", container.Name) + return pod + } + container.Env[idx].Value = fmt.Sprintf("%s:%s", container.Env[idx].Value, dotNetAdditionalDepsPath) + } + + idx = getIndexOfEnv(container.Env, envDotNetSharedStore) + if idx == -1 { + container.Env = append(container.Env, corev1.EnvVar{ + Name: envDotNetSharedStore, + Value: dotNetSharedStorePath, + }) + } else if idx > -1 { + if container.Env[idx].ValueFrom != nil { + // TODO add to status object or submit it as an event + logger.Info("Skipping DotNet SDK injection, the container defines DOTNET_SHARED_STORE env var value via ValueFrom", "container", container.Name) + return pod + } + container.Env[idx].Value = fmt.Sprintf("%s:%s", container.Env[idx].Value, dotNetSharedStorePath) + } + + container.VolumeMounts = append(container.VolumeMounts, corev1.VolumeMount{ + Name: volumeName, + MountPath: "/otel-auto-instrumentation", + }) + + // We just inject Volumes and init containers for the first processed container + if isInitContainerMissing(pod) { + 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: dotNetSpec.Image, + Command: []string{"cp", "-a", "/autoinstrumentation/.", "/otel-auto-instrumentation/"}, + VolumeMounts: []corev1.VolumeMount{{ + Name: volumeName, + MountPath: "/otel-auto-instrumentation", + }}, + }) + } + + pod.Spec.Containers[index] = container + return pod +} diff --git a/pkg/instrumentation/dotnet_test.go b/pkg/instrumentation/dotnet_test.go new file mode 100644 index 0000000000..7e3e827e6f --- /dev/null +++ b/pkg/instrumentation/dotnet_test.go @@ -0,0 +1,274 @@ +// 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 ( + "fmt" + "testing" + + "github.com/go-logr/logr" + "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" + + "github.com/open-telemetry/opentelemetry-operator/apis/v1alpha1" +) + +func TestInjectDotNetSDK(t *testing.T) { + tests := []struct { + name string + v1alpha1.DotNet + pod corev1.Pod + expected corev1.Pod + }{ + { + name: "DOTNET_STARTUP_HOOKS, DOTNET_SHARED_STORE, DOTNET_ADDITIONAL_DEPS not defined", + DotNet: v1alpha1.DotNet{Image: "foo/bar:1", Env: []corev1.EnvVar{{Name: "OTEL_DOTNET_AUTO_TRACES_ENABLED_INSTRUMENTATIONS", Value: "AspNet,HttpClient,SqlClient"}}}, + pod: corev1.Pod{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + {}, + }, + }, + }, + expected: corev1.Pod{ + Spec: corev1.PodSpec{ + Volumes: []corev1.Volume{ + { + Name: volumeName, + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{}, + }, + }, + }, + InitContainers: []corev1.Container{ + { + Name: initContainerName, + Image: "foo/bar:1", + Command: []string{"cp", "-a", "/autoinstrumentation/.", "/otel-auto-instrumentation/"}, + VolumeMounts: []corev1.VolumeMount{{ + Name: volumeName, + MountPath: "/otel-auto-instrumentation", + }}, + }, + }, + Containers: []corev1.Container{ + { + VolumeMounts: []corev1.VolumeMount{ + { + Name: volumeName, + MountPath: "/otel-auto-instrumentation", + }, + }, + Env: []corev1.EnvVar{ + { + Name: "OTEL_DOTNET_AUTO_TRACES_ENABLED_INSTRUMENTATIONS", + Value: "AspNet,HttpClient,SqlClient", + }, + { + Name: envDotNetStartupHook, + Value: dotNetStartupHookPath, + }, + { + Name: envDotNetAdditionalDeps, + Value: dotNetAdditionalDepsPath, + }, + { + Name: envDotNetSharedStore, + Value: dotNetSharedStorePath, + }, + }, + }, + }, + }, + }, + }, + { + name: "DOTNET_STARTUP_HOOKS, DOTNET_ADDITIONAL_DEPS, DOTNET_SHARED_STORE defined", + DotNet: v1alpha1.DotNet{Image: "foo/bar:1"}, + pod: corev1.Pod{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Env: []corev1.EnvVar{ + { + Name: envDotNetStartupHook, + Value: "/foo:/bar", + }, + { + Name: envDotNetAdditionalDeps, + Value: "/foo:/bar", + }, + { + Name: envDotNetSharedStore, + Value: "/foo:/bar", + }, + }, + }, + }, + }, + }, + expected: corev1.Pod{ + Spec: corev1.PodSpec{ + Volumes: []corev1.Volume{ + { + Name: volumeName, + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{}, + }, + }, + }, + InitContainers: []corev1.Container{ + { + Name: initContainerName, + Image: "foo/bar:1", + Command: []string{"cp", "-a", "/autoinstrumentation/.", "/otel-auto-instrumentation/"}, + VolumeMounts: []corev1.VolumeMount{{ + Name: volumeName, + MountPath: "/otel-auto-instrumentation", + }}, + }, + }, + Containers: []corev1.Container{ + { + VolumeMounts: []corev1.VolumeMount{ + { + Name: volumeName, + MountPath: "/otel-auto-instrumentation", + }, + }, + Env: []corev1.EnvVar{ + { + Name: envDotNetStartupHook, + Value: fmt.Sprintf("%s:%s", "/foo:/bar", dotNetStartupHookPath), + }, + { + Name: envDotNetAdditionalDeps, + Value: fmt.Sprintf("%s:%s", "/foo:/bar", dotNetAdditionalDepsPath), + }, + { + Name: envDotNetSharedStore, + Value: fmt.Sprintf("%s:%s", "/foo:/bar", dotNetSharedStorePath), + }, + }, + }, + }, + }, + }, + }, + { + name: "DOTNET_STARTUP_HOOKS defined as ValueFrom", + DotNet: v1alpha1.DotNet{Image: "foo/bar:1"}, + pod: corev1.Pod{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Env: []corev1.EnvVar{ + { + Name: envDotNetStartupHook, + ValueFrom: &corev1.EnvVarSource{}, + }, + }, + }, + }, + }, + }, + expected: corev1.Pod{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Env: []corev1.EnvVar{ + { + Name: envDotNetStartupHook, + ValueFrom: &corev1.EnvVarSource{}, + }, + }, + }, + }, + }, + }, + }, + { + name: "DOTNET_ADDITIONAL_DEPS defined as ValueFrom", + DotNet: v1alpha1.DotNet{Image: "foo/bar:1"}, + pod: corev1.Pod{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Env: []corev1.EnvVar{ + { + Name: envDotNetAdditionalDeps, + ValueFrom: &corev1.EnvVarSource{}, + }, + }, + }, + }, + }, + }, + expected: corev1.Pod{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Env: []corev1.EnvVar{ + { + Name: envDotNetAdditionalDeps, + ValueFrom: &corev1.EnvVarSource{}, + }, + }, + }, + }, + }, + }, + }, + { + name: "DOTNET_SHARED_STORE defined as ValueFrom", + DotNet: v1alpha1.DotNet{Image: "foo/bar:1"}, + pod: corev1.Pod{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Env: []corev1.EnvVar{ + { + Name: envDotNetSharedStore, + ValueFrom: &corev1.EnvVarSource{}, + }, + }, + }, + }, + }, + }, + expected: corev1.Pod{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Env: []corev1.EnvVar{ + { + Name: envDotNetSharedStore, + ValueFrom: &corev1.EnvVarSource{}, + }, + }, + }, + }, + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + pod := injectDotNetSDK(logr.Discard(), test.DotNet, test.pod, 0) + assert.Equal(t, test.expected, pod) + }) + } +} diff --git a/pkg/instrumentation/podmutator.go b/pkg/instrumentation/podmutator.go index cc49a8eab1..41c1830df9 100644 --- a/pkg/instrumentation/podmutator.go +++ b/pkg/instrumentation/podmutator.go @@ -43,6 +43,7 @@ type languageInstrumentations struct { Java *v1alpha1.Instrumentation NodeJS *v1alpha1.Instrumentation Python *v1alpha1.Instrumentation + DotNet *v1alpha1.Instrumentation } var _ webhookhandler.PodMutator = (*instPodMutator)(nil) @@ -95,7 +96,14 @@ func (pm *instPodMutator) Mutate(ctx context.Context, ns corev1.Namespace, pod c } insts.Python = inst - if insts.Java == nil && insts.NodeJS == nil && insts.Python == nil { + if inst, err = pm.getInstrumentationInstance(ctx, ns, pod, annotationInjectDotNet); err != nil { + // we still allow the pod to be created, but we log a message to the operator's logs + logger.Error(err, "failed to select an OpenTelemetry Instrumentation instance for this pod") + return pod, err + } + insts.DotNet = inst + + if insts.Java == nil && insts.NodeJS == nil && insts.Python == nil && insts.DotNet == nil { logger.V(1).Info("annotation not present in deployment, skipping instrumentation injection") return pod, nil } diff --git a/pkg/instrumentation/podmutator_test.go b/pkg/instrumentation/podmutator_test.go index 00aa549c1b..92aa9945d2 100644 --- a/pkg/instrumentation/podmutator_test.go +++ b/pkg/instrumentation/podmutator_test.go @@ -549,6 +549,171 @@ func TestMutatePod(t *testing.T) { }, }, }, + { + name: "dotnet injection, true", + ns: corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "dotnet", + }, + }, + inst: v1alpha1.Instrumentation{ + ObjectMeta: metav1.ObjectMeta{ + Name: "example-inst", + Namespace: "dotnet", + }, + Spec: v1alpha1.InstrumentationSpec{ + DotNet: v1alpha1.DotNet{ + Image: "otel/dotnet:1", + Env: []corev1.EnvVar{ + { + Name: "OTEL_LOG_LEVEL", + Value: "debug", + }, + { + Name: "OTEL_EXPORTER_OTLP_ENDPOINT", + Value: "http://localhost:4317", + }, + }, + }, + Exporter: v1alpha1.Exporter{ + Endpoint: "http://collector:12345", + }, + Env: []corev1.EnvVar{ + { + Name: "OTEL_EXPORTER_OTLP_TIMEOUT", + Value: "20", + }, + { + Name: "OTEL_TRACES_SAMPLER", + Value: "parentbased_traceidratio", + }, + { + Name: "OTEL_TRACES_SAMPLER_ARG", + Value: "0.85", + }, + { + Name: "SPLUNK_TRACE_RESPONSE_HEADER_ENABLED", + Value: "true", + }, + }, + }, + }, + pod: corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + annotationInjectDotNet: "true", + }, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "app", + }, + }, + }, + }, + expected: corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + annotationInjectDotNet: "true", + }, + }, + Spec: corev1.PodSpec{ + Volumes: []corev1.Volume{ + { + Name: "opentelemetry-auto-instrumentation", + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{}, + }, + }, + }, + InitContainers: []corev1.Container{ + { + Name: initContainerName, + Image: "otel/dotnet:1", + Command: []string{"cp", "-a", "/autoinstrumentation/.", "/otel-auto-instrumentation/"}, + VolumeMounts: []corev1.VolumeMount{{ + Name: volumeName, + MountPath: "/otel-auto-instrumentation", + }}, + }, + }, + Containers: []corev1.Container{ + { + Name: "app", + Env: []corev1.EnvVar{ + { + Name: "OTEL_LOG_LEVEL", + Value: "debug", + }, + { + Name: "OTEL_EXPORTER_OTLP_ENDPOINT", + Value: "http://localhost:4317", + }, + { + Name: envDotNetStartupHook, + Value: dotNetStartupHookPath, + }, + { + Name: envDotNetAdditionalDeps, + Value: dotNetAdditionalDepsPath, + }, + { + Name: envDotNetSharedStore, + Value: dotNetSharedStorePath, + }, + { + Name: "OTEL_EXPORTER_OTLP_TIMEOUT", + Value: "20", + }, + { + Name: "OTEL_TRACES_SAMPLER", + Value: "parentbased_traceidratio", + }, + { + Name: "OTEL_TRACES_SAMPLER_ARG", + Value: "0.85", + }, + { + Name: "SPLUNK_TRACE_RESPONSE_HEADER_ENABLED", + Value: "true", + }, + { + Name: "OTEL_SERVICE_NAME", + Value: "app", + }, + { + Name: "OTEL_RESOURCE_ATTRIBUTES_POD_NAME", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + FieldPath: "metadata.name", + }, + }, + }, + { + Name: "OTEL_RESOURCE_ATTRIBUTES_NODE_NAME", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + FieldPath: "spec.nodeName", + }, + }, + }, + { + Name: "OTEL_RESOURCE_ATTRIBUTES", + Value: "k8s.container.name=app,k8s.namespace.name=dotnet,k8s.node.name=$(OTEL_RESOURCE_ATTRIBUTES_NODE_NAME),k8s.pod.name=$(OTEL_RESOURCE_ATTRIBUTES_POD_NAME)", + }, + }, + VolumeMounts: []corev1.VolumeMount{ + { + Name: "opentelemetry-auto-instrumentation", + MountPath: "/otel-auto-instrumentation", + }, + }, + }, + }, + }, + }, + }, { name: "missing annotation", ns: corev1.Namespace{ diff --git a/pkg/instrumentation/sdk.go b/pkg/instrumentation/sdk.go index cbaaa7ac91..996044a0df 100644 --- a/pkg/instrumentation/sdk.go +++ b/pkg/instrumentation/sdk.go @@ -83,6 +83,13 @@ func (i *sdkInjector) inject(ctx context.Context, insts languageInstrumentations pod = i.injectCommonEnvVar(otelinst, pod, index) pod = i.injectCommonSDKConfig(ctx, otelinst, ns, pod, index) } + if insts.DotNet != nil { + otelinst := *insts.DotNet + i.logger.V(1).Info("injecting dotnet instrumentation into pod", "otelinst-namespace", otelinst.Namespace, "otelinst-name", otelinst.Name) + pod = injectDotNetSDK(i.logger, otelinst.Spec.DotNet, pod, index) + pod = i.injectCommonEnvVar(otelinst, pod, index) + pod = i.injectCommonSDKConfig(ctx, otelinst, ns, pod, index) + } return pod } diff --git a/pkg/instrumentation/sdk_test.go b/pkg/instrumentation/sdk_test.go index 304d0d93ad..0a6b558be3 100644 --- a/pkg/instrumentation/sdk_test.go +++ b/pkg/instrumentation/sdk_test.go @@ -670,3 +670,109 @@ func TestInjectPython(t *testing.T) { }, }, pod) } + +func TestInjectDotNet(t *testing.T) { + inst := v1alpha1.Instrumentation{ + Spec: v1alpha1.InstrumentationSpec{ + DotNet: v1alpha1.DotNet{ + Image: "img:1", + }, + Exporter: v1alpha1.Exporter{ + Endpoint: "https://collector:4318", + }, + }, + } + insts := languageInstrumentations{ + DotNet: &inst, + } + inj := sdkInjector{ + logger: logr.Discard(), + } + pod := inj.inject(context.Background(), insts, + corev1.Namespace{}, + corev1.Pod{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "app", + }, + }, + }, + }, "") + assert.Equal(t, corev1.Pod{ + Spec: corev1.PodSpec{ + Volumes: []corev1.Volume{ + { + Name: volumeName, + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{}, + }, + }, + }, + InitContainers: []corev1.Container{ + { + Name: initContainerName, + Image: "img:1", + Command: []string{"cp", "-a", "/autoinstrumentation/.", "/otel-auto-instrumentation/"}, + VolumeMounts: []corev1.VolumeMount{{ + Name: volumeName, + MountPath: "/otel-auto-instrumentation", + }}, + }, + }, + Containers: []corev1.Container{ + { + Name: "app", + VolumeMounts: []corev1.VolumeMount{ + { + Name: volumeName, + MountPath: "/otel-auto-instrumentation", + }, + }, + Env: []corev1.EnvVar{ + { + Name: envDotNetStartupHook, + Value: dotNetStartupHookPath, + }, + { + Name: envDotNetAdditionalDeps, + Value: dotNetAdditionalDepsPath, + }, + { + Name: envDotNetSharedStore, + Value: dotNetSharedStorePath, + }, + { + Name: "OTEL_SERVICE_NAME", + Value: "app", + }, + { + Name: "OTEL_EXPORTER_OTLP_ENDPOINT", + Value: "https://collector:4318", + }, + { + Name: "OTEL_RESOURCE_ATTRIBUTES_POD_NAME", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + FieldPath: "metadata.name", + }, + }, + }, + { + Name: "OTEL_RESOURCE_ATTRIBUTES_NODE_NAME", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + FieldPath: "spec.nodeName", + }, + }, + }, + { + Name: "OTEL_RESOURCE_ATTRIBUTES", + Value: "k8s.container.name=app,k8s.node.name=$(OTEL_RESOURCE_ATTRIBUTES_NODE_NAME),k8s.pod.name=$(OTEL_RESOURCE_ATTRIBUTES_POD_NAME)", + }, + }, + }, + }, + }, + }, pod) +} diff --git a/pkg/instrumentation/upgrade/upgrade.go b/pkg/instrumentation/upgrade/upgrade.go index 9a702cbf3f..65e7b6f774 100644 --- a/pkg/instrumentation/upgrade/upgrade.go +++ b/pkg/instrumentation/upgrade/upgrade.go @@ -30,6 +30,7 @@ type InstrumentationUpgrade struct { DefaultAutoInstJava string DefaultAutoInstNodeJS string DefaultAutoInstPython string + DefaultAutoInstDotNet string Client client.Client } @@ -92,5 +93,13 @@ func (u *InstrumentationUpgrade) upgrade(_ context.Context, inst v1alpha1.Instru inst.Annotations[v1alpha1.AnnotationDefaultAutoInstrumentationPython] = u.DefaultAutoInstPython } } + autoInstDotnet := inst.Annotations[v1alpha1.AnnotationDefaultAutoInstrumentationDotNet] + if autoInstDotnet != "" { + // upgrade the image only if the image matches the annotation + if inst.Spec.DotNet.Image == autoInstDotnet { + inst.Spec.DotNet.Image = u.DefaultAutoInstDotNet + inst.Annotations[v1alpha1.AnnotationDefaultAutoInstrumentationDotNet] = u.DefaultAutoInstDotNet + } + } return inst } diff --git a/pkg/instrumentation/upgrade/upgrade_test.go b/pkg/instrumentation/upgrade/upgrade_test.go index 07eb0a9332..89a1c251d5 100644 --- a/pkg/instrumentation/upgrade/upgrade_test.go +++ b/pkg/instrumentation/upgrade/upgrade_test.go @@ -46,6 +46,7 @@ func TestUpgrade(t *testing.T) { v1alpha1.AnnotationDefaultAutoInstrumentationJava: "java:1", v1alpha1.AnnotationDefaultAutoInstrumentationNodeJS: "nodejs:1", v1alpha1.AnnotationDefaultAutoInstrumentationPython: "python:1", + v1alpha1.AnnotationDefaultAutoInstrumentationDotNet: "dotnet:1", }, }, Spec: v1alpha1.InstrumentationSpec{ @@ -58,6 +59,7 @@ func TestUpgrade(t *testing.T) { assert.Equal(t, "java:1", inst.Spec.Java.Image) assert.Equal(t, "nodejs:1", inst.Spec.NodeJS.Image) assert.Equal(t, "python:1", inst.Spec.Python.Image) + assert.Equal(t, "dotnet:1", inst.Spec.DotNet.Image) err = k8sClient.Create(context.Background(), inst) require.NoError(t, err) @@ -66,6 +68,7 @@ func TestUpgrade(t *testing.T) { DefaultAutoInstJava: "java:2", DefaultAutoInstNodeJS: "nodejs:2", DefaultAutoInstPython: "python:2", + DefaultAutoInstDotNet: "dotnet:2", Client: k8sClient, } err = up.ManagedInstances(context.Background()) @@ -83,4 +86,6 @@ func TestUpgrade(t *testing.T) { assert.Equal(t, "nodejs:2", updated.Spec.NodeJS.Image) assert.Equal(t, "python:2", updated.Annotations[v1alpha1.AnnotationDefaultAutoInstrumentationPython]) assert.Equal(t, "python:2", updated.Spec.Python.Image) + assert.Equal(t, "dotnet:2", updated.Annotations[v1alpha1.AnnotationDefaultAutoInstrumentationDotNet]) + assert.Equal(t, "dotnet:2", updated.Spec.DotNet.Image) } diff --git a/tests/e2e/instrumentation-dotnet-multicontainer/00-install-collector.yaml b/tests/e2e/instrumentation-dotnet-multicontainer/00-install-collector.yaml new file mode 100644 index 0000000000..f8e1e98e07 --- /dev/null +++ b/tests/e2e/instrumentation-dotnet-multicontainer/00-install-collector.yaml @@ -0,0 +1,23 @@ +apiVersion: opentelemetry.io/v1alpha1 +kind: OpenTelemetryCollector +metadata: + name: sidecar +spec: + mode: sidecar + config: | + receivers: + otlp: + protocols: + grpc: + http: + processors: + + exporters: + logging: + + service: + pipelines: + traces: + receivers: [otlp] + processors: [] + exporters: [logging] diff --git a/tests/e2e/instrumentation-dotnet-multicontainer/00-install-instrumentation.yaml b/tests/e2e/instrumentation-dotnet-multicontainer/00-install-instrumentation.yaml new file mode 100644 index 0000000000..391b502dfa --- /dev/null +++ b/tests/e2e/instrumentation-dotnet-multicontainer/00-install-instrumentation.yaml @@ -0,0 +1,30 @@ +apiVersion: opentelemetry.io/v1alpha1 +kind: Instrumentation +metadata: + name: dotnet +spec: + env: + - name: OTEL_TRACES_EXPORTER + value: otlp + - name: OTEL_EXPORTER_OTLP_ENDPOINT + value: http://localhost:4317 + - name: OTEL_EXPORTER_OTLP_TIMEOUT + value: "20" + - name: OTEL_TRACES_SAMPLER + value: parentbased_traceidratio + - name: OTEL_TRACES_SAMPLER_ARG + value: "0.85" + - name: SPLUNK_TRACE_RESPONSE_HEADER_ENABLED + value: "true" + exporter: + endpoint: http://localhost:4317 + propagators: + - jaeger + - b3 + sampler: + type: parentbased_traceidratio + argument: "0.25" + dotnet: + env: + - name: OTEL_LOG_LEVEL + value: "debug" \ No newline at end of file diff --git a/tests/e2e/instrumentation-dotnet-multicontainer/01-assert.yaml b/tests/e2e/instrumentation-dotnet-multicontainer/01-assert.yaml new file mode 100644 index 0000000000..9fca3fec8f --- /dev/null +++ b/tests/e2e/instrumentation-dotnet-multicontainer/01-assert.yaml @@ -0,0 +1,84 @@ +apiVersion: v1 +kind: Pod +metadata: + annotations: + sidecar.opentelemetry.io/inject: "true" + instrumentation.opentelemetry.io/container-names: myapp,myrabbit + instrumentation.opentelemetry.io/inject-dotnet: "true" + labels: + app: my-pod-with-sidecar +spec: + containers: + - name: myapp + env: + - name: OTEL_LOG_LEVEL + value: "debug" + - name: OTEL_DOTNET_AUTO_TRACES_ENABLED_INSTRUMENTATIONS + value: AspNet,HttpClient,SqlClient + - name: DOTNET_STARTUP_HOOKS + value: "/otel-auto-instrumentation/netcoreapp3.1/OpenTelemetry.AutoInstrumentation.StartupHook.dll" + - name: DOTNET_ADDITIONAL_DEPS + value: ./otel-auto-instrumentation/AdditionalDeps + - name: DOTNET_SHARED_STORE + value: /otel-auto-instrumentation/store + - name: OTEL_TRACES_EXPORTER + value: otlp + - name: OTEL_EXPORTER_OTLP_ENDPOINT + value: http://localhost:4317 + - name: OTEL_EXPORTER_OTLP_TIMEOUT + value: "20" + - name: OTEL_TRACES_SAMPLER + value: parentbased_traceidratio + - name: OTEL_TRACES_SAMPLER_ARG + value: "0.85" + - name: SPLUNK_TRACE_RESPONSE_HEADER_ENABLED + value: "true" + - name: OTEL_SERVICE_NAME + value: my-deployment-with-sidecar + - name: OTEL_RESOURCE_ATTRIBUTES_POD_NAME + - name: OTEL_RESOURCE_ATTRIBUTES_NODE_NAME + - name: OTEL_PROPAGATORS + value: jaeger,b3 + - name: OTEL_RESOURCE_ATTRIBUTES + volumeMounts: + - mountPath: /var/run/secrets/kubernetes.io/serviceaccount + - mountPath: /otel-auto-instrumentation + name: opentelemetry-auto-instrumentation + - name: myrabbit + env: + - name: OTEL_LOG_LEVEL + value: "debug" + - name: OTEL_DOTNET_AUTO_TRACES_ENABLED_INSTRUMENTATIONS + value: AspNet,HttpClient,SqlClient + - name: DOTNET_STARTUP_HOOKS + value: "/otel-auto-instrumentation/netcoreapp3.1/OpenTelemetry.AutoInstrumentation.StartupHook.dll" + - name: DOTNET_ADDITIONAL_DEPS + value: ./otel-auto-instrumentation/AdditionalDeps + - name: DOTNET_SHARED_STORE + value: /otel-auto-instrumentation/store + - name: OTEL_TRACES_EXPORTER + value: otlp + - name: OTEL_EXPORTER_OTLP_ENDPOINT + value: http://localhost:4317 + - name: OTEL_EXPORTER_OTLP_TIMEOUT + value: "20" + - name: OTEL_TRACES_SAMPLER + value: parentbased_traceidratio + - name: OTEL_TRACES_SAMPLER_ARG + value: "0.85" + - name: SPLUNK_TRACE_RESPONSE_HEADER_ENABLED + value: "true" + - name: OTEL_SERVICE_NAME + value: my-deployment-with-sidecar + - name: OTEL_RESOURCE_ATTRIBUTES_POD_NAME + - name: OTEL_RESOURCE_ATTRIBUTES_NODE_NAME + - name: OTEL_PROPAGATORS + value: jaeger,b3 + - name: OTEL_RESOURCE_ATTRIBUTES + volumeMounts: + - mountPath: /var/run/secrets/kubernetes.io/serviceaccount + - mountPath: /otel-auto-instrumentation + name: opentelemetry-auto-instrumentation + - name: otc-container +status: + phase: Running \ No newline at end of file diff --git a/tests/e2e/instrumentation-dotnet-multicontainer/01-install-app.yaml b/tests/e2e/instrumentation-dotnet-multicontainer/01-install-app.yaml new file mode 100644 index 0000000000..ce6889a699 --- /dev/null +++ b/tests/e2e/instrumentation-dotnet-multicontainer/01-install-app.yaml @@ -0,0 +1,23 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: my-deployment-with-sidecar +spec: + selector: + matchLabels: + app: my-pod-with-sidecar + replicas: 1 + template: + metadata: + labels: + app: my-pod-with-sidecar + annotations: + sidecar.opentelemetry.io/inject: "true" + instrumentation.opentelemetry.io/inject-dotnet: "true" + instrumentation.opentelemetry.io/container-names: "myapp,myrabbit" + spec: + containers: + - name: myapp + image: docker.io/adib070/mvc + - name: myrabbit + image: rabbitmq diff --git a/tests/e2e/instrumentation-dotnet-multicontainer/02-assert.yaml b/tests/e2e/instrumentation-dotnet-multicontainer/02-assert.yaml new file mode 100644 index 0000000000..8cb31810bb --- /dev/null +++ b/tests/e2e/instrumentation-dotnet-multicontainer/02-assert.yaml @@ -0,0 +1,52 @@ +apiVersion: v1 +kind: Pod +metadata: + annotations: + sidecar.opentelemetry.io/inject: "true" + instrumentation.opentelemetry.io/container-names: myrabbit + instrumentation.opentelemetry.io/inject-dotnet: "true" + labels: + app: my-pod-with-sidecar +spec: + containers: + - name: myapp + volumeMounts: + - mountPath: /var/run/secrets/kubernetes.io/serviceaccount + - name: myrabbit + env: + - name: OTEL_LOG_LEVEL + value: "debug" + - name: OTEL_DOTNET_AUTO_TRACES_ENABLED_INSTRUMENTATIONS + value: AspNet,HttpClient,SqlClient + - name: DOTNET_STARTUP_HOOKS + value: "/otel-auto-instrumentation/netcoreapp3.1/OpenTelemetry.AutoInstrumentation.StartupHook.dll" + - name: DOTNET_ADDITIONAL_DEPS + value: ./otel-auto-instrumentation/AdditionalDeps + - name: DOTNET_SHARED_STORE + value: /otel-auto-instrumentation/store + - name: OTEL_TRACES_EXPORTER + value: otlp + - name: OTEL_EXPORTER_OTLP_ENDPOINT + value: http://localhost:4317 + - name: OTEL_EXPORTER_OTLP_TIMEOUT + value: "20" + - name: OTEL_TRACES_SAMPLER + value: parentbased_traceidratio + - name: OTEL_TRACES_SAMPLER_ARG + value: "0.85" + - name: SPLUNK_TRACE_RESPONSE_HEADER_ENABLED + value: "true" + - name: OTEL_SERVICE_NAME + value: my-deployment-with-sidecar + - name: OTEL_RESOURCE_ATTRIBUTES_POD_NAME + - name: OTEL_RESOURCE_ATTRIBUTES_NODE_NAME + - name: OTEL_PROPAGATORS + value: jaeger,b3 + - name: OTEL_RESOURCE_ATTRIBUTES + volumeMounts: + - mountPath: /var/run/secrets/kubernetes.io/serviceaccount + - mountPath: /otel-auto-instrumentation + name: opentelemetry-auto-instrumentation + - name: otc-container +status: + phase: Running \ No newline at end of file diff --git a/tests/e2e/instrumentation-dotnet-multicontainer/02-install-app.yaml b/tests/e2e/instrumentation-dotnet-multicontainer/02-install-app.yaml new file mode 100644 index 0000000000..137023368c --- /dev/null +++ b/tests/e2e/instrumentation-dotnet-multicontainer/02-install-app.yaml @@ -0,0 +1,23 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: my-deployment-with-sidecar +spec: + selector: + matchLabels: + app: my-pod-with-sidecar + replicas: 1 + template: + metadata: + labels: + app: my-pod-with-sidecar + annotations: + sidecar.opentelemetry.io/inject: "true" + instrumentation.opentelemetry.io/inject-dotnet: "true" + instrumentation.opentelemetry.io/container-names: "myrabbit" + spec: + containers: + - name: myapp + image: docker.io/adib070/mvc + - name: myrabbit + image: rabbitmq diff --git a/tests/e2e/instrumentation-dotnet/00-install-collector.yaml b/tests/e2e/instrumentation-dotnet/00-install-collector.yaml new file mode 100644 index 0000000000..f8e1e98e07 --- /dev/null +++ b/tests/e2e/instrumentation-dotnet/00-install-collector.yaml @@ -0,0 +1,23 @@ +apiVersion: opentelemetry.io/v1alpha1 +kind: OpenTelemetryCollector +metadata: + name: sidecar +spec: + mode: sidecar + config: | + receivers: + otlp: + protocols: + grpc: + http: + processors: + + exporters: + logging: + + service: + pipelines: + traces: + receivers: [otlp] + processors: [] + exporters: [logging] diff --git a/tests/e2e/instrumentation-dotnet/00-install-instrumentation.yaml b/tests/e2e/instrumentation-dotnet/00-install-instrumentation.yaml new file mode 100644 index 0000000000..affbe29822 --- /dev/null +++ b/tests/e2e/instrumentation-dotnet/00-install-instrumentation.yaml @@ -0,0 +1,30 @@ +apiVersion: opentelemetry.io/v1alpha1 +kind: Instrumentation +metadata: + name: dotnet +spec: + env: + - name: OTEL_TRACES_EXPORTER + value: otlp + - name: OTEL_EXPORTER_OTLP_ENDPOINT + value: http://localhost:4317 + - name: OTEL_EXPORTER_OTLP_TIMEOUT + value: "20" + - name: OTEL_TRACES_SAMPLER + value: parentbased_traceidratio + - name: OTEL_TRACES_SAMPLER_ARG + value: "0.85" + - name: SPLUNK_TRACE_RESPONSE_HEADER_ENABLED + value: "true" + exporter: + endpoint: http://localhost:4317 + propagators: + - jaeger + - b3 + sampler: + type: parentbased_traceidratio + argument: "0.25" + dotnet: + env: + - name: OTEL_DOTNET_AUTO_TRACES_ENABLED_INSTRUMENTATIONS + value: AspNet,HttpClient diff --git a/tests/e2e/instrumentation-dotnet/01-assert.yaml b/tests/e2e/instrumentation-dotnet/01-assert.yaml new file mode 100644 index 0000000000..9b5320b07f --- /dev/null +++ b/tests/e2e/instrumentation-dotnet/01-assert.yaml @@ -0,0 +1,48 @@ +apiVersion: v1 +kind: Pod +metadata: + annotations: + sidecar.opentelemetry.io/inject: "true" + instrumentation.opentelemetry.io/inject-dotnet: "true" + labels: + app: my-pod-with-sidecar +spec: + containers: + - name: myapp + env: + - name: OTEL_DOTNET_AUTO_TRACES_ENABLED_INSTRUMENTATIONS + value: AspNet,HttpClient + - name: DOTNET_STARTUP_HOOKS + value: "/otel-auto-instrumentation/netcoreapp3.1/OpenTelemetry.AutoInstrumentation.StartupHook.dll" + - name: DOTNET_ADDITIONAL_DEPS + value: ./otel-auto-instrumentation/AdditionalDeps + - name: DOTNET_SHARED_STORE + value: /otel-auto-instrumentation/store + - name: OTEL_TRACES_EXPORTER + value: otlp + - name: OTEL_EXPORTER_OTLP_ENDPOINT + value: http://localhost:4317 + - name: OTEL_EXPORTER_OTLP_TIMEOUT + value: "20" + - name: OTEL_TRACES_SAMPLER + value: parentbased_traceidratio + - name: OTEL_TRACES_SAMPLER_ARG + value: "0.85" + - name: SPLUNK_TRACE_RESPONSE_HEADER_ENABLED + value: "true" + - name: OTEL_SERVICE_NAME + value: my-deployment-with-sidecar + - name: OTEL_RESOURCE_ATTRIBUTES_POD_NAME + - name: OTEL_RESOURCE_ATTRIBUTES_NODE_NAME + - name: OTEL_PROPAGATORS + value: jaeger,b3 + - name: OTEL_RESOURCE_ATTRIBUTES + volumeMounts: + - mountPath: /var/run/secrets/kubernetes.io/serviceaccount + - mountPath: /otel-auto-instrumentation + name: opentelemetry-auto-instrumentation + - name: otc-container + initContainers: + - name: opentelemetry-auto-instrumentation +status: + phase: Running diff --git a/tests/e2e/instrumentation-dotnet/01-install-app.yaml b/tests/e2e/instrumentation-dotnet/01-install-app.yaml new file mode 100644 index 0000000000..15c652bac4 --- /dev/null +++ b/tests/e2e/instrumentation-dotnet/01-install-app.yaml @@ -0,0 +1,24 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: my-deployment-with-sidecar +spec: + selector: + matchLabels: + app: my-pod-with-sidecar + replicas: 1 + template: + metadata: + labels: + app: my-pod-with-sidecar + annotations: + sidecar.opentelemetry.io/inject: "true" + instrumentation.opentelemetry.io/inject-dotnet: "true" + spec: + securityContext: + runAsUser: 1000 + runAsGroup: 3000 + fsGroup: 2000 + containers: + - name: myapp + image: docker.io/adib070/mvc diff --git a/versions.txt b/versions.txt index b9c1073538..2c8b4553ba 100644 --- a/versions.txt +++ b/versions.txt @@ -21,3 +21,7 @@ autoinstrumentation-nodejs=0.27.0 # Represents the current release of Python instrumentation. # Should match value in autoinstrumentation/python/requirements.txt autoinstrumentation-python=0.30b1 + +# Represents the current release of DotNet instrumentation. +# Should match autoinstrumentation/dotnet/version.txt +autoinstrumentation-dotnet=0.2.0-beta.1 \ No newline at end of file