K8sattributes가 ebpf-profiler와 함께 동작하지 않음?

주말에 집에서 여러가지 테스트를 하고 있는데요. 아래의 문제가 있어서요. 전문가들의 의견과 답변을 주시면 너무 감사합니다.

ebpf-profiler를 실험해보던 중, 로컬 Docker 환경에서 Kubernetes로 옮겼을 때 아래와 같이 정상 작동이 안되는거 같아요.

  • Kubernetes에서 에이전트를 DaemonSet으로 실행하면,
    • container.id가 항상 비어 있습니다.
    • 프로파일에 여러 개의 바이너리/함수가 섞여 나타나는 경우가 있습니다. (예: 트레이스에 python, ruby, docker-compose, 심지어 host daemon까지 같은 프로파일 안에 함께 표시됨)
    • 프로파일러가 Pod/컨테이너에 정확히 할당하지 않고 호스트 전체 프로세스를 샘플링하는 것처럼 보입니다.

예시
(아래는 샘플 속성을 포함한 샘플 프로파일 예시입니다)

 "value": ""
        "value": "10.244.0.1"
            "value": "irq/165-iwlwifi"
            "value": "1134"
            "value": "1134"
            "value": "ebpf-profiler"
            "value": "216228"
            "value": "216255"
            "value": "brave"
            "value": "183023"
            "value": "183023"
            "value": "ebpf-profiler"
            "value": "216228"
            "value": "216248"
            "value": "docker-compose"
            "value": "210490"
            "value": "210494"
            "value": "kube-apiserver"
            "value": "78493"
            "value": "78545"
            "value": "ebpf-profiler"
            "value": "216228"
            "value": "216251"
            "value": "ebpf-profiler"
            "value": "216228"
            "value": "216253"
            "value": "ebpf-profiler"
            "value": "216228"
            "value": "216255"
            "value": "slack"
            "value": "10945"
            "value": "14808"

여기서 첫 번째 빈 값은 container.id여야 합니다.

제 생각은 …

  • 로컬 Docker 환경에서는 프로파일러가 cgroups/runtime 상태에 직접 접근할 수 있으므로 → 컨테이너 ID 해석이 동작되어야하고,

  • Kubernetes에서는 에이전트가 /proc, /sys/fs/cgroup, /run/containerd, /var/lib/containerd 같은 호스트 경로를 마운트하지 않으면 프로파일러가 컨테이너 ID를 해석할 수 없습니다.

  • 현재로서는 “글로벌 호스트 모드”로 폴백되는 것처럼 보이며, 그래서 하나의 프로파일 안에 여러 바이너리가 보이는 것 같습니다.

  • containerd를 사용하는 Kubernetes에서 ebpf-profiler를 실행할 때 container.id와 Pod 단위 할당이 제대로 동작하도록 하기 위해 필요한 최소 마운트/플래그를 명확히 알려주실 수 있나요?

  • 루트 경로(/) 전체를 마운트해야 하나요, 아니면 /host/proc, /host/sys/fs/cgroup 등으로 프로파일러가 참조할 경로만 지정해도 해결될까요?

  • cri-containerd-.scope를 컨테이너 ID로 자동 해석하는 등 Kubernetes/containerd 통합을 더 매끄럽게 만들 계획이 있나요?

참고로, 여기에서 사용된 설정은 다음과 같습니다.

Collector 설정입니다.

