diff --git a/api/v1alpha1/spinappexecutor_types.go b/api/v1alpha1/spinappexecutor_types.go index 9ca9fa59..2953145a 100644 --- a/api/v1alpha1/spinappexecutor_types.go +++ b/api/v1alpha1/spinappexecutor_types.go @@ -48,6 +48,9 @@ type ExecutorDeploymentConfig struct { // will be created containing the certificates. If no secret name is // defined in `CACertSecret` the secret name will be `spin-ca`. InstallDefaultCACerts bool `json:"installDefaultCACerts,omitempty"` + + // Otel provides Kubernetes Bindings to Otel Variables. + Otel *OtelConfig `json:"otel,omitempty"` } // SpinAppExecutorStatus defines the observed state of SpinAppExecutor @@ -56,6 +59,18 @@ type SpinAppExecutorStatus struct { // Important: Run "make" to regenerate code after modifying this file } +// OtelConfig is the supported environment variables for OpenTelemetry +type OtelConfig struct { + // ExporterOtlpEndpoint configures the default combined otlp endpoint for sending telemetry + ExporterOtlpEndpoint string `json:"exporter_otlp_endpoint,omitempty"` + // ExporterOtlpTracesEndpoint configures the trace-specific otlp endpoint + ExporterOtlpTracesEndpoint string `json:"exporter_otlp_traces_endpoint,omitempty"` + // ExporterOtlpMetricsEndpoint configures the metrics-specific otlp endpoint + ExporterOtlpMetricsEndpoint string `json:"exporter_otlp_metrics_endpoint,omitempty"` + // ExporterOtlpLogsEndpoint configures the logs-specific otlp endpoint + ExporterOtlpLogsEndpoint string `json:"exporter_otlp_logs_endpoint,omitempty"` +} + //+kubebuilder:object:root=true //+kubebuilder:subresource:status diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 6c8833db..41201234 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -29,6 +29,11 @@ import ( // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ExecutorDeploymentConfig) DeepCopyInto(out *ExecutorDeploymentConfig) { *out = *in + if in.Otel != nil { + in, out := &in.Otel, &out.Otel + *out = new(OtelConfig) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExecutorDeploymentConfig. @@ -165,6 +170,21 @@ func (in *LLMComputeConfig) DeepCopy() *LLMComputeConfig { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OtelConfig) DeepCopyInto(out *OtelConfig) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OtelConfig. +func (in *OtelConfig) DeepCopy() *OtelConfig { + if in == nil { + return nil + } + out := new(OtelConfig) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Resources) DeepCopyInto(out *Resources) { *out = *in @@ -365,7 +385,7 @@ func (in *SpinAppExecutorSpec) DeepCopyInto(out *SpinAppExecutorSpec) { if in.DeploymentConfig != nil { in, out := &in.DeploymentConfig, &out.DeploymentConfig *out = new(ExecutorDeploymentConfig) - **out = **in + (*in).DeepCopyInto(*out) } } diff --git a/config/crd/bases/core.spinoperator.dev_spinappexecutors.yaml b/config/crd/bases/core.spinoperator.dev_spinappexecutors.yaml index 820d2269..c5451298 100644 --- a/config/crd/bases/core.spinoperator.dev_spinappexecutors.yaml +++ b/config/crd/bases/core.spinoperator.dev_spinappexecutors.yaml @@ -61,6 +61,26 @@ spec: will be created containing the certificates. If no secret name is defined in `CACertSecret` the secret name will be `spin-ca`. type: boolean + otel: + description: Otel provides Kubernetes Bindings to Otel Variables. + properties: + exporter_otlp_endpoint: + description: ExporterOtlpEndpoint configures the default combined + otlp endpoint for sending telemetry + type: string + exporter_otlp_logs_endpoint: + description: ExporterOtlpLogsEndpoint configures the logs-specific + otlp endpoint + type: string + exporter_otlp_metrics_endpoint: + description: ExporterOtlpMetricsEndpoint configures the metrics-specific + otlp endpoint + type: string + exporter_otlp_traces_endpoint: + description: ExporterOtlpTracesEndpoint configures the trace-specific + otlp endpoint + type: string + type: object runtimeClassName: description: |- RuntimeClassName is the runtime class name that should be used by pods created diff --git a/config/samples/otel.yaml b/config/samples/otel.yaml new file mode 100644 index 00000000..c2e513ff --- /dev/null +++ b/config/samples/otel.yaml @@ -0,0 +1,20 @@ +apiVersion: core.spinoperator.dev/v1alpha1 +kind: SpinApp +metadata: + name: otel-spinapp +spec: + image: ghcr.io/spinkube/spin-operator/cpu-load-gen:20240311-163328-g1121986 + executor: otel-shim-executor + replicas: 1 +--- +apiVersion: core.spinoperator.dev/v1alpha1 +kind: SpinAppExecutor +metadata: + name: otel-shim-executor +spec: + createDeployment: true + deploymentConfig: + runtimeClassName: wasmtime-spin-v2 + installDefaultCACerts: true + otel: + exporter_otlp_endpoint: http://otel-collector.default.svc.cluster.local:4318 diff --git a/internal/controller/deployment.go b/internal/controller/deployment.go index d8f2780b..7ea337b7 100644 --- a/internal/controller/deployment.go +++ b/internal/controller/deployment.go @@ -103,8 +103,9 @@ func ConstructVolumeMountsForApp(ctx context.Context, app *spinv1alpha1.SpinApp, // ConstructEnvForApp constructs the env for a spin app that runs as a k8s pod. // Variables are not guaranteed to stay backed by ENV. -func ConstructEnvForApp(ctx context.Context, app *spinv1alpha1.SpinApp, listenPort int) []corev1.EnvVar { +func ConstructEnvForApp(ctx context.Context, app *spinv1alpha1.SpinApp, listenPort int, otel *spinv1alpha1.OtelConfig) []corev1.EnvVar { envs := make([]corev1.EnvVar, len(app.Spec.Variables)) + // Adding the Spin Variables for idx, variable := range app.Spec.Variables { env := corev1.EnvVar{ // Spin Variables only allow lowercase ascii characters, `_`, and numbers. @@ -121,6 +122,24 @@ func ConstructEnvForApp(ctx context.Context, app *spinv1alpha1.SpinApp, listenPo Name: "SPIN_HTTP_LISTEN_ADDR", Value: fmt.Sprintf("0.0.0.0:%d", listenPort), }) + // Adding the OpenTelemetry params + if otel != nil { + if otel.ExporterOtlpEndpoint != "" { + envs = append(envs, corev1.EnvVar{Name: "OTEL_EXPORTER_OTLP_ENDPOINT", Value: otel.ExporterOtlpEndpoint}) + } + + if otel.ExporterOtlpTracesEndpoint != "" { + envs = append(envs, corev1.EnvVar{Name: "OTEL_EXPORTER_OTLP_TRACES_ENDPOINT", Value: otel.ExporterOtlpTracesEndpoint}) + } + + if otel.ExporterOtlpMetricsEndpoint != "" { + envs = append(envs, corev1.EnvVar{Name: "OTEL_EXPORTER_OTLP_METRICS_ENDPOINT", Value: otel.ExporterOtlpMetricsEndpoint}) + } + + if otel.ExporterOtlpLogsEndpoint != "" { + envs = append(envs, corev1.EnvVar{Name: "OTEL_EXPORTER_OTLP_LOGS_ENDPOINT", Value: otel.ExporterOtlpLogsEndpoint}) + } + } return envs } diff --git a/internal/controller/deployment_test.go b/internal/controller/deployment_test.go index 877e6fa7..9da781c0 100644 --- a/internal/controller/deployment_test.go +++ b/internal/controller/deployment_test.go @@ -94,6 +94,9 @@ func TestConstructEnvForApp(t *testing.T) { value string valueFrom *corev1.EnvVarSource + + expectedOtelVars map[string]string + otelVars spinv1alpha1.OtelConfig }{ { name: "simple_secret_with_static_value", @@ -129,6 +132,24 @@ func TestConstructEnvForApp(t *testing.T) { }, }, }, + { + name: "otel_vars_are_properly_set", + varName: "simple_secret", + expectedEnvName: "SPIN_VARIABLE_SIMPLE_SECRET", + value: "f00", + otelVars: spinv1alpha1.OtelConfig{ + ExporterOtlpEndpoint: "http://otlp", + ExporterOtlpTracesEndpoint: "http://traces", + ExporterOtlpMetricsEndpoint: "http://metrics", + ExporterOtlpLogsEndpoint: "http://logs", + }, + expectedOtelVars: map[string]string{ + "OTEL_EXPORTER_OTLP_ENDPOINT": "http://otlp", + "OTEL_EXPORTER_OTLP_TRACES_ENDPOINT": "http://traces", + "OTEL_EXPORTER_OTLP_METRICS_ENDPOINT": "http://metrics", + "OTEL_EXPORTER_OTLP_LOGS_ENDPOINT": "http://logs", + }, + }, } for _, test := range tests { @@ -142,11 +163,28 @@ func TestConstructEnvForApp(t *testing.T) { }, } - envs := ConstructEnvForApp(context.Background(), app, 0) + envs := ConstructEnvForApp(context.Background(), app, 0, &test.otelVars) require.Equal(t, test.expectedEnvName, envs[0].Name) require.Equal(t, test.value, envs[0].Value) require.Equal(t, test.valueFrom, envs[0].ValueFrom) + + for key, value := range test.expectedOtelVars { + varNotFound := true + for _, envVar := range envs { + if envVar.Name == key { + varNotFound = false + if envVar.Value != value { + require.Equal(t, test.value, envVar.Value) + break + } + } + } + + if varNotFound { + require.NotContains(t, test.expectedOtelVars, key) + } + } }) } } diff --git a/internal/controller/spinapp_controller.go b/internal/controller/spinapp_controller.go index 1b3b9396..1fb49deb 100644 --- a/internal/controller/spinapp_controller.go +++ b/internal/controller/spinapp_controller.go @@ -423,7 +423,7 @@ func constructDeployment(ctx context.Context, app *spinv1alpha1.SpinApp, config Requests: app.Spec.Resources.Requests, } - env := ConstructEnvForApp(ctx, app, spinapp.DefaultHTTPPort) + env := ConstructEnvForApp(ctx, app, spinapp.DefaultHTTPPort, config.Otel) readinessProbe, livenessProbe, err := ConstructPodHealthChecks(app) if err != nil {