주말에 집에서 여러가지 테스트를 하고 있는데요. 아래의 문제가 있어서요. 전문가들의 의견과 답변을 주시면 너무 감사합니다.
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
힌트/팁/의견 등 뭐가 됐든 전문가분들의 고견을 부탁드립니다.