---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: otel-collector
  namespace: profiling
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: otel-collector-k8sattributes
rules:
  - apiGroups: [""]
    resources: ["pods", "nodes", "namespaces"]
    verbs: ["get", "list", "watch"]
  - apiGroups: ["apps"]
    resources: ["replicasets", "deployments", "statefulsets", "daemonsets"]
    verbs: ["get", "list", "watch"]
  - apiGroups: [""]
    resources: ["events"]
    verbs: ["list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: otel-collector-k8sattributes
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: otel-collector-k8sattributes
subjects:
  - kind: ServiceAccount
    name: otel-collector
    namespace: profiling
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: otel-collector-config
  namespace: profiling
data:
  otel-collector-config.yaml: |
    receivers:
      otlp:
        protocols:
          grpc:
            endpoint: 0.0.0.0:4317
          http:
            endpoint: 0.0.0.0:4318

    processors:
      k8sattributes:
        auth_type: serviceAccount
        passthrough: false
        filter:
          node_from_env_var: KUBERNETES_NODE_NAME
        extract:
          metadata:
            - k8s.pod.name
            - k8s.pod.uid
            - k8s.deployment.name
            - k8s.namespace.name
            - k8s.node.name
            - k8s.pod.start_time
            - service.namespace
            - service.name
            - service.version
            - service.instance.id
          otel_annotations: true
        pod_association:
          - sources:
              - from: resource_attributes
                name: container.id

    exporters:
      debug:
        verbosity: detailed
      otlp/profiles:
        endpoint: ${env:PROFILES_EXPORTER_ENDPOINT}
        tls:
          insecure: true

    extensions:
      health_check:
        endpoint: 0.0.0.0:13133

    service:
      extensions: [health_check]
      pipelines:
        traces:
          receivers: [otlp]
          processors: [k8sattributes]
          exporters: [debug]
        metrics:
          receivers: [otlp]
          processors: [k8sattributes]
          exporters: [debug]
        logs:
          receivers: [otlp]
          processors: [k8sattributes]
          exporters: [debug]
        profiles:
          receivers: [otlp]
          processors: [k8sattributes]
          exporters: [otlp/profiles]

      telemetry:
        logs:
          level: debug
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: otel-collector
  namespace: profiling
spec:
  selector:
    matchLabels:
      app: otel-collector
  template:
    metadata:
      labels:
        app: otel-collector
    spec:
      serviceAccountName: otel-collector
      tolerations:
        - operator: "Exists"
      terminationGracePeriodSeconds: 30
      containers:
        - name: otel-collector
          image: otel/opentelemetry-collector-contrib:latest
          imagePullPolicy: IfNotPresent
          args:
            - "--config=/etc/otel/otel-collector-config.yaml"
            - "--feature-gates=service.profilesSupport"
          ports:
            - name: otlp-grpc
              containerPort: 4317
              hostPort: 4317
            - name: otlp-http
              containerPort: 4318
              hostPort: 4318
            - name: healthz
              containerPort: 13133
          env:
            - name: KUBERNETES_NODE_NAME
              valueFrom:
                fieldRef:
                  fieldPath: spec.nodeName
            - name: PROFILES_EXPORTER_ENDPOINT
              value: "backend.profiling.svc.cluster.local:50051"
          volumeMounts:
            - name: otel-config
              mountPath: /etc/otel
              readOnly: true
          livenessProbe:
            httpGet:
              path: /
              port: healthz
            initialDelaySeconds: 10
            periodSeconds: 20
            timeoutSeconds: 2
            failureThreshold: 3
          readinessProbe:
            httpGet:
              path: /
              port: healthz
            initialDelaySeconds: 5
            periodSeconds: 10
            timeoutSeconds: 2
            failureThreshold: 3
          resources:
            requests:
              cpu: "100m"
              memory: "128Mi"
            limits:
              cpu: "500m"
              memory: "512Mi"
      volumes:
        - name: config
          configMap:
            name: collector
        - name: otel-config
          configMap:
            name: otel-collector-config
            items:
              - key: otel-collector-config.yaml
                path: otel-collector-config.yaml
---
apiVersion: v1
kind: Service
metadata:
  name: otel-collector
  namespace: profiling
spec:
  selector:
    app: otel-collector
  ports:
    - name: otlp-grpc
      port: 4317
      targetPort: otlp-grpc
      protocol: TCP
    - name: otlp-http
      port: 4318
      targetPort: otlp-http
      protocol: TCP

프로파일러 Dockerfile 설정입니다.

FROM gcr.io/distroless/base-debian11

# Copy the profiler binary
COPY ./ebpf-profiler /otel-ebpf-profiler

ENTRYPOINT ["/otel-ebpf-profiler"]

docker 실행 명령어 결과입니다.

hanshal101@lol:~/opsrc/opentelemetry-ebpf-profiler$ docker run --privileged --pid=host --network=host   --security-opt apparmor=unconfined   --security-opt seccomp=unconfined   --cap-add=ALL   -v /sys/kernel/debug:/sys/kernel/debug:rw   --mount type=bind,source=/proc,target=/host-proc,readonly   otel-prof:latest   -collection-agent localhost:50051   -no-kernel-version-check   -disable-tls
time="2025-08-21T20:53:44Z" level=info msg="Starting OTEL profiling agent v0.0.0 (revision main-ec6ee459, build timestamp 1755794620)"
time="2025-08-21T20:53:44Z" level=info msg="Interpreter tracers: perl,php,python,hotspot,ruby,v8,dotnet,go,labels"
time="2025-08-21T20:53:45Z" level=info msg="Found offsets: task stack 0x20, pt_regs 0x3f48, tpbase 0x25a8"
time="2025-08-21T20:53:45Z" level=info msg="Supports generic eBPF map batch operations"
time="2025-08-21T20:53:45Z" level=info msg="Supports LPM trie eBPF map batch operations"
time="2025-08-21T20:53:45Z" level=info msg="eBPF tracer loaded"
time="2025-08-21T20:53:45Z" level=info msg="Attached tracer program"
time="2025-08-21T20:53:45Z" level=info msg="Attached sched monitor"

프로파일러 config 내용입니다.

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: otel-agent
  namespace: profiling
spec:
  selector:
    matchLabels:
      app: otel-agent
  template:
    metadata:
      labels:
        app: otel-agent
    spec:
      hostPID: true
      hostNetwork: true
      dnsPolicy: ClusterFirstWithHostNet
      containers:
        - name: otel
          image: otel-prof:latest
          imagePullPolicy: IfNotPresent
          securityContext:
            privileged: true
            capabilities:
              add:
                - ALL
          args:
            - "-collection-agent"
            - "otel-backend.profiling.svc.cluster.local:50051"
            - "-no-kernel-version-check"
            - "-disable-tls"
          volumeMounts:
            - name: debugfs
              mountPath: /sys/kernel/debug
              readOnly: false
            - name: cgroupfs
              mountPath: /sys/fs/cgroup
              mountPropagation: HostToContainer
              readOnly: true
            - name: procfs
              mountPath: /proc
              mountPropagation: HostToContainer
              readOnly: true
            - name: sys
              mountPath: /sys
              readOnly: true
            - name: modules
              mountPath: /lib/modules
              readOnly: true
          resources:
            requests:
              cpu: "200m"
              memory: "256Mi"
            limits:
              cpu: "1"
              memory: "512Mi"
      volumes:
        - name: sys
          hostPath:
            path: /sys
        - name: modules
          hostPath:
            path: /lib/modules
        - name: debugfs
          hostPath:
            path: /sys/kernel/debug
        - name: cgroupfs
          hostPath:
            path: /sys/fs/cgroup
        - name: procfs
          hostPath:
            path: /proc
      terminationGracePeriodSeconds: 30

힌트/팁/의견 등 뭐가 됐든 전문가분들의 고견을 부탁드립니다.

조만간 Bro분들이 의견, 댓글을 주실꺼에요. ^^;;

1 Like

@jerry 대표님! 감사합니다.