diff --git a/.chloggen/feat_allow-hostpid-support-collector.yaml b/.chloggen/feat_allow-hostpid-support-collector.yaml new file mode 100644 index 0000000000..d9c254d718 --- /dev/null +++ b/.chloggen/feat_allow-hostpid-support-collector.yaml @@ -0,0 +1,18 @@ +# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix' +change_type: enhancement + +# The name of the component, or a single word describing the area of concern, (e.g. collector, target allocator, auto-instrumentation, opamp, github action) +component: collector + +# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). +note: adds a feature to allow setting the `spec.hostPID` field for the collector. + +# One or more tracking issues related to the change +issues: [4214] + +# (Optional) One or more lines of additional information to render under the primary note. +# These lines will be padded with 2 spaces and then inserted directly into the document. +# Use pipe (|) for multiline entries. +subtext: + The allowing of the operator hostPID support can be enabled with `--feature-gates=+operator.security.hostpid`. + The feature gate is disabled by default and it will be enabled in the future releases. diff --git a/apis/v1alpha1/opentelemetrycollector_types.go b/apis/v1alpha1/opentelemetrycollector_types.go index 53ac68c3a0..42a42ca430 100644 --- a/apis/v1alpha1/opentelemetrycollector_types.go +++ b/apis/v1alpha1/opentelemetrycollector_types.go @@ -205,6 +205,9 @@ type OpenTelemetryCollectorSpec struct { // HostNetwork indicates if the pod should run in the host networking namespace. // +optional HostNetwork bool `json:"hostNetwork,omitempty"` + // HostPID indicates if the pod should have access to the host process ID namespace. + // +optional + HostPID bool `json:"hostPID,omitempty"` // ShareProcessNamespace indicates if the pod's containers should share process namespace. // +optional ShareProcessNamespace bool `json:"shareProcessNamespace,omitempty"` diff --git a/apis/v1beta1/opentelemetrycollector_types.go b/apis/v1beta1/opentelemetrycollector_types.go index 03fb2d0e96..d0eb860f82 100644 --- a/apis/v1beta1/opentelemetrycollector_types.go +++ b/apis/v1beta1/opentelemetrycollector_types.go @@ -138,6 +138,13 @@ type OpenTelemetryCollectorSpec struct { // This is only applicable to Deployment mode. // +optional DeploymentUpdateStrategy appsv1.DeploymentStrategy `json:"deploymentUpdateStrategy,omitempty"` + // ServiceName is the name of the Service to be used. + // If not specified, it will default to "-headless". + // +optional + ServiceName string `json:"serviceName,omitempty"` + // HostPID indicates if the pod should have access to the host process ID namespace. + // +optional + HostPID bool `json:"hostPID,omitempty"` } // TargetAllocatorEmbedded defines the configuration for the Prometheus target allocator, embedded in the diff --git a/bundle/community/manifests/opentelemetry-operator.clusterserviceversion.yaml b/bundle/community/manifests/opentelemetry-operator.clusterserviceversion.yaml index 61f1e64ca8..b4821e3905 100644 --- a/bundle/community/manifests/opentelemetry-operator.clusterserviceversion.yaml +++ b/bundle/community/manifests/opentelemetry-operator.clusterserviceversion.yaml @@ -99,7 +99,7 @@ metadata: categories: Logging & Tracing,Monitoring certified: "false" containerImage: ghcr.io/open-telemetry/opentelemetry-operator/opentelemetry-operator - createdAt: "2025-09-29T13:00:18Z" + createdAt: "2025-10-24T13:19:55Z" description: Provides the OpenTelemetry components, including the Collector operators.operatorframework.io/builder: operator-sdk-v1.29.0 operators.operatorframework.io/project_layout: go.kubebuilder.io/v3 diff --git a/bundle/community/manifests/opentelemetry.io_opentelemetrycollectors.yaml b/bundle/community/manifests/opentelemetry.io_opentelemetrycollectors.yaml index 41080ab6fc..a416a186b0 100644 --- a/bundle/community/manifests/opentelemetry.io_opentelemetrycollectors.yaml +++ b/bundle/community/manifests/opentelemetry.io_opentelemetrycollectors.yaml @@ -1456,6 +1456,8 @@ spec: type: array hostNetwork: type: boolean + hostPID: + type: boolean image: type: string imagePullPolicy: @@ -6067,6 +6069,8 @@ spec: type: array hostNetwork: type: boolean + hostPID: + type: boolean image: type: string imagePullPolicy: diff --git a/bundle/openshift/manifests/opentelemetry-operator.clusterserviceversion.yaml b/bundle/openshift/manifests/opentelemetry-operator.clusterserviceversion.yaml index 86f06e4b36..cd0924ee04 100644 --- a/bundle/openshift/manifests/opentelemetry-operator.clusterserviceversion.yaml +++ b/bundle/openshift/manifests/opentelemetry-operator.clusterserviceversion.yaml @@ -99,7 +99,7 @@ metadata: categories: Logging & Tracing,Monitoring certified: "false" containerImage: ghcr.io/open-telemetry/opentelemetry-operator/opentelemetry-operator - createdAt: "2025-09-29T13:00:19Z" + createdAt: "2025-10-24T13:19:55Z" description: Provides the OpenTelemetry components, including the Collector operators.operatorframework.io/builder: operator-sdk-v1.29.0 operators.operatorframework.io/project_layout: go.kubebuilder.io/v3 diff --git a/bundle/openshift/manifests/opentelemetry.io_opentelemetrycollectors.yaml b/bundle/openshift/manifests/opentelemetry.io_opentelemetrycollectors.yaml index 6c0da5d8ec..e96e88881a 100644 --- a/bundle/openshift/manifests/opentelemetry.io_opentelemetrycollectors.yaml +++ b/bundle/openshift/manifests/opentelemetry.io_opentelemetrycollectors.yaml @@ -1455,6 +1455,8 @@ spec: type: array hostNetwork: type: boolean + hostPID: + type: boolean image: type: string imagePullPolicy: @@ -6066,6 +6068,8 @@ spec: type: array hostNetwork: type: boolean + hostPID: + type: boolean image: type: string imagePullPolicy: diff --git a/config/crd/bases/opentelemetry.io_opentelemetrycollectors.yaml b/config/crd/bases/opentelemetry.io_opentelemetrycollectors.yaml index 69a4db2bd6..bcbb700e0b 100644 --- a/config/crd/bases/opentelemetry.io_opentelemetrycollectors.yaml +++ b/config/crd/bases/opentelemetry.io_opentelemetrycollectors.yaml @@ -1442,6 +1442,8 @@ spec: type: array hostNetwork: type: boolean + hostPID: + type: boolean image: type: string imagePullPolicy: @@ -6053,6 +6055,8 @@ spec: type: array hostNetwork: type: boolean + hostPID: + type: boolean image: type: string imagePullPolicy: diff --git a/config/manager/kustomization.yaml b/config/manager/kustomization.yaml index 5c5f0b84cb..730a38e808 100644 --- a/config/manager/kustomization.yaml +++ b/config/manager/kustomization.yaml @@ -1,2 +1,8 @@ resources: - manager.yaml +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +images: +- name: controller + newName: ghcr.io/open-telemetry/opentelemetry-operator/opentelemetry-operator + newTag: 0.136.0-9-g247d92bf diff --git a/docs/api/opentelemetrycollectors.md b/docs/api/opentelemetrycollectors.md index 9ea17fe96e..ebe4caea5f 100644 --- a/docs/api/opentelemetrycollectors.md +++ b/docs/api/opentelemetrycollectors.md @@ -183,6 +183,13 @@ These can then in certain cases be consumed in the config file for the Collector HostNetwork indicates if the pod should run in the host networking namespace.
false + + hostPID + boolean + + HostPID indicates if the pod should have access to the host process ID namespace.
+ + false image string @@ -19552,6 +19559,13 @@ This is only applicable to Deployment mode.
HostNetwork indicates if the pod should run in the host networking namespace.
false + + hostPID + boolean + + HostPID indicates if the pod should have access to the host process ID namespace.
+ + false image string diff --git a/internal/manifests/collector/daemonset.go b/internal/manifests/collector/daemonset.go index 1ccfcebfd7..e6b2f304fa 100644 --- a/internal/manifests/collector/daemonset.go +++ b/internal/manifests/collector/daemonset.go @@ -52,6 +52,7 @@ func DaemonSet(params manifests.Params) (*appsv1.DaemonSet, error) { Tolerations: params.OtelCol.Spec.Tolerations, NodeSelector: params.OtelCol.Spec.NodeSelector, HostNetwork: params.OtelCol.Spec.HostNetwork, + HostPID: params.OtelCol.Spec.HostPID, ShareProcessNamespace: ¶ms.OtelCol.Spec.ShareProcessNamespace, DNSPolicy: manifestutils.GetDNSPolicy(params.OtelCol.Spec.HostNetwork, params.OtelCol.Spec.PodDNSConfig), DNSConfig: ¶ms.OtelCol.Spec.PodDNSConfig, diff --git a/internal/manifests/collector/daemonset_test.go b/internal/manifests/collector/daemonset_test.go index c49e0023bc..40377dafc4 100644 --- a/internal/manifests/collector/daemonset_test.go +++ b/internal/manifests/collector/daemonset_test.go @@ -660,3 +660,48 @@ func TestDaemonSetTerminationGracePeriodSeconds(t *testing.T) { assert.NotNil(t, d2.Spec.Template.Spec.TerminationGracePeriodSeconds) assert.Equal(t, gracePeriodSec, *d2.Spec.Template.Spec.TerminationGracePeriodSeconds) } + +func TestDaemonSetHostPIDCanBeSet(t *testing.T) { + + // Test the case where hostPID is not set, should default to false + otelcol1 := v1beta1.OpenTelemetryCollector{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-instance", + }, + } + + cfg := config.New() + + params1 := manifests.Params{ + Config: cfg, + OtelCol: otelcol1, + Log: testLogger, + } + + d1, err := DaemonSet(params1) + require.NoError(t, err) + assert.False(t, d1.Spec.Template.Spec.HostPID) + + // Test the case where hostPID is set to true + otelcol2 := v1beta1.OpenTelemetryCollector{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-instance-terminationGracePeriodSeconds", + }, + Spec: v1beta1.OpenTelemetryCollectorSpec{ + HostPID: true, + }, + } + + cfg = config.New() + + params2 := manifests.Params{ + Config: cfg, + OtelCol: otelcol2, + Log: testLogger, + } + + d2, err := DaemonSet(params2) + require.NoError(t, err) + assert.NotNil(t, d2.Spec.Template.Spec.HostPID) + assert.True(t, d2.Spec.Template.Spec.HostPID) +} diff --git a/internal/manifests/collector/deployment.go b/internal/manifests/collector/deployment.go index 4028fac041..8753592132 100644 --- a/internal/manifests/collector/deployment.go +++ b/internal/manifests/collector/deployment.go @@ -53,6 +53,7 @@ func Deployment(params manifests.Params) (*appsv1.Deployment, error) { DNSPolicy: manifestutils.GetDNSPolicy(params.OtelCol.Spec.HostNetwork, params.OtelCol.Spec.PodDNSConfig), DNSConfig: ¶ms.OtelCol.Spec.PodDNSConfig, HostNetwork: params.OtelCol.Spec.HostNetwork, + HostPID: params.OtelCol.Spec.HostPID, ShareProcessNamespace: ¶ms.OtelCol.Spec.ShareProcessNamespace, Tolerations: params.OtelCol.Spec.Tolerations, NodeSelector: params.OtelCol.Spec.NodeSelector, diff --git a/internal/manifests/collector/deployment_test.go b/internal/manifests/collector/deployment_test.go index 1f617dafbf..97431b7141 100644 --- a/internal/manifests/collector/deployment_test.go +++ b/internal/manifests/collector/deployment_test.go @@ -808,3 +808,48 @@ func TestGetInitialReplicas(t *testing.T) { func int32Ptr(i int32) *int32 { return &i } + +func TestDeploymentHostPIDCanBeSet(t *testing.T) { + + // Test default + otelcol1 := v1beta1.OpenTelemetryCollector{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-instance", + }, + } + + cfg := config.New() + + params1 := manifests.Params{ + Config: cfg, + OtelCol: otelcol1, + Log: testLogger, + } + + d1, err := Deployment(params1) + require.NoError(t, err) + + assert.False(t, d1.Spec.Template.Spec.HostPID) + + // Test hostPID=true + otelcol2 := v1beta1.OpenTelemetryCollector{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-instance-hostnetwork", + }, + Spec: v1beta1.OpenTelemetryCollectorSpec{ + HostPID: true, + }, + } + + cfg = config.New() + + params2 := manifests.Params{ + Config: cfg, + OtelCol: otelcol2, + Log: testLogger, + } + + d2, err := Deployment(params2) + require.NoError(t, err) + assert.True(t, d2.Spec.Template.Spec.HostPID) +} diff --git a/internal/manifests/collector/statefulset.go b/internal/manifests/collector/statefulset.go index 72b34c006d..0f146584b6 100644 --- a/internal/manifests/collector/statefulset.go +++ b/internal/manifests/collector/statefulset.go @@ -58,6 +58,7 @@ func StatefulSet(params manifests.Params) (*appsv1.StatefulSet, error) { DNSPolicy: manifestutils.GetDNSPolicy(params.OtelCol.Spec.HostNetwork, params.OtelCol.Spec.PodDNSConfig), DNSConfig: ¶ms.OtelCol.Spec.PodDNSConfig, HostNetwork: params.OtelCol.Spec.HostNetwork, + HostPID: params.OtelCol.Spec.HostPID, ShareProcessNamespace: ¶ms.OtelCol.Spec.ShareProcessNamespace, Tolerations: params.OtelCol.Spec.Tolerations, NodeSelector: params.OtelCol.Spec.NodeSelector, diff --git a/internal/manifests/collector/statefulset_test.go b/internal/manifests/collector/statefulset_test.go index 9b7fc6eaa9..7c411192f4 100644 --- a/internal/manifests/collector/statefulset_test.go +++ b/internal/manifests/collector/statefulset_test.go @@ -828,3 +828,48 @@ func TestStatefulSetServiceName(t *testing.T) { }) } } + +func TestStatefulSetHostPIDCanBeSet(t *testing.T) { + + // Test default + otelcol1 := v1beta1.OpenTelemetryCollector{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-instance", + }, + } + + cfg := config.New() + + params1 := manifests.Params{ + OtelCol: otelcol1, + Config: cfg, + Log: testLogger, + } + + d1, err := StatefulSet(params1) + require.NoError(t, err) + + assert.False(t, d1.Spec.Template.Spec.HostPID) + + // Test HostPID=true + otelcol2 := v1beta1.OpenTelemetryCollector{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-instance-HostPID", + }, + Spec: v1beta1.OpenTelemetryCollectorSpec{ + HostPID: true, + }, + } + + cfg = config.New() + + params2 := manifests.Params{ + OtelCol: otelcol2, + Config: cfg, + Log: testLogger, + } + + d2, err := StatefulSet(params2) + require.NoError(t, err) + assert.True(t, d2.Spec.Template.Spec.HostPID) +} diff --git a/tests/e2e/smoke-collector-hostpid/00-assert-daemonset.yaml b/tests/e2e/smoke-collector-hostpid/00-assert-daemonset.yaml new file mode 100644 index 0000000000..f9649dbef9 --- /dev/null +++ b/tests/e2e/smoke-collector-hostpid/00-assert-daemonset.yaml @@ -0,0 +1,40 @@ +apiVersion: apps/v1 +kind: DaemonSet +metadata: + name: daemonset-collector +spec: + updateStrategy: + rollingUpdate: + maxSurge: 0 + maxUnavailable: 1 + type: RollingUpdate +status: + numberMisscheduled: 0 + (desiredNumberScheduled == numberReady): true + +--- +apiVersion: v1 +kind: Pod +metadata: + labels: + app.kubernetes.io/component: opentelemetry-collector + app.kubernetes.io/managed-by: opentelemetry-operator + app.kubernetes.io/name: daemonset-collector +status: + containerStatuses: + - ready: true + started: true + phase: Running +spec: + hostPID: true +--- +apiVersion: opentelemetry.io/v1beta1 +kind: OpenTelemetryCollector +metadata: + name: daemonset +status: + (starts_with(image, 'ghcr.io/open-telemetry/opentelemetry-collector-releases/opentelemetry-collector')): true + scale: + (replicas >= `1`): true + (statusReplicas != ''): true + (version != ''): true diff --git a/tests/e2e/smoke-collector-hostpid/00-install-daemonset.yaml b/tests/e2e/smoke-collector-hostpid/00-install-daemonset.yaml new file mode 100644 index 0000000000..f90c30800b --- /dev/null +++ b/tests/e2e/smoke-collector-hostpid/00-install-daemonset.yaml @@ -0,0 +1,24 @@ +apiVersion: opentelemetry.io/v1alpha1 +kind: OpenTelemetryCollector +metadata: + name: daemonset +spec: + hostPID: true + mode: daemonset + updateStrategy: + type: RollingUpdate + rollingUpdate: + maxUnavailable: 1 + config: | + receivers: + jaeger: + protocols: + grpc: + processors: + exporters: + debug: + service: + pipelines: + traces: + receivers: [jaeger] + exporters: [debug] diff --git a/tests/e2e/smoke-collector-hostpid/01-assert-deployment.yaml b/tests/e2e/smoke-collector-hostpid/01-assert-deployment.yaml new file mode 100644 index 0000000000..7d8febeeca --- /dev/null +++ b/tests/e2e/smoke-collector-hostpid/01-assert-deployment.yaml @@ -0,0 +1,30 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: deployment-collector +--- +apiVersion: v1 +kind: Pod +metadata: + labels: + app.kubernetes.io/component: opentelemetry-collector + app.kubernetes.io/managed-by: opentelemetry-operator + app.kubernetes.io/name: deployment-collector +status: + containerStatuses: + - ready: true + started: true + phase: Running +spec: + hostPID: true +--- +apiVersion: opentelemetry.io/v1beta1 +kind: OpenTelemetryCollector +metadata: + name: deployment +status: + (starts_with(image, 'ghcr.io/open-telemetry/opentelemetry-collector-releases/opentelemetry-collector')): true + scale: + (replicas >= `1`): true + (statusReplicas != ''): true + (version != ''): true diff --git a/tests/e2e/smoke-collector-hostpid/01-install-deployment.yaml b/tests/e2e/smoke-collector-hostpid/01-install-deployment.yaml new file mode 100644 index 0000000000..14ad40eebb --- /dev/null +++ b/tests/e2e/smoke-collector-hostpid/01-install-deployment.yaml @@ -0,0 +1,20 @@ +apiVersion: opentelemetry.io/v1alpha1 +kind: OpenTelemetryCollector +metadata: + name: deployment +spec: + hostPID: true + mode: deployment + config: | + receivers: + jaeger: + protocols: + grpc: + processors: + exporters: + debug: + service: + pipelines: + traces: + receivers: [jaeger] + exporters: [debug] diff --git a/tests/e2e/smoke-collector-hostpid/02-assert-statefulset.yaml b/tests/e2e/smoke-collector-hostpid/02-assert-statefulset.yaml new file mode 100644 index 0000000000..7fb5d0c1c3 --- /dev/null +++ b/tests/e2e/smoke-collector-hostpid/02-assert-statefulset.yaml @@ -0,0 +1,56 @@ +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: stateful-collector +spec: + serviceName: stateful-collector-headless +status: + replicas: 1 + readyReplicas: 1 +--- +apiVersion: v1 +kind: Service +metadata: + name: stateful-collector-headless + labels: + app.kubernetes.io/component: opentelemetry-collector + app.kubernetes.io/managed-by: opentelemetry-operator + app.kubernetes.io/part-of: opentelemetry + operator.opentelemetry.io/collector-headless-service: "Exists" +spec: + clusterIP: None + selector: + app.kubernetes.io/component: opentelemetry-collector + app.kubernetes.io/managed-by: opentelemetry-operator + app.kubernetes.io/part-of: opentelemetry + ports: + - name: jaeger-grpc + port: 14250 + protocol: TCP + targetPort: 14250 +--- +apiVersion: v1 +kind: Pod +metadata: + labels: + app.kubernetes.io/component: opentelemetry-collector + app.kubernetes.io/managed-by: opentelemetry-operator + app.kubernetes.io/name: stateful-collector +status: + containerStatuses: + - ready: true + started: true + phase: Running +spec: + hostPID: true +--- +apiVersion: opentelemetry.io/v1beta1 +kind: OpenTelemetryCollector +metadata: + name: stateful +status: + (starts_with(image, 'ghcr.io/open-telemetry/opentelemetry-collector-releases/opentelemetry-collector')): true + (version != ''): true + scale: + replicas: 1 + statusReplicas: "1/1" diff --git a/tests/e2e/smoke-collector-hostpid/02-install-statefulset.yaml b/tests/e2e/smoke-collector-hostpid/02-install-statefulset.yaml new file mode 100644 index 0000000000..6752a20521 --- /dev/null +++ b/tests/e2e/smoke-collector-hostpid/02-install-statefulset.yaml @@ -0,0 +1,19 @@ +apiVersion: opentelemetry.io/v1beta1 +kind: OpenTelemetryCollector +metadata: + name: stateful +spec: + hostPID: true + mode: statefulset + config: + receivers: + jaeger: + protocols: + grpc: + exporters: + debug: + service: + pipelines: + traces: + receivers: [jaeger] + exporters: [debug] diff --git a/tests/e2e/smoke-collector-hostpid/chainsaw-test.yaml b/tests/e2e/smoke-collector-hostpid/chainsaw-test.yaml new file mode 100644 index 0000000000..0886165eb3 --- /dev/null +++ b/tests/e2e/smoke-collector-hostpid/chainsaw-test.yaml @@ -0,0 +1,58 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/kyverno/chainsaw/main/.schemas/json/test-chainsaw-v1alpha1.json +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + creationTimestamp: null + name: smoke-collector-hostpid +spec: + steps: + - name: Enable operator hostPID + try: + - command: + entrypoint: kubectl + args: + - get + - pods + - -A + - -l control-plane=controller-manager + - -l app.kubernetes.io/name=opentelemetry-operator + - -o + - jsonpath={.items[0].metadata.namespace} + outputs: + - name: OTEL_NAMESPACE + value: ($stdout) + - command: + env: + - name: otelnamespace + value: ($OTEL_NAMESPACE) + entrypoint: kubectl + args: + - patch + - deployment + - opentelemetry-operator-controller-manager + - -n + - $otelnamespace + - --type=json + - -p + - '[{"op": "add", "path": "/spec/template/spec/containers/0/args/-", "value": "--feature-gates=+operator.security.hostpid"}]' + # Adding 30s sleep here cause statefulset seems to not like when it is lower than 20s + - sleep: + duration: 30s + - name: step-00-daemonset + try: + - apply: + file: 00-install-daemonset.yaml + - assert: + file: 00-assert-daemonset.yaml + - name: step-01-deployment + try: + - apply: + file: 01-install-deployment.yaml + - assert: + file: 01-assert-deployment.yaml + - name: step-02-statefulset + try: + - apply: + file: 02-install-statefulset.yaml + - assert: + file: 02-assert-statefulset.yaml