| name | ebpf-observability |
| description | Use eBPF for deep kernel-level observability — trace syscalls, network flows, and application behavior without code changes using Cilium, Tetragon, and bpftrace. |
| license | MIT |
| metadata | {"author":"devops-skills","version":"1.0"} |
eBPF Observability
eBPF (extended Berkeley Packet Filter) allows you to run sandboxed programs in the Linux kernel without modifying kernel source code or loading kernel modules. This skill covers using eBPF for deep observability, network monitoring, and security enforcement across cloud-native infrastructure.
1. When to Use
Use eBPF-based observability when you need:
- Deep performance debugging -- trace kernel-level latency, syscall overhead, and scheduling delays that application-level metrics cannot reveal.
- Network observability without sidecars -- capture L3/L4/L7 flows, DNS queries, and TCP state transitions directly from the kernel, eliminating the CPU and memory overhead of sidecar proxies.
- Security monitoring at the kernel boundary -- detect container escapes, unexpected process execution, sensitive file access, and anomalous syscall patterns in real time.
- Continuous profiling in production -- generate CPU flame graphs and memory allocation profiles with negligible overhead (typically under 1% CPU).
- Service mesh replacement or augmentation -- Cilium can replace kube-proxy and provide identity-aware network policies enforced at the kernel level.
Avoid eBPF when your kernel version is below 4.19, when you are running on managed platforms that restrict BPF capabilities, or when your debugging needs are fully met by application-level tracing.
2. Prerequisites
Kernel Version Requirements
| Feature | Minimum Kernel | Recommended Kernel |
|---|
| Basic BPF maps & probes | 4.9 | 5.10+ |
| BPF CO-RE (BTF support) | 5.2 | 5.10+ |
| BPF ring buffer | 5.8 | 5.10+ |
| BPF LSM hooks | 5.7 | 5.15+ |
| Cilium full features | 4.19 | 5.10+ |
| Tetragon | 4.19 | 5.13+ |
Verify Kernel Support
uname -r
ls /sys/kernel/btf/vmlinux
mount | grep bpf
sudo mount -t bpf bpf /sys/fs/bpf
cat /proc/sys/net/core/bpf_jit_enable
sudo sysctl net.core.bpf_jit_enable=1
Install Toolchain
sudo apt-get update
sudo apt-get install -y bpftrace bpfcc-tools libbpf-dev linux-headers-$(uname -r)
sudo dnf install -y bpftrace bcc-tools libbpf-devel kernel-devel
sudo bpftrace -e 'BEGIN { printf("eBPF is working\n"); exit(); }'
3. Cilium Setup
Cilium replaces kube-proxy with eBPF-based networking, providing identity-aware security and deep network observability via Hubble.
Install Cilium on Kubernetes
helm repo add cilium https://helm.cilium.io/
helm repo update
helm install cilium cilium/cilium --version 1.16.4 \
--namespace kube-system \
--set kubeProxyReplacement=true \
--set k8sServiceHost="${API_SERVER_IP}" \
--set k8sServicePort="${API_SERVER_PORT}" \
--set hubble.enabled=true \
--set hubble.relay.enabled=true \
--set hubble.ui.enabled=true \
--set hubble.metrics.enableOpenMetrics=true \
--set hubble.metrics.enabled="{dns,drop,tcp,flow,port-distribution,icmp,httpV2:exemplars=true;labelsContext=source_ip\,source_namespace\,source_workload\,destination_ip\,destination_namespace\,destination_workload}"
cilium status --wait
Install the Cilium CLI and Hubble CLI
CILIUM_CLI_VERSION=$(curl -s https://raw.githubusercontent.com/cilium/cilium-cli/main/stable.txt)
curl -L --remote-name "https://github.com/cilium/cilium-cli/releases/download/${CILIUM_CLI_VERSION}/cilium-linux-amd64.tar.gz"
sudo tar xzvf cilium-linux-amd64.tar.gz -C /usr/local/bin
rm cilium-linux-amd64.tar.gz
HUBBLE_VERSION=$(curl -s https://raw.githubusercontent.com/cilium/hubble/master/stable.txt)
curl -L --remote-name "https://github.com/cilium/hubble/releases/download/${HUBBLE_VERSION}/hubble-linux-amd64.tar.gz"
sudo tar xzvf hubble-linux-amd64.tar.gz -C /usr/local/bin
rm hubble-linux-amd64.tar.gz
Hubble Network Observability
cilium hubble port-forward &
hubble observe --follow
hubble observe --namespace production --follow
hubble observe --verdict DROPPED --follow
hubble observe --protocol DNS --follow
hubble observe --to-label "app=api-server" --protocol HTTP --follow
hubble observe --output json --last 1000 > flows.json
Hubble UI Access
kubectl port-forward -n kube-system svc/hubble-ui 12000:80
4. Tetragon for Security
Tetragon is Cilium's runtime security enforcement engine. It uses eBPF to observe and enforce security policies at the kernel level with zero application changes.
Install Tetragon
helm repo add cilium https://helm.cilium.io/
helm repo update
helm install tetragon cilium/tetragon \
--namespace kube-system \
--set tetragon.grpc.enabled=true \
--set tetragon.exportFilename=/var/run/cilium/tetragon/tetragon.log
curl -LO "https://github.com/cilium/tetragon/releases/latest/download/tetra-linux-amd64.tar.gz"
sudo tar xzvf tetra-linux-amd64.tar.gz -C /usr/local/bin
rm tetra-linux-amd64.tar.gz
Process Execution Monitoring
apiVersion: cilium.io/v1alpha1
kind: TracingPolicy
metadata:
name: process-execution-monitor
spec:
kprobes: []
tracepoints: []
uprobes: []
enforcers: []
kubectl exec -n kube-system ds/tetragon -c tetragon -- tetra getevents -o compact --process-exec
kubectl exec -n kube-system ds/tetragon -c tetragon -- tetra getevents -o compact \
--namespace production
File Access Tracking
apiVersion: cilium.io/v1alpha1
kind: TracingPolicy
metadata:
name: sensitive-file-access
spec:
kprobes:
- call: "security_file_open"
syscall: false
args:
- index: 0
type: "file"
selectors:
- matchArgs:
- index: 0
operator: "Prefix"
values:
- "/etc/shadow"
- "/etc/passwd"
- "/etc/kubernetes/pki"
- "/var/run/secrets/kubernetes.io"
- "/root/.ssh"
kubectl apply -f file-access-policy.yaml
kubectl exec -n kube-system ds/tetragon -c tetragon -- tetra getevents -o compact \
| grep "sensitive-file-access"
Network Connection Enforcement
apiVersion: cilium.io/v1alpha1
kind: TracingPolicy
metadata:
name: restrict-egress-connections
spec:
kprobes:
- call: "tcp_connect"
syscall: false
args:
- index: 0
type: "sock"
selectors:
- matchArgs:
- index: 0
operator: "DAddr"
values:
- "169.254.169.254"
matchActions:
- action: Sigkill
- matchNamespaces:
- namespace: Mnt
operator: NotIn
values:
- "host_mnt"
kubectl apply -f restrict-egress.yaml
Privileged Escalation Detection
apiVersion: cilium.io/v1alpha1
kind: TracingPolicy
metadata:
name: detect-privilege-escalation
spec:
kprobes:
- call: "__x64_sys_setuid"
syscall: true
args:
- index: 0
type: "int"
selectors:
- matchArgs:
- index: 0
operator: "Equal"
values:
- "0"
matchActions:
- action: Post
rateLimit: "1m"
- call: "__x64_sys_setns"
syscall: true
args:
- index: 1
type: "int"
selectors:
- matchActions:
- action: Post
kubectl apply -f detect-privilege-escalation.yaml
5. bpftrace One-Liners
These are practical bpftrace commands you can run directly in production for targeted debugging.
Syscall Latency
sudo bpftrace -e 'tracepoint:syscalls:sys_enter_read { @start[tid] = nsecs; }
tracepoint:syscalls:sys_exit_read /@start[tid]/ {
@usecs = hist((nsecs - @start[tid]) / 1000);
delete(@start[tid]);
}'
sudo bpftrace -e 'tracepoint:raw_syscalls:sys_enter { @start[tid] = nsecs; }
tracepoint:raw_syscalls:sys_exit /@start[tid]/ {
@ns[probe] = sum(nsecs - @start[tid]);
delete(@start[tid]);
} END { print(@ns, 10); }'
DNS Tracing
sudo bpftrace -e 'kprobe:udp_sendmsg {
$sk = (struct sock *)arg0;
$dport = ($sk->__sk_common.skc_dport >> 8) | (($sk->__sk_common.skc_dport & 0xff) << 8);
if ($dport == 53) {
printf("%-8d %-16s DNS query to %s\n", pid, comm,
ntop($sk->__sk_common.skc_daddr));
}
}'
sudo bpftrace -e 'kprobe:udp_sendmsg {
$sk = (struct sock *)arg0;
$dport = ($sk->__sk_common.skc_dport >> 8) | (($sk->__sk_common.skc_dport & 0xff) << 8);
if ($dport == 53) { @dns[comm] = count(); }
}'
TCP Retransmits
sudo bpftrace -e 'kprobe:tcp_retransmit_skb {
$sk = (struct sock *)arg0;
$daddr = ntop($sk->__sk_common.skc_daddr);
$saddr = ntop($sk->__sk_common.skc_rcv_saddr);
$dport = ($sk->__sk_common.skc_dport >> 8) | (($sk->__sk_common.skc_dport & 0xff) << 8);
$sport = $sk->__sk_common.skc_num;
printf("%-20s %-6d -> %-20s %-6d (%s)\n", $saddr, $sport, $daddr, $dport, comm);
}'
Disk I/O Latency
sudo bpftrace -e 'tracepoint:block:block_rq_issue { @start[args->dev, args->sector] = nsecs; }
tracepoint:block:block_rq_complete /@start[args->dev, args->sector]/ {
@usecs[args->dev] = hist((nsecs - @start[args->dev, args->sector]) / 1000);
delete(@start[args->dev, args->sector]);
}'
sudo bpftrace -e 'tracepoint:block:block_rq_issue {
@bytes[comm] = sum(args->bytes);
} interval:s:5 { print(@bytes, 10); clear(@bytes); }'
Container-Aware Tracing
sudo bpftrace -e 'tracepoint:syscalls:sys_enter_execve {
printf("%-8d %-8d %-16s %s\n", pid, cgroup, comm, str(args->filename));
}'
sudo bpftrace -e 'kprobe:__alloc_pages { @pages[cgroup] = count(); }
interval:s:10 { print(@pages, 10); clear(@pages); }'
6. Prometheus Integration
Hubble Metrics for Prometheus
Hubble automatically exposes Prometheus metrics when configured in the Cilium Helm install. Verify the metrics endpoint:
kubectl exec -n kube-system ds/cilium -- curl -s http://localhost:9965/metrics | head -50
Create a ServiceMonitor for Prometheus Operator:
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: hubble-metrics
namespace: kube-system
labels:
app: cilium
spec:
selector:
matchLabels:
k8s-app: cilium
endpoints:
- port: hubble-metrics
interval: 15s
path: /metrics
eBPF Exporter for Custom Kernel Metrics
helm repo add ebpf-exporter https://cloudflare.github.io/ebpf_exporter
helm install ebpf-exporter ebpf-exporter/ebpf-exporter \
--namespace monitoring \
--set config.programs[0].name=oom_kills \
--set config.programs[0].metrics.counters[0].name=oom_kill_total \
--set config.programs[0].metrics.counters[0].help="Total number of OOM kills"
Example ebpf_exporter config for tracking OOM kills and run queue latency:
programs:
- name: oom_kills
metrics:
counters:
- name: oom_kill_total
help: "Total number of OOM kills"
labels:
- name: cgroup
size: 128
decoders:
- name: string
kprobes:
oom_kill_process: count_oom
- name: runqlat
metrics:
histograms:
- name: run_queue_latency_seconds
help: "Run queue latency histogram in seconds"
bucket_type: exp2
bucket_min: 0
bucket_max: 26
bucket_multiplier: 0.000000001
tracepoints:
sched:sched_wakeup: trace_wakeup
sched:sched_switch: trace_switch
Grafana Dashboard
Import these community dashboards for eBPF metrics:
kubectl create configmap grafana-cilium-dashboard \
--from-file=cilium-dashboard.json \
--namespace monitoring \
-o yaml --dry-run=client | \
kubectl label --local -f - grafana_dashboard=1 -o yaml | \
kubectl apply -f -
Key Prometheus queries for eBPF-sourced metrics:
# Dropped packets rate by reason
rate(hubble_drop_total[5m])
# DNS error rate by query type
sum(rate(hubble_dns_responses_total{rcode!="No Error"}[5m])) by (rcode, qtypes)
# HTTP request latency (p99) from Hubble L7 visibility
histogram_quantile(0.99, sum(rate(hubble_http_request_duration_seconds_bucket[5m])) by (le, destination))
# TCP retransmit rate from eBPF exporter
rate(tcp_retransmits_total[5m])
# Run queue latency p99
histogram_quantile(0.99, sum(rate(run_queue_latency_seconds_bucket[5m])) by (le))
7. Network Observability
L3/L4 Flow Logging
hubble observe --type l3/l4 --protocol TCP --follow
hubble observe --type trace:to-endpoint --tcp-flags SYN --follow
hubble observe --output json --since 1h > network-flows.json
hubble observe --output json --since 1h | \
jq -r '.destination.labels[] | select(startswith("k8s:app="))' | \
sort | uniq -c | sort -rn | head -20
L7 Protocol Visibility
Enable L7 visibility with Cilium annotations on target pods:
apiVersion: v1
kind: Namespace
metadata:
name: production
annotations:
policy.cilium.io/proxy-visibility: "<Egress/53/UDP/DNS>,<Ingress/80/TCP/HTTP>,<Ingress/443/TCP/HTTP>"
hubble observe --type l7 --protocol HTTP --follow
hubble observe --type l7 --http-status "500+" --follow
hubble observe --type l7 --http-method GET --http-path "/api/v1/.*" --follow
DNS Monitoring
hubble observe --type l7 --protocol DNS --follow
hubble observe --type l7 --protocol DNS --dns-rcode NXDOMAIN --follow
sudo bpftrace -e 'kprobe:dns_resolve { @start[tid] = nsecs; }
kretprobe:dns_resolve /@start[tid]/ {
@dns_latency_us = hist((nsecs - @start[tid]) / 1000);
delete(@start[tid]);
}'
Service Dependency Map Generation
Hubble UI automatically generates service maps. For programmatic access:
hubble observe --output json --since 24h | \
jq '{src: .source.labels, dst: .destination.labels, verdict: .verdict}' | \
jq -s 'group_by(.src, .dst) | map({
source: .[0].src,
destination: .[0].dst,
flow_count: length,
verdicts: [.[].verdict] | group_by(.) | map({(.[0]): length}) | add
})' > service-map.json
8. Security Monitoring
Detect Container Escapes
apiVersion: cilium.io/v1alpha1
kind: TracingPolicy
metadata:
name: detect-container-escape
spec:
kprobes:
- call: "__x64_sys_unshare"
syscall: true
args:
- index: 0
type: "int"
selectors:
- matchActions:
- action: Post
- call: "__x64_sys_mount"
syscall: true
args:
- index: 0
type: "string"
- index: 1
type: "string"
- index: 2
type: "string"
selectors:
- matchArgs:
- index: 2
operator: "Equal"
values:
- "proc"
- "sysfs"
- "cgroup"
matchActions:
- action: Post
- call: "__x64_sys_ptrace"
syscall: true
args:
- index: 0
type: "int"
selectors:
- matchActions:
- action: Post
kubectl apply -f container-escape-detection.yaml
Unexpected Syscall Detection
apiVersion: cilium.io/v1alpha1
kind: TracingPolicy
metadata:
name: unexpected-syscalls
spec:
kprobes:
- call: "__x64_sys_bpf"
syscall: true
args:
- index: 0
type: "int"
selectors:
- matchNamespaces:
- namespace: Pid
operator: NotIn
values:
- "host_ns"
matchActions:
- action: Post
- call: "__x64_sys_perf_event_open"
syscall: true
selectors:
- matchNamespaces:
- namespace: Pid
operator: NotIn
values:
- "host_ns"
matchActions:
- action: Post
- call: "__x64_sys_init_module"
syscall: true
selectors:
- matchActions:
- action: Sigkill
File Integrity Monitoring
apiVersion: cilium.io/v1alpha1
kind: TracingPolicy
metadata:
name: file-integrity-monitor
spec:
kprobes:
- call: "security_file_open"
syscall: false
args:
- index: 0
type: "file"
selectors:
- matchArgs:
- index: 0
operator: "Prefix"
values:
- "/etc/"
- "/usr/bin/"
- "/usr/sbin/"
- "/usr/lib/"
matchActions:
- action: Post
rateLimit: "1m"
- call: "security_inode_rename"
syscall: false
args:
- index: 0
type: "path"
- index: 1
type: "path"
selectors:
- matchActions:
- action: Post
kubectl apply -f file-integrity-monitor.yaml
kubectl logs -n kube-system ds/tetragon -c export-stdout -f | \
jq 'select(.process_kprobe.policy_name == "file-integrity-monitor")' | \
tee /dev/stderr | \
curl -X POST -H "Content-Type: application/json" -d @- https://siem.internal/api/events
9. Performance Profiling
Continuous Profiling with Parca
Parca uses eBPF to collect CPU profiles continuously with minimal overhead.
helm repo add parca https://parca-dev.github.io/helm-charts
helm repo update
helm install parca-agent parca/parca-agent \
--namespace parca \
--create-namespace \
--set config.node=true \
--set config.store.address="parca-server.parca.svc:7070" \
--set config.store.insecure=true \
--set config.debuginfo.strip=true \
--set config.debuginfo.upload.enabled=true
Continuous Profiling with Pyroscope
helm repo add grafana https://grafana.github.io/helm-charts
helm repo update
helm install pyroscope grafana/pyroscope \
--namespace pyroscope \
--create-namespace \
--set ebpf.enabled=true \
--set agent.mode=ebpf
CPU Flame Graphs with bpftrace
sudo bpftrace -e 'profile:hz:99 { @[kstack, ustack, comm] = count(); }' \
-d 30 > stacks.out
sudo perf record -F 99 -a -g -- sleep 30
sudo perf script > perf.stacks
git clone https://github.com/brendangregg/FlameGraph.git
./FlameGraph/stackcollapse-perf.pl perf.stacks | \
./FlameGraph/flamegraph.pl > flamegraph.svg
Off-CPU Analysis
sudo bpftrace -e '
kprobe:finish_task_switch {
$prev = (struct task_struct *)arg0;
if ($prev->__state != 0) {
@block_start[$prev->pid] = nsecs;
}
if (@block_start[tid]) {
@off_cpu_us[kstack, comm] = sum((nsecs - @block_start[tid]) / 1000);
delete(@block_start[tid]);
}
}
END { print(@off_cpu_us, 20); }'
Memory Leak Detection
sudo bpftrace -e '
kprobe:kmalloc { @allocs[kstack] = count(); @bytes[kstack] = sum(arg0); }
kprobe:kfree { @frees = count(); }
interval:s:10 { print(@bytes, 10); }
'
sudo bpftrace -e '
uprobe:/lib/x86_64-linux-gnu/libc.so.6:malloc { @size[comm, tid] = sum(arg0); }
interval:s:5 { print(@size, 10); clear(@size); }
'
10. Troubleshooting
Common eBPF Issues
BPF verifier rejects program:
sudo bpftrace -d -e 'your_program_here' 2>&1 | tail -50
BTF not available:
cat /boot/config-$(uname -r) | grep CONFIG_DEBUG_INFO_BTF
wget "https://github.com/aquasecurity/btfhub-archive/raw/main/ubuntu/22.04/x86_64/$(uname -r).btf.tar.xz"
tar xvf "$(uname -r).btf.tar.xz"
Permission denied:
cat /proc/self/status | grep Cap
capsh --decode=$(cat /proc/self/status | grep CapEff | awk '{print $2}')
Cilium pods not starting:
kubectl logs -n kube-system -l k8s-app=cilium --tail=100
kubectl exec -n kube-system ds/cilium -- mount | grep bpf
ls /etc/cni/net.d/
cilium connectivity test
Tetragon events missing:
kubectl get tracingpolicies
kubectl logs -n kube-system ds/tetragon -c tetragon --tail=200 | grep -i error
kubectl exec -n kube-system ds/tetragon -c tetragon -- \
cat /sys/kernel/debug/kprobes/list | grep your_function
High overhead from eBPF programs:
sudo bpftool prog show
sudo bpftool prog profile id <PROG_ID> duration 5
sudo bpftool map show
sudo bpftool map dump id <MAP_ID> | wc -l
sudo bpftool prog show id <PROG_ID> --json | jq '{run_cnt, run_time_ns}'
sudo bpftool prog detach id <PROG_ID> type <ATTACH_TYPE>
Kernel Compatibility Matrix
sudo bpftool feature probe kernel
sudo bpftool feature probe kernel | grep program_type
sudo bpftool feature probe kernel | grep map_type
sudo bpftool feature probe kernel | grep helper