Part 7 of 756 min read · 26 diagramsAI-assisted

eBPF for Observability & Networking

Table of Contents#

  1. Why This Part Exists
  2. What eBPF Actually Is
  3. The Verifier — eBPF's Own Safety Guarantee
  4. JIT Compilation — Why eBPF Is Fast
  5. Program Types and Hook Points
  6. eBPF Maps — Kernel-Userspace Shared State
  7. A Minimal eBPF Program, Conceptually Built Up
  8. CO-RE — Compile Once, Run Everywhere
  9. bpftrace — Ad-Hoc Kernel Tracing Without Writing C
  10. A Worked Example: Tracing Syscall Latency With bpftrace
  11. A Worked Example: Tracing TCP Connections With bpftrace
  12. XDP Revisited — This Series' Own Earlier Preview, in Full
  13. How Cilium Builds a CNI Dataplane on eBPF
  14. eBPF Maps Replacing iptables — the Concrete O(n)-to-O(1) Win
  15. Cilium's NetworkPolicy Enforcement, Concretely
  16. TC (Traffic Control) eBPF and the Newer TCX Hook
  17. seccomp-BPF — This Series' Own systemd Callback, Explained
  18. eBPF for Observability — Beyond Networking
  19. Zero-Instrumentation Observability — What It Actually Means
  20. eBPF vs. Traditional Tracing Tools
  21. Security Considerations — eBPF's Own Privilege Model
  22. Debugging a Rejected eBPF Program
  23. When NOT to Reach for eBPF
  24. A Full Worked Example: a Simple Connection-Tracking bpftrace Script
  25. Hubble — Cilium's Own Observability Layer
  26. Multi-Cluster and Cross-Node eBPF Considerations
  27. eBPF and the Broader Container/Kubernetes Security Landscape
  28. Performance Overhead — eBPF Is Fast, Not Free
  29. eBPF's Own Ecosystem Beyond Cilium and bpftrace
  30. A Worked Comparison: Diagnosing the Same Issue With and Without eBPF
  31. Key Terms Glossary — This Chapter's Vocabulary in One Place
  32. An eBPF Adoption Checklist
  33. A Second Worked Example: Measuring Real DNS Resolution Latency
  34. Correlating eBPF Data With Application-Level Tracing
  35. A Full Realistic Reference Architecture: eBPF Across a Production Kubernetes Node
  36. Load-Time vs. Run-Time Failure Modes — a Practical Distinction
  37. A Decision Framework: bpftrace, a Custom eBPF Program, or an Established Tool?
  38. WebAssembly (WASM) — a Brief, Honest Comparison
  39. Flame Graphs — Visualizing Sampled Profiling Data
  40. Common Mistakes
  41. Worked Practice Problems
  42. eBPF's Kernel Version Requirements — a Practical Adoption Note
  43. Summary and What's Next

Why This Part Exists#

This series has previewed eBPF twice already without fully explaining it: Part 4 introduced XDP as an "even earlier than netfilter" hook point for extreme-packet-rate needs, and both Part 4 and Part 5 referenced Cilium's eBPF-based dataplane as an alternative to iptables-based CNI implementations. This final chapter closes both loops directly — covering eBPF's own programming model in genuine depth, bpftrace as the practical, hands-on entry point for ad-hoc kernel-level tracing, and exactly how Cilium composes eBPF's primitives into a complete CNI dataplane.

Diagram

Worth setting expectations precisely before diving in: this chapter does not teach writing eBPF programs in C from scratch (a genuinely deep, specialized skill) — it teaches the concepts, the safety model, and bpftrace as the practical tool that lets a platform engineer write real, useful eBPF-powered tracing scripts without first becoming a kernel developer, which covers the large majority of what a working SRE/platform engineer actually needs from eBPF directly.


What eBPF Actually Is#

eBPF (extended Berkeley Packet Filter) lets a platform engineer run genuinely custom, sandboxed programs directly inside the Linux kernel, triggered by specific kernel events — network packets arriving, syscalls being made, kernel functions being called — without writing or loading a traditional kernel module.

Diagram

The "without a traditional kernel module" distinction deserves the strongest emphasis, since it's the single fact that explains eBPF's entire safety and adoption story: a buggy kernel module can crash the entire system, since it runs with full, unchecked kernel privileges — an eBPF program, by contrast, is mathematically verified safe (covered in depth next) before the kernel ever allows it to run at all, closing off the single scariest failure mode of the older kernel-module approach. This is exactly why eBPF adoption has grown so dramatically since its 2014 introduction (Linux 3.18) — it offers genuine, deep kernel-level programmability without asking an operator to trust an unverified module's own correctness, a fundamentally different risk profile from anything that came before it.


The Verifier — eBPF's Own Safety Guarantee#

Every eBPF program, before the kernel allows it to load and run at all, passes through the verifier — a static analysis pass proving the program cannot crash the kernel, cannot access memory outside its allowed bounds, and is guaranteed to terminate.

Diagram

Worth stating precisely what "proven safe" actually requires, since it's a genuinely strict standard: no unbounded loops (a loop the verifier cannot statically prove will terminate is rejected outright — a real constraint eBPF programs are written around, not worked past), no access to memory outside the program's own verified bounds, and a bounded total instruction count. This is exactly why eBPF programs are typically small, tightly-scoped pieces of logic rather than arbitrary general-purpose code — the verifier's own constraints are a deliberate, structural tradeoff (real expressiveness limits) in direct exchange for the safety guarantee that makes running arbitrary-ish code inside the kernel acceptable at all. A rejected program (this chapter's own later debugging section covers this concretely) simply never loads — there is no partial, half-verified execution state to worry about.


JIT Compilation — Why eBPF Is Fast#

An eBPF program is not interpreted at runtime the way, for instance, a shell script is — after passing the verifier, it's JIT-compiled (Just-In-Time) directly to native machine code for the specific CPU architecture it's running on.

Diagram

This is worth stating as the direct, concrete explanation for a claim this series has made in passing without fully justifying — that eBPF programs can run on every single packet, or every single syscall, at genuinely high event rates with acceptable overhead: JIT compilation to real native machine code is what makes this performance profile possible at all, in direct contrast to an interpreted approach that would add meaningful, unacceptable per-event overhead at high event rates. This is the same class of "compile to native code rather than interpret" performance argument familiar from any JIT-compiled language runtime — applied here specifically to code running inside the kernel's own hot packet-processing and syscall-handling paths.


Program Types and Hook Points#

eBPF programs are classified by type, each type attaching to a specific class of kernel hook point — worth surveying the types most relevant to this chapter's own networking and observability focus.

Program typeAttaches toTypical use
XDPThe earliest possible network hook (Part 4's own preview)Line-rate packet filtering/redirection, DDoS mitigation
TC (Traffic Control)Later in the network stack than XDP, both ingress and egressCilium's own primary dataplane hook (covered in depth later)
LSM (Linux Security Module)Kernel security-decision pointsFine-grained, in-kernel security policy enforcement, an alternative to seccomp for some use cases
raw_tracepointThe same stable tracepoints as tracepoint, with lower per-event overheadHigh-frequency tracing where even tracepoint's own small overhead matters
sk_msg / sk_skbSocket-layer message/packet interceptionHigher-level socket-based load balancing, used by some service mesh dataplanes
kprobe / kretprobeKernel function entry/returnTracing arbitrary kernel function calls (bpftrace's core mechanism)
uprobe / uretprobeUserspace function entry/returnTracing library-level code (this chapter's own DNS-latency worked example)
tracepointStable, kernel-maintainer-defined tracing pointsMore stable than kprobes across kernel versions
socket filterSocket-level packet inspectionThe original, historical BPF use case this whole technology is named after
cgroupCgroup-scoped events (this series' own Part 6 cgroup material)Per-cgroup network/syscall policy
perf_eventCPU performance counters and samplingLow-overhead, sampled profiling (e.g. flame graphs) rather than per-event tracing

The kprobe-vs-tracepoint distinction deserves specific emphasis, since it's a genuinely practical consideration when writing real tracing scripts: a tracepoint is a stable, explicitly-maintained hook point the kernel developers commit to keeping consistent across kernel versions, while a kprobe attaches to essentially ANY kernel function by name — far more flexible and comprehensive, but with no stability guarantee at all, since a kernel function's own name, arguments, or existence can change between kernel versions with no warning. A tracing script built around a tracepoint is meaningfully more portable across different kernel versions than one built around a kprobe targeting an internal, non-stable kernel function — worth choosing deliberately based on how much version-portability a given script actually needs.


eBPF Maps — Kernel-Userspace Shared State#

An eBPF program running inside the kernel needs a way to both persist state across invocations and communicate with userspace — eBPF maps are the mechanism, a family of kernel-resident key-value data structures both the eBPF program and a userspace process can read and write.

Diagram

This bidirectional access is worth stating as the core mechanism underneath essentially every practical eBPF use case this chapter covers: a kernel-side eBPF program can increment a counter, record a timestamp, or store a connection's own state in a map on every single triggering event (a packet, a syscall), while a userspace process reads that same map continuously to aggregate, display, or act on that data — this is literally how bpftrace's own aggregations work (covered in this chapter's own worked examples), and it's the exact mechanism Cilium uses to replace iptables' own rule-based lookups with a genuinely faster hash-map lookup, covered in depth later in this chapter.


A Minimal eBPF Program, Conceptually Built Up#

Worth seeing the conceptual shape of a real eBPF program — not full, compilable C, but close enough to make the verifier/map/hook concepts from this chapter's own earlier sections concrete.

// A conceptual XDP program: count packets by source IP
SEC("xdp")
int count_packets(struct xdp_md *ctx) {
    // 1. Parse the packet to extract the source IP
    __u32 src_ip = parse_source_ip(ctx);  // simplified

    // 2. Look up (or initialize) a counter in an eBPF map, keyed by that IP
    __u64 *count = bpf_map_lookup_elem(&packet_counts, &src_ip);
    if (count) {
        (*count)++;
    } else {
        __u64 initial = 1;
        bpf_map_update_elem(&packet_counts, &src_ip, &initial, BPF_ANY);
    }

    // 3. Let the packet continue processing normally
    return XDP_PASS;
}

Worth reading this conceptual example against every mechanism this chapter has already covered: SEC("xdp") declares this program's TYPE (this chapter's own program-types section) and hook point; bpf_map_lookup_elem/bpf_map_update_elem are the actual map read/write operations (this chapter's own maps section); and the entire function body is small, loop-free, and bounded — exactly the shape the verifier requires to prove it safe. return XDP_PASS is worth noting specifically — an XDP program must explicitly decide the packet's fate (XDP_PASS to continue normal processing, XDP_DROP to discard it at the earliest possible point, XDP_TX to bounce it back out the same interface) on every single invocation, a direct, concrete instance of Part 4's own XDP preview now made fully explicit.


CO-RE — Compile Once, Run Everywhere#

A genuinely practical problem worth naming directly: kernel data structures (the exact internal layout of a kernel struct) can differ between kernel versions and even between distributions' own kernel builds — historically meaning an eBPF program compiled against one specific kernel's headers might not work correctly on a different kernel at all.

Diagram

BTF (BPF Type Format) is the concrete mechanism worth naming precisely: it's kernel-embedded type metadata describing the actual, real layout of kernel data structures on that specific running kernel, and CO-RE-compiled eBPF programs use this metadata to correctly resolve struct field offsets at load time, rather than baking in offsets fixed at compile time against one specific kernel's headers. This is a genuinely significant practical improvement worth stating plainly: it's what makes it realistic to distribute a single, pre-compiled eBPF-based tool (Cilium's own agent, for instance) across a fleet of nodes running meaningfully different kernel versions, rather than needing a separate, kernel-version-matched build for every distinct kernel a fleet happens to run.


bpftrace — Ad-Hoc Kernel Tracing Without Writing C#

bpftrace is a high-level tracing language and runtime, compiling concise, one-line-friendly scripts down into real eBPF programs automatically — worth treating as this chapter's own practical, hands-on entry point into everything covered so far, without needing to write raw eBPF C or manage the verifier/map/hook mechanics directly.

# Count syscalls by name, system-wide, live
bpftrace -e 'tracepoint:raw_syscalls:sys_enter { @[comm] = count(); }'

# Trace every process execution, with its arguments
bpftrace -e 'tracepoint:syscalls:sys_enter_execve { printf("%s -> %s\n", comm, str(args->filename)); }'

Worth reading these one-liners against this chapter's own program-types section directly: tracepoint:raw_syscalls:sys_enter attaches a tracepoint-type eBPF program (this chapter's own more-stable-across-kernel-versions category) to the syscall-entry hook, and @[comm] = count() is bpftrace's own high-level syntax for exactly the map-based counting pattern this chapter's earlier conceptual C example demonstrated manually — bpftrace handles the map creation, the aggregation, and the userspace-side reporting entirely automatically. This is the single most practically useful fact about bpftrace worth internalizing: it turns "I want to trace this specific kernel event, live, right now, without writing and compiling a real eBPF program" into a genuinely one-line, interactive command — worth reaching for as the default starting point for any ad-hoc kernel-level investigation, well before considering hand-writing raw eBPF C.


A Worked Example: Tracing Syscall Latency With bpftrace#

bpftrace -e '
kprobe:vfs_read { @start[tid] = nsecs; }
kretprobe:vfs_read /@start[tid]/ {
    @latency_ns = hist(nsecs - @start[tid]);
    delete(@start[tid]);
}
'

Worth walking through this precisely, since it demonstrates the kprobe+kretprobe pairing pattern that's genuinely central to real-world latency tracing: kprobe:vfs_read fires on function ENTRY, recording a start timestamp keyed by thread ID into a map (@start[tid]); kretprobe:vfs_read fires on function RETURN, computing the elapsed time and feeding it into a histogram aggregation (hist()), then cleaning up the per-thread start-time entry. hist() is worth calling out specifically as bpftrace's own built-in histogram aggregation — producing a genuinely useful, log2-bucketed latency distribution directly in the terminal, without any manual bucketing logic required from the script author, a real, practical time-saver for exactly the kind of "what's the actual latency distribution of this kernel operation" question this course's Observability series covers at the application layer, now answered directly at the kernel level.


A Worked Example: Tracing TCP Connections With bpftrace#

bpftrace -e '
kprobe:tcp_connect {
    printf("TCP connect: pid=%d comm=%s\n", pid, comm);
}
'

Worth connecting this directly back to Part 2's own TCP material: this one-liner fires every time ANY process on the host initiates a TCP connection (the exact moment Part 2's own three-way handshake begins), reporting which process initiated it — genuinely useful for a real, common production question ("which process is opening all these outbound connections") that's otherwise surprisingly hard to answer definitively without kernel-level tracing, since standard tools like netstat/ss show a point-in-time snapshot of currently-open connections, not a live, attributed stream of every connection attempt as it actually happens. This is worth recognizing as a genuinely distinct capability from Part 3's own troubleshooting toolkit — bpftrace answers a fundamentally different class of question (live, per-event, kernel-level attribution) than a point-in-time inspection tool ever can.


XDP Revisited — This Series' Own Earlier Preview, in Full#

Part 4 previewed XDP as "even earlier than netfilter" — worth now stating the full, concrete mechanics, grounded in this chapter's own eBPF vocabulary.

Diagram

Every one of these four XDP actions (XDP_DROP, XDP_PASS, XDP_TX, XDP_REDIRECT) is a value an XDP-type eBPF program returns from its own function body — the exact return-value mechanism this chapter's own minimal-program example demonstrated directly. XDP_REDIRECT deserves specific mention as the mechanism underneath the highest-performance packet-forwarding use cases (Meta's own Katran load balancer, a widely-cited real production example) — redirecting a packet to a different interface entirely at this earliest possible hook point, without the packet ever traversing the kernel's normal, slower forwarding path at all.


How Cilium Builds a CNI Dataplane on eBPF#

Worth the direct, concrete payoff of this chapter's own opening promise: Cilium composes exactly the primitives covered so far in this chapter — program types, maps, hook points — into a complete, production CNI dataplane.

Diagram

This is worth stating as the direct, concrete synthesis this entire chapter has been building toward: Cilium's own control-plane agent watches Kubernetes objects (Services, NetworkPolicies, Pod creation/deletion — the exact objects this course's Kubernetes series covers), and compiles/loads real eBPF programs implementing the corresponding dataplane behavior, using this chapter's own maps as the shared state between the kernel-side enforcement and Cilium's own userspace agent. A Kubernetes Service's own virtual-IP-to-backend-Pod mapping (Part 4's own kube-proxy-iptables-mode callback) becomes, in Cilium's own architecture, an entry in an eBPF map instead — the concrete mechanism this chapter's next section quantifies precisely.


eBPF Maps Replacing iptables — the Concrete O(n)-to-O(1) Win#

Worth quantifying precisely why Cilium's eBPF-based Service implementation is genuinely, measurably faster than kube-proxy's iptables mode at real scale — not a vague "eBPF is faster" claim, but a concrete algorithmic difference.

Diagram

This is worth stating with the precision the underlying claim deserves, directly grounded in Part 4's own iptables material: kube-proxy's iptables mode implements each Service as a chain of DNAT rules (Part 4's own mechanism), evaluated with the exact first-match-wins, top-to-bottom semantics Part 4 covered — meaning the WORST-CASE lookup cost genuinely does grow linearly with the total number of Services/rules in the cluster. Cilium's eBPF-based implementation replaces this entirely with a hash-map lookup keyed directly by the Service's virtual IP — a real, algorithmic O(1) operation, independent of how many other Services exist. This is a genuine, measurable, non-marketing performance difference specifically at large cluster scale (thousands of Services), directly explaining why very large-scale Kubernetes deployments frequently choose an eBPF-based CNI specifically for this reason, beyond any other feature consideration.


Cilium's NetworkPolicy Enforcement, Concretely#

Worth a direct callback to Part 4's own CNI-plugin NetworkPolicy section, resolving the "how does an eBPF-based CNI plugin implement this differently" question that section left open.

Diagram

The identity-based model deserves specific emphasis as a genuine Cilium-specific design choice worth understanding, distinct from Part 4's own iptables-based CNI description: rather than generating IP-address-based filtering rules directly (which would need regenerating every time a Pod's IP changes, a genuinely frequent event in Kubernetes), Cilium derives a stable "identity" from a Pod's own labels and enforces policy based on that identity, with IP-to-identity resolution handled as a separate, fast lookup. This is worth recognizing as a real architectural advantage specifically because Pod IPs churn constantly (Pods are recreated with new IPs routinely) while a Pod's own labels — and therefore its Cilium identity — typically remain stable across that churn, meaning policy enforcement doesn't need to be regenerated on every single Pod recreation the way a naive IP-based rule set would.


TC (Traffic Control) eBPF and the Newer TCX Hook#

Worth a brief, honest note on Cilium's own primary hook point choice, and a genuinely current (2026) evolution worth knowing about: while XDP operates at the earliest possible point, Cilium's core dataplane logic primarily attaches at the TC (Traffic Control) hook — later in the network stack, with access to more complete packet/socket context than XDP's earlier, more limited view.

Diagram

The XDP-vs-TC tradeoff worth stating precisely: XDP's earlier hook point buys the absolute highest possible performance ceiling, at the cost of a more limited view of the packet (some kernel networking context simply hasn't been constructed yet at that early point) — TC's later position trades some of that raw performance ceiling for meaningfully richer context, a better fit for Cilium's own genuinely complex identity-and-policy logic than XDP's more limited, earlier vantage point. TCX is worth knowing by name as a real, current (2026) evolution of the TC attachment mechanism specifically — designed to let multiple independent eBPF programs attach and cooperate at the same hook point more cleanly than the older TC mechanism allowed, relevant for exactly the kind of multi-purpose (networking, security, observability) eBPF program composition this chapter's own examples have gestured toward throughout.


seccomp-BPF — This Series' Own systemd Callback, Explained#

Part 6 introduced SystemCallFilter= as configuring "a seccomp-BPF filter" without fully explaining the mechanism — worth closing that loop directly, since it's a genuine, if narrower, eBPF application.

Diagram

This is worth recognizing directly as this chapter's own program-types table applied to a genuinely different purpose than networking or tracing: seccomp-BPF is a syscall-filtering eBPF program, evaluated on every syscall a process attempts, closing off an entire class of kernel attack surface (invoking a syscall the process was never designed to need) — exactly the mechanism Part 6's own SystemCallFilter= directive configures on a systemd-managed service's behalf, now fully explained as one more concrete instance of eBPF's own general programming model.


eBPF for Observability — Beyond Networking#

Worth a direct, explicit broadening beyond this chapter's own networking focus so far: eBPF's observability applications extend well past network tracing — file I/O latency, memory allocation patterns, lock contention, and off-CPU time analysis are all genuinely accessible through the exact same kprobe/tracepoint/map mechanisms this chapter has already covered.

# File open latency, system-wide
bpftrace -e 'kprobe:vfs_open { @start[tid] = nsecs; }
kretprobe:vfs_open /@start[tid]/ { @ = hist(nsecs - @start[tid]); delete(@start[tid]); }'

# Off-CPU time — how long processes spend BLOCKED, not just running
bpftrace -e 'kprobe:finish_task_switch { @time[curtask] = nsecs; }'

Worth stating precisely why this matters, connecting directly to this course's own Observability series: standard CPU-usage monitoring (this course's own golden-signals material) only sees time a process spends ACTIVELY running on a CPU — it's structurally blind to time a process spends BLOCKED (waiting on I/O, waiting on a lock, waiting to be scheduled), which can be the actual dominant cause of a real-world latency problem that pure CPU monitoring never surfaces at all. Off-CPU analysis via eBPF is a genuinely distinct, complementary observability technique specifically because it answers a question CPU-usage metrics structurally cannot — worth knowing exists as a real diagnostic tool for the specific, otherwise-hard-to-diagnose "the process isn't using much CPU but is still slow" class of production problem.


Zero-Instrumentation Observability — What It Actually Means#

Worth a precise, honest definition of a term this chapter's own opening implicitly invoked: "zero-instrumentation" observability via eBPF means gaining real, detailed visibility into an application's behavior WITHOUT modifying that application's own code, redeploying it, or requiring it to link any specific library at all.

Diagram

The honest, precise scope of this claim matters, worth stating directly rather than overselling: "zero instrumentation" is genuinely true for anything observable at the KERNEL boundary — syscalls, network activity, file I/O, process scheduling — but does NOT extend to genuinely application-internal behavior (a specific function's own business logic, an internal cache hit rate) that never crosses a kernel boundary at all, which still requires real application-level instrumentation (this course's own OpenTelemetry material) to observe. This is worth stating as a real, meaningful, but bounded capability — eBPF-based tools (Pixie, Cilium's own Hubble observability layer) genuinely deliver rich network/syscall-level visibility with zero code changes, while deeper, business-logic-level observability remains squarely in application-level instrumentation's own domain, the two approaches complementary rather than one fully replacing the other.


eBPF vs. Traditional Tracing Tools#

Worth a direct, honest comparison table closing this chapter's own tooling survey — eBPF/bpftrace against the more familiar tools this series' own Part 3 troubleshooting toolkit already covered.

ToolWhat it seesLive/dynamic?
straceEvery syscall a SPECIFIC process makes, with full argumentsYes, but with SIGNIFICANT per-syscall overhead — genuinely disruptive to a latency-sensitive process
ltraceLibrary-call tracing, an strace cousin for shared-library callsYes, with similarly significant overhead
Parca / PyroscopeContinuous, always-on eBPF-based profilingYes, designed specifically for low-overhead standing production use
tcpdumpRaw packet captureYes, but sees only what crosses the interface it's watching
netstat/ssPoint-in-time connection snapshotNo — a snapshot, not a live stream; misses connections between checks
bpftrace/eBPFEssentially any kernel event, system-wide or scoped, with custom aggregationYes, with dramatically lower overhead than strace specifically, due to JIT compilation (this chapter's own material)
perfCPU performance counters, sampled profilingYes, lower overhead than syscall-level tracing for CPU-focused questions specifically

The strace-overhead comparison deserves the strongest emphasis, since it's a genuinely important practical distinction for a production incident specifically: strace works by using ptrace() to intercept and pause the traced process on every single syscall, a mechanism with real, sometimes-severe performance overhead that can itself distort the very behavior being investigated (a classic observer-effect problem) — an eBPF-based equivalent achieves comparable visibility with dramatically lower overhead, precisely because JIT-compiled eBPF programs execute inline, in-kernel, without the pause-and-context-switch overhead ptrace()-based tracing incurs on every single event. This is worth knowing as a genuine, practical reason to reach for bpftrace over strace specifically on a production, latency-sensitive process during a real incident — not merely a newer/older tool preference, but a real difference in how much the tracing itself perturbs the system being observed.


Security Considerations — eBPF's Own Privilege Model#

Worth a direct, honest security note, connecting to this course's own DevSecOps series: loading an eBPF program has historically required genuine elevated privileges (CAP_BPF, or the broader CAP_SYS_ADMIN on older kernels) — worth understanding the real security implications of this requirement.

Diagram

Worth stating precisely why this two-layer model (privilege check, THEN verifier) matters for a real security assessment: the verifier guarantees a LOADED eBPF program cannot crash the kernel or access unauthorized memory, but it says nothing about whether that program is doing something an operator would actually WANT — a correctly-verified, perfectly memory-safe eBPF program could still, for instance, legitimately read every packet on the network for a purpose the operator never intended. This is exactly why the privilege requirement (CAP_BPF) matters as a genuinely separate, complementary control from the verifier's own memory-safety guarantee — a platform team auditing which processes/containers hold CAP_BPF is auditing a real, meaningful privilege boundary, not a redundant check duplicating what the verifier already covers.


Debugging a Rejected eBPF Program#

# Attempt to load a program, capturing verifier output
bpftool prog load my_program.o /sys/fs/bpf/my_program

# Common verifier rejection reasons, worth recognizing by their actual error text:
# "back-edge from insn X to Y" -> an unbounded loop the verifier cannot prove terminates
# "invalid mem access" -> an out-of-bounds or unchecked pointer dereference
# "too many instructions" -> exceeds the verifier's own bounded instruction-count limit

Worth stating this section's own practical value directly: a rejected eBPF program's verifier error message, while sometimes genuinely cryptic on first read, almost always maps back to one of this chapter's own explicitly-covered constraints — an unbounded loop, an unverified memory access, or an instruction-count ceiling — meaning a platform engineer encountering a rejection has a real, structured starting point for diagnosis rather than an opaque, unexplained failure. This is worth connecting directly to this course's own general debugging philosophy — root-causing a rejection means mapping the actual error text back to which specific verifier guarantee (from this chapter's own dedicated verifier section) the program failed to satisfy, then restructuring the program's own logic to satisfy it, rather than treating the rejection as an unexplainable black box.


When NOT to Reach for eBPF#

Worth a genuinely honest, closing counter-section, consistent with this course's own pattern of never presenting one technology as unconditionally correct: eBPF is a real, powerful capability, but reaching for it by default for every kernel-level need is a real, avoidable overreach.

Diagram

Worth stating the honest cases where eBPF is genuine overreach: a routine firewall rule need is fully served by Part 4's own netfilter/nftables material — reaching for a custom XDP program for ordinary packet filtering adds real, unnecessary complexity (writing, verifying, and maintaining actual eBPF programs, a genuinely more specialized skill than authoring nftables rules) for a need nftables already serves adequately. Similarly, a one-off debugging need that strace or tcpdump genuinely answers well enough doesn't require reaching for bpftrace — the real, justified case for eBPF is specifically extreme performance requirements (XDP-level packet processing), genuinely custom in-kernel logic no existing tool provides, or the specific zero-instrumentation observability use case this chapter covered directly — not a default, reach-for-eBPF-first instinct for every kernel-adjacent need a platform engineer encounters.


A Full Worked Example: a Simple Connection-Tracking bpftrace Script#

Tying multiple mechanisms from this chapter together into one complete, realistic diagnostic script — tracking every new TCP connection's source, destination, and eventual duration.

bpftrace -e '
kprobe:tcp_connect {
    @start[tid] = nsecs;
    @info[tid] = comm;
}
kprobe:tcp_close /@start[tid]/ {
    $duration_ms = (nsecs - @start[tid]) / 1000000;
    printf("%s: connection lasted %d ms\n", @info[tid], $duration_ms);
    delete(@start[tid]);
    delete(@info[tid]);
}
'

Worth reading this as the concrete, cumulative synthesis of this entire chapter: kprobe pairing (this chapter's own latency-tracing pattern), per-thread map-based state (@start[tid], @info[tid]), and live, real-time reporting — all composed together into a genuinely useful, ad-hoc diagnostic tool answering a real production question ("which connections are lasting an unusually long or short time, and from which process") with zero application code changes, zero redeployment, and a script short enough to type directly during a live incident. This is worth recognizing as the practical, hands-on payoff of this entire chapter — the theoretical machinery (verifier, JIT, maps, hook points) all in service of exactly this kind of fast, live, kernel-level diagnostic capability.


Hubble — Cilium's Own Observability Layer#

Worth a direct, concrete extension of this chapter's own Cilium material: Hubble is Cilium's own observability component, built directly on top of the eBPF data Cilium's dataplane already collects — worth understanding as a genuine, practical example of this chapter's own "eBPF for observability" material applied specifically to network flow visibility.

Diagram
# Live flow observation, filtered to denied traffic specifically
hubble observe --verdict DROPPED

# Flows for a specific Pod
hubble observe --pod checkout-7f9d8

This is worth stating as the direct, concrete payoff of Cilium's own architectural choice to enforce policy in-kernel via eBPF, covered earlier in this chapter: because every packet's identity resolution and policy verdict already happens in-kernel as part of normal traffic processing, Hubble doesn't need any SEPARATE instrumentation or sidecar to produce rich network observability — it surfaces data the dataplane was already computing anyway, the same "zero additional instrumentation, since the data already exists at the kernel boundary" property this chapter's own zero-instrumentation section described generally, now demonstrated as a real, shipped production tool. A platform engineer debugging "why is this Pod's traffic being blocked" on a Cilium cluster should reach for hubble observe --verdict DROPPED directly, rather than attempting to reconstruct the answer from raw eBPF map state by hand — Hubble is precisely the tool built to make that data genuinely usable.


Multi-Cluster and Cross-Node eBPF Considerations#

Worth a direct connection to Part 5's own cross-host virtual networking material: eBPF programs, being loaded per-node, need a genuine strategy for maintaining consistent policy and identity state ACROSS an entire cluster, not just correctness on any single node in isolation.

Diagram

Worth stating directly why this matters practically: an eBPF program's own maps are inherently local to the node they're loaded on — there's no built-in, automatic cross-node map replication at the eBPF level itself, meaning Cilium's own control-plane agent is responsible for propagating identity and policy state to every node's own local eBPF maps, keeping them consistent with the cluster's actual, current desired state. This is worth recognizing as directly analogous to the "declarative configuration, continuously reconciled" pattern this entire course returns to repeatedly — a NetworkPolicy change doesn't take effect by magically updating every node's eBPF state instantaneously and atomically; it propagates through Cilium's own control plane, with each node's agent reconciling its own local eBPF maps to match, the same eventually-consistent reconciliation model Kubernetes itself uses for every other object type this course has covered.


eBPF and the Broader Container/Kubernetes Security Landscape#

Worth a direct, closing connection to this course's own DevSecOps series: eBPF's runtime, kernel-level visibility makes it a genuinely powerful foundation for a class of security tooling distinct from static, build-time scanning — runtime threat detection tools (Falco, Tetragon) are built directly on the same kprobe/tracepoint primitives this chapter has covered throughout.

Diagram

This is worth stating as a genuine, complementary layer to this course's own existing supply-chain-security material, not a replacement for it: static scanning answers "does this image contain a KNOWN vulnerable dependency," a build-time, before-the-fact question — eBPF-based runtime security answers a fundamentally different, complementary question: "is this ALREADY-RUNNING container doing something it shouldn't," using exactly this chapter's own kprobe/tracepoint mechanism to observe real, live syscall and file-access behavior and flag genuine anomalies (a web server process suddenly spawning a shell, a container reading a credentials file it's never legitimately needed before) that no amount of build-time scanning could ever catch, since those are runtime behaviors with no equivalent at build time at all. A mature security posture uses both layers together — static scanning catching known-bad dependencies before deployment, eBPF-based runtime detection catching genuinely anomalous behavior in an already-running, already-scanned container that a static scan structurally cannot see.


Performance Overhead — eBPF Is Fast, Not Free#

Worth a direct, honest correction to a claim this chapter has made repeatedly in a way that could be misread as "eBPF has zero cost" — every eBPF program attached to a hot path (every packet, every syscall) still executes real instructions on every single triggering event, and that cost, while genuinely low relative to alternatives, is not literally zero.

Diagram

This is worth stating precisely as a closing, honest caveat to this chapter's own consistently favorable framing of eBPF's performance: "near-native speed" (this chapter's own JIT section) means dramatically lower overhead than an interpreted or ptrace()-based alternative, not literally free — a platform team attaching an increasingly complex chain of eBPF programs to an extremely high-throughput hot path (Cilium's own dataplane, or a custom XDP program processing a fully-saturated link) should still genuinely benchmark real, measured overhead under real production load, rather than assuming "it's eBPF, so it's effectively free." This is worth connecting directly to this course's own general performance-engineering discipline — measure real, observed impact under real conditions rather than trusting a technology's own general reputation for being fast, the same discipline this course applies to every other performance claim throughout its material.


eBPF's Own Ecosystem Beyond Cilium and bpftrace#

Worth a brief, honest survey closing out this chapter's own tooling coverage — Cilium and bpftrace are this chapter's own focus, but worth knowing the broader eBPF ecosystem this ties into.

ToolPurpose
bpftoolThe low-level, official CLI for inspecting loaded eBPF programs and maps directly — this chapter's own debugging section used it
BCC (BPF Compiler Collection)An older, Python-based framework for writing eBPF tools — largely superseded by bpftrace for ad-hoc use, still used for more complex, packaged tools
Aya (Rust)A Rust-native eBPF development framework, an alternative to the C/libbpf toolchain
eBPF for WindowsAn in-progress port bringing eBPF's own programming model to the Windows kernel
bpftopA live, top-style resource-usage view across every currently-loaded eBPF program on a host
retsnoopA focused kprobe/kretprobe-based tool for tracing specific kernel function call chains
execsnoop / opensnoopFocused, single-purpose bpftrace-style tools tracing process execution and file opens respectively
tcplife / tcptopFocused BCC/bpftrace-style tools for TCP connection lifetime and per-connection throughput
biolatency / biosnoopFocused BCC/bpftrace-style tools for block-device I/O latency, extending this chapter's off-CPU material to disk I/O specifically
FalcoeBPF-based runtime security detection (this chapter's own security-landscape section)
Inspektor GadgetA curated collection of pre-built bpftrace-style diagnostic gadgets for Kubernetes specifically
Retina (Microsoft)An eBPF-based Kubernetes network observability platform, an AKS-adjacent equivalent to Hubble
PixieZero-instrumentation Kubernetes observability, built on eBPF
Beyla (Grafana)Zero-instrumentation OpenTelemetry auto-instrumentation, built on eBPF's uprobe mechanism
KatranMeta's own high-performance XDP-based load balancer — a real, production XDP_REDIRECT use case (this chapter's own XDP section)
TetragonCilium's own eBPF-based runtime security enforcement project, alongside Falco in the same category
libbpfThe standard C library and toolchain most CO-RE-compiled eBPF programs are actually built against
bpftrace-flameFlame-graph generation built on perf_event sampling, this chapter's own closing visualization topic
Parca / Grafana PyroscopeContinuous, always-on eBPF-based profiling products, built on the same perf_event foundation

Worth reading this table as confirmation of a pattern this entire chapter has argued for: every tool listed here is built from the exact same small set of primitives this chapter has covered directly — program types, the verifier, maps, JIT compilation — meaning genuine fluency in this chapter's own foundational material transfers directly to understanding (and evaluating) any of these tools, rather than needing to learn each one's own internals from scratch as an unrelated, separate technology.

bpftool deserves one further, practical note beyond its listing above: it's worth keeping as a standing reference for direct, low-level inspection of what's actually loaded on a given host at any moment — bpftool prog list and bpftool map list show every currently-loaded eBPF program and map system-wide, regardless of which higher-level tool (Cilium, Falco, a bpftrace script) originally loaded it, making it the correct first command when the actual question is simply "what eBPF is running on this host right now" rather than a question scoped to any one specific tool's own reporting.

This same "one low-level command reveals ground truth, regardless of which higher-level tool produced the current state" property recurs across this entire seven-part series — nft list ruleset (Part 4), ip netns list (Part 5), systemctl list-units (Part 6), and bpftool prog list (this chapter) each serve the identical role at their own respective layer of the stack.


A Worked Comparison: Diagnosing the Same Issue With and Without eBPF#

Worth a closing, concrete comparison making this chapter's own practical value unmistakable — the identical production question, answered two different ways.

Diagram

This is worth stating as the concrete, practical culmination of everything this chapter has covered: the traditional approach (this chapter's own earlier comparison to strace/tcpdump's own limitations) is fundamentally a point-in-time or interface-scoped technique, structurally capable of missing short-lived events between observation windows — the eBPF-based approach (this chapter's own TCP-connection-tracing worked example, revisited here) observes every single triggering event as it happens, with zero possibility of missing a connection that occurred entirely between two manual snapshots. This is worth internalizing as the actual, concrete reason to reach for bpftrace specifically during a live production incident involving any kind of transient, hard-to-catch-in-a-snapshot event — not a theoretical technology preference, but a genuine, practical difference in what's observable at all.


Key Terms Glossary — This Chapter's Vocabulary in One Place#

TermMeaning in this chapter's context
eBPFExtended Berkeley Packet Filter — kernel-level, verified, sandboxed custom programs
VerifierThe static analysis pass proving a program safe (bounded loops, valid memory access) before it can load
JIT compilationCompiling verified eBPF bytecode to native machine code for near-native execution speed
eBPF mapKernel-resident key-value state, shared between an eBPF program and userspace
CO-RE / BTFCompile Once, Run Everywhere — using kernel type metadata to run one build across kernel versions
bpftraceA high-level tracing language compiling concise scripts into real eBPF programs
XDPThe earliest network hook point — highest performance ceiling, most limited packet context
TC / TCXA later network hook point with richer context — Cilium's own primary dataplane hook
HubbleCilium's observability layer, surfacing the same eBPF-collected flow/policy data as queryable output
seccomp-BPFA syscall-filtering eBPF program type — the mechanism underneath systemd's SystemCallFilter=
Off-CPU analysisMeasuring time a process spends BLOCKED, not actively running — invisible to CPU-usage metrics
raw_tracepointA lower-overhead variant of tracepoint, for genuinely high-frequency events
CAP_BPFThe kernel capability required to load an eBPF program — a genuine, auditable privilege boundary
uprobe / uretprobeUserspace function entry/return probes — extending kprobe-style tracing to library code
bpftoolThe low-level CLI for inspecting every currently-loaded eBPF program/map on a host, tool-agnostic
WASM (WebAssembly)A genuinely separate, userspace sandboxed-execution technology — complementary to, not a substitute for, eBPF
Flame graphA width-encodes-time visualization of perf_event-sampled CPU profiling data
perf_eventThe CPU performance-counter/sampling program type underlying flame-graph profiling
libbpfThe standard toolchain most CO-RE-compiled production eBPF programs are built against

An eBPF Adoption Checklist#

Worth closing this chapter's practical guidance with one final, walkable checklist — the concrete sequence a platform team should actually follow before adopting an eBPF-based tool or writing a custom eBPF-based diagnostic script for production use.

StepWhy
1. Confirm the target kernel version supports the required eBPF featuresPer this chapter's own kernel-version-requirements section — not every fleet runs a uniformly current kernel
2. Default to bpftrace for ad-hoc diagnostics, not custom CMeaningfully lower barrier to entry, per this chapter's own tooling material
3. Confirm a standard, simpler mechanism doesn't already cover the needPer this chapter's own "when not to reach for eBPF" guidance
4. Audit CAP_BPF grants as a genuine, sensitive privilege boundaryNot a redundant check the verifier already covers
5. Benchmark real overhead under real, representative peak load"Near-native" is not "free" — verify under genuine production conditions
6. For a production tool (not ad-hoc tracing), prefer an established project (Cilium, Falco, Hubble) over custom codeBattle-tested verifier-compatibility and CO-RE portability already solved
7. Document which hook point(s) and program type(s) a new tool attachesKeeps future debugging (per this chapter's own multi-tool coexistence material) tractable

This checklist deliberately closes the same loop this entire series has built up across every prior part's own closing checklist — worth treating this as the seventh and final entry in one continuous production-change discipline spanning firewall rules, virtual networking, service management, and now kernel-level programmability itself.


A Second Worked Example: Measuring Real DNS Resolution Latency#

Worth a direct, practical callback to Part 2's own DNS material, demonstrating eBPF's own diagnostic value against a concrete, familiar problem this series already introduced conceptually.

bpftrace -e '
uprobe:/usr/lib/x86_64-linux-gnu/libc.so.6:getaddrinfo {
    @start[tid] = nsecs;
}
uretprobe:/usr/lib/x86_64-linux-gnu/libc.so.6:getaddrinfo /@start[tid]/ {
    @dns_latency_ms = hist((nsecs - @start[tid]) / 1000000);
    delete(@start[tid]);
}
'

Worth noting a genuinely important new detail this example introduces: uprobe/uretprobe (userspace probe) attach to a function in a USERSPACE library — libc's own getaddrinfo, the actual C library function underneath most applications' own DNS resolution calls — rather than a kernel function, extending this chapter's own kprobe/kretprobe pairing pattern to userspace code as well. This is worth stating precisely as a real, useful extension of "zero-instrumentation observability" (this chapter's own earlier, more bounded claim): while genuinely internal application business logic remains invisible without real instrumentation, a WIDELY-SHARED userspace library function like getaddrinfo — called by countless applications without any of them individually instrumenting it — becomes directly observable via uprobe, closing part of the gap this chapter's own zero-instrumentation section deliberately left open, specifically for library-level (not application-specific) behavior.


Correlating eBPF Data With Application-Level Tracing#

Worth a closing, direct connection to this course's own Observability series' OpenTelemetry material: the most complete production observability picture combines eBPF's own kernel/library-level visibility with application-level distributed tracing, rather than treating either as a complete answer alone.

Diagram

This is worth stating as the honest, complete picture this chapter's own earlier "zero-instrumentation observability" section deliberately scoped narrowly: an OpenTelemetry span showing a slow checkout call tells a team WHAT was slow from the application's own point of view, but doesn't automatically explain WHY — was it genuinely slow application logic, or was it actually blocked on a slow DNS lookup, a slow TCP handshake, or scheduler contention, all of which this chapter's own eBPF techniques observe directly and the application's own span data never captures at all. A mature observability practice treats these as complementary, correlated layers — application-level tracing identifying WHICH request/service call was slow, eBPF-based kernel/library-level data explaining the underlying, lower-level WHY — rather than expecting either layer alone to provide a complete diagnostic picture on its own.


A Full Realistic Reference Architecture: eBPF Across a Production Kubernetes Node#

Tying nearly every mechanism from this chapter, and several from earlier in this series, into one composite, realistic picture of a single production Kubernetes node running an eBPF-based CNI.

Diagram

Worth reading this as the concrete, cumulative payoff of this entire seven-part series, not just this final chapter: a single production Kubernetes node, in this realistic picture, has MULTIPLE independent eBPF programs loaded simultaneously and cooperating (or at minimum coexisting) at different hook points — Cilium's own dataplane logic at the TC hook, Hubble reading the same underlying data, Falco's own separate kprobe-based security monitoring, and per-container seccomp-BPF filters, all running alongside systemd's own service-management layer from Part 6, which itself sits on top of the namespace and virtual-networking primitives from Part 5, all ultimately filtering and routing packets according to the same netfilter concepts from Part 4 wherever eBPF-based enforcement isn't in the path. This composite picture is worth holding in mind as the honest, full-complexity reality of a modern production node — not a simplified, single-layer diagram, but the genuine, multi-layered composition this entire series has built up one primitive at a time.


Load-Time vs. Run-Time Failure Modes — a Practical Distinction#

Worth a closing, practical clarification for anyone debugging an eBPF-based tool in production: a program that successfully passes the verifier and loads can still encounter genuinely distinct failure categories once it's actually running, worth distinguishing precisely rather than treating "eBPF isn't working" as one undifferentiated problem.

Diagram

This distinction is worth internalizing precisely, since the two failure categories demand genuinely different diagnostic approaches: a load-time rejection is loud and immediate — the program simply never runs, with a verifier error explaining exactly why, per this chapter's own debugging section. A run-time issue is quieter and more insidious — a correctly-loaded, verifier-approved program can still behave unexpectedly at run time for reasons the verifier has no visibility into at all, the most common example being an eBPF map reaching its own pre-configured maximum size and silently failing further insert operations, degrading a tracing script's own accuracy (missed events) without any explicit error at all. bpftool map dump (inspecting a map's actual current contents and size directly) is the practical diagnostic step for exactly this run-time category — worth checking explicitly rather than assuming a correctly-loaded program is necessarily producing complete, accurate data.


A Decision Framework: bpftrace, a Custom eBPF Program, or an Established Tool?#

Worth closing this chapter's own tooling guidance with a single, walkable decision flow — the practical "which approach" question a platform engineer actually faces when a real eBPF-shaped need arises.

Diagram

Every branch in this flow maps directly to a section already covered in depth across this chapter — worth treating this as a navigation aid back into the chapter's own content, the same closing pattern this series has used consistently across every prior part. The recurring shape worth internalizing: writing a genuinely custom eBPF program from scratch is deliberately the LAST resort in this flow, not the default — bpftrace for ad-hoc needs and established, already-hardened projects for standing production needs cover the overwhelming majority of real, practical eBPF use cases a platform engineer actually encounters.


WebAssembly (WASM) — a Brief, Honest Comparison#

Worth a closing, brief comparison to a genuinely different sandboxed-execution technology this course's own quiz material touches on: WebAssembly (WASM) is sometimes mentioned alongside eBPF as "another way to run sandboxed code safely," worth distinguishing precisely rather than conflating.

eBPFWASM
Runs whereInside the KERNELTypically in USERSPACE (a WASM runtime, an Envoy filter, an edge platform)
Primary use caseKernel-level networking, tracing, securityPortable application/plugin logic, edge compute, browser code
Safety mechanismThe verifier — static proof before loadSandboxed runtime execution — different isolation model entirely

Worth stating the honest, precise distinction rather than treating these as interchangeable: eBPF's entire value proposition in this chapter has been kernel-level access with kernel-level performance — WASM's own value proposition is portable, sandboxed application-level logic, most commonly used for things like Envoy proxy filters (this course's own CI/CD & GitOps series' Gateway API chapters reference Envoy directly) or edge-compute platforms, running in USERSPACE, not inside the kernel at all. They are genuinely complementary, not competing, technologies solving different problems at different layers of the stack — worth knowing both exist and what each is actually for, rather than assuming either one is simply a newer or older version of the same underlying idea.


Flame Graphs — Visualizing Sampled Profiling Data#

Worth a brief, closing note on the perf_event program type from this chapter's own program-types table: sampled CPU profiling data, collected via eBPF, is most commonly visualized as a flame graph — worth knowing how to read one, since it's a genuinely common artifact in real performance investigations.

Diagram

The single most important reading rule worth stating directly: a flame graph's WIDTH represents proportion of total samples (time), not left-to-right chronological order the way a typical timeline chart would — a wide frame near the bottom of the graph is where the CPU is genuinely spending the most time, regardless of its horizontal position. This is worth knowing as the practical visualization layer sitting on top of this chapter's own perf_event program type — the same sampled-profiling data this chapter's program-types table introduced, made human-readable through this specific, now-standard visualization convention.


Common Mistakes#

MistakeWhy it's a problemFix
Assuming an eBPF program can contain an unbounded loop if the logic genuinely needs oneThe verifier will reject any loop it cannot statically prove terminates, full stopRestructure the logic to use bounded loops, or move genuinely unbounded logic to userspace, communicating via a map
Reaching for a custom eBPF program for a routine firewall needAdds real complexity (writing, verifying, maintaining eBPF code) for a need nftables already servesUse Part 4's own netfilter/nftables material for standard packet filtering
Using kprobe on an internal, non-stable kernel function for a script meant to run across many kernel versionsKprobes have no stability guarantee — the target function can change or disappear between kernel versionsPrefer a tracepoint when one exists for the event of interest, for meaningfully better cross-version portability
Treating "zero-instrumentation observability" as covering everything an application doesIt only covers what crosses a kernel boundary — genuinely internal business logic remains invisible to itCombine eBPF-based kernel-level visibility with real application-level instrumentation (OpenTelemetry) for full coverage
Assuming CAP_BPF privilege alone means a loaded eBPF program is doing something safe/intendedThe verifier guarantees memory safety, not that the program's actual PURPOSE is authorized or wantedAudit which processes/containers hold CAP_BPF as a genuine, separate privilege boundary, not a redundant check
Using strace on a latency-sensitive production process during an incidentptrace()-based tracing has real, sometimes-severe overhead that can distort the very behavior being investigatedPrefer bpftrace/eBPF-based tracing for meaningfully lower observer-effect overhead in a production incident
Assuming Cilium's identity-based policy model works identically to a plain IP-based iptables NetworkPolicy implementationCilium derives policy from Pod labels/identity, not raw IPs — a genuinely different underlying modelUnderstand the identity-based model specifically before assuming IP-centric debugging techniques transfer directly
Assuming a correctly-loaded eBPF program is necessarily producing complete, accurate dataA map reaching its own size limit can silently drop new entries at run time, with no explicit error at allCheck bpftool map dump directly rather than assuming load success implies correctness
Conflating eBPF and WASM as interchangeable sandboxing technologiesThey operate at genuinely different layers (kernel vs. userspace) solving different problemsUnderstand each technology's own actual scope before assuming either replaces the other
Reading a flame graph's horizontal position as chronological orderWidth represents proportion of samples/time, not a left-to-right timelineRead WIDTH as time spent; ignore horizontal position as meaningless ordering
Using ltrace/strace on a production process without weighing the overhead tradeoffBoth introduce real, sometimes severe overhead that can distort the very behavior under investigationDefault to bpftrace/eBPF-based tracing for a production, latency-sensitive process

Worked Practice Problems#

Problem 1: A platform engineer writes an eBPF program with a loop intended to iterate until a specific condition is met, and the kernel refuses to load it with a "back-edge" error. What's the actual cause, and what are their real options?

Answer: The verifier cannot statically prove the loop will terminate — an unbounded loop (one whose termination depends on runtime data the verifier cannot reason about in advance) is rejected outright, regardless of whether the loop would, in practice, always terminate correctly. The real options are restructuring the logic into a bounded loop (a fixed, verifier-provable maximum iteration count) or moving the genuinely unbounded portion of the logic to a userspace process, with the eBPF program itself only recording data into a map for that userspace process to act on.

Problem 2: A team wants to build a custom XDP program to implement basic SSH-port allowlisting for a set of trusted IPs — functionality nftables already handles. What should they be told, per this chapter's own guidance?

Answer: This is a case where standard netfilter/nftables (Part 4's own material) already adequately serves the need — reaching for a custom eBPF/XDP program here adds real, unnecessary complexity (writing, verifying, and maintaining actual eBPF code, a genuinely more specialized skill than authoring nftables rules) for a routine filtering need nftables' own sets/maps already handle cleanly. eBPF is the correct, justified choice specifically for extreme performance requirements, genuinely custom logic no existing tool provides, or zero-instrumentation observability — not a default choice for standard firewall needs.

Problem 3: A platform engineer is debugging a mysterious latency issue on a production service where CPU usage metrics show the process is barely using any CPU at all, yet requests are still slow. Standard CPU-based monitoring shows nothing unusual. What eBPF-based technique from this chapter directly addresses this class of problem, and why does standard monitoring miss it?

Answer: Off-CPU time analysis via eBPF (kprobe:finish_task_switch, this chapter's own worked example) — standard CPU-usage monitoring is structurally blind to time a process spends BLOCKED (waiting on I/O, a lock, or being scheduled), since it only measures time actively spent running ON a CPU. A process that's slow because it's blocked, not because it's computing, will show low CPU usage precisely because the actual bottleneck (the blocking wait) is invisible to CPU-usage metrics by design — off-CPU analysis directly measures this otherwise-invisible blocked time instead.

Problem 4: A team debugging a Kubernetes NetworkPolicy issue on a Cilium-based cluster tries to trace the issue by inspecting iptables -L on a node, expecting to find the relevant DROP/ACCEPT rules the way they would on an iptables-based CNI plugin. They find nothing relevant. Why, given this chapter's own material?

Answer: Cilium enforces NetworkPolicy through eBPF maps and identity-based lookups, not through generated iptables rules at all — the entire enforcement mechanism this chapter covered (identity derived from Pod labels, enforced via an eBPF map keyed by that identity) bypasses iptables entirely. Debugging a Cilium NetworkPolicy issue requires Cilium-specific tooling (Hubble, cilium CLI commands inspecting the actual eBPF-enforced policy state) rather than the iptables-focused debugging technique that would work correctly on an iptables-based CNI plugin instead.

Problem 5: A security team is auditing a cluster and finds a container with CAP_BPF granted. They want to understand precisely what risk this represents, given that eBPF's own verifier guarantees memory safety. How should they reason about this?

Answer: The verifier's memory-safety guarantee and the CAP_BPF privilege check are two genuinely separate, complementary controls, not redundant ones — the verifier guarantees a loaded program cannot crash the kernel or access unauthorized memory, but says nothing about whether the program's actual PURPOSE is something the operator would want. A container with CAP_BPF can load a perfectly verifier-safe eBPF program that, for instance, legitimately captures and exfiltrates network traffic — a real, meaningful security risk the verifier's own memory-safety guarantee does nothing to prevent. CAP_BPF should be treated as a genuine, sensitive privilege boundary worth auditing carefully, not assumed safe purely because any program that uses it must pass the verifier.

Problem 6: A platform team is evaluating whether their existing static image-scanning pipeline (per this course's own DevSecOps series) is sufficient coverage against a container that gets compromised and starts behaving maliciously AFTER deployment. What gap should they be told about, and what closes it?

Answer: Static scanning only catches known-vulnerable dependencies at build time — it says nothing about what an already-deployed, already-running container actually does at runtime. A container that passed every static scan cleanly can still be compromised post-deployment (via a zero-day, a credential leak, or a supply-chain attack the scan didn't catch) and begin behaving maliciously with no static-scanning signal at all. eBPF-based runtime security tooling (Falco, Tetragon) closes exactly this gap, using kprobe/tracepoint-based observation of real, live syscall and file-access behavior to detect genuinely anomalous runtime behavior a static, build-time scan structurally cannot see.

Problem 7: A team benchmarks their XDP-based packet-processing pipeline under normal load and it performs excellently, then deploys it against a fully-saturated 100Gbps link during a real traffic spike and observes measurable, unexpected overhead. Does this contradict this chapter's own claims about eBPF's performance?

Answer: No — this chapter explicitly cautioned that "near-native speed" and JIT compilation mean dramatically lower overhead than alternatives, not literally zero cost; every eBPF program attached to a hot path still executes real instructions on every single triggering event, and that real, if individually small, per-event cost genuinely compounds at extreme event rates. The correct response is benchmarking real overhead under real, representative production load (including genuine peak conditions) rather than assuming a favorable benchmark under normal load guarantees identical behavior at a fully-saturated extreme — exactly the general performance-engineering discipline this chapter closed by recommending.

Problem 8: An application's OpenTelemetry trace shows a checkout service call taking 450ms, but the application's own internal code, per the trace's own span breakdown, only accounts for 200ms of that time. Where should the team look next, and with what tool?

Answer: The unaccounted 250ms is invisible to the application-level trace precisely because it happened below the application's own instrumentation boundary — a slow DNS resolution, a slow TCP handshake, or scheduler contention (off-CPU time) are all genuine candidates, and none of them are visible in application-level span data at all. The team should reach for eBPF-based tooling (bpftrace, using this chapter's own uprobe-based DNS latency example or kprobe-based TCP connection tracing) to observe exactly where that missing time actually went — the correlated combination of application-level tracing (WHAT was slow) and eBPF-based kernel/library-level data (WHY, at a lower level) this chapter's own closing section described.

Problem 9: A platform team runs Cilium, Hubble, and Falco simultaneously on the same production Kubernetes nodes, and a new engineer asks whether these three eBPF-based tools might conflict with each other by all trying to attach programs at the same hook points. What's the accurate answer?

Answer: They can coexist without conflicting, because they serve genuinely different purposes and, per this chapter's own TCX material, modern eBPF tooling is specifically designed to let multiple independent programs attach and cooperate at shared hook points more cleanly than older mechanisms allowed. Cilium's own dataplane logic (Service routing, NetworkPolicy enforcement) operates at the TC hook; Hubble reads Cilium's own already-collected data rather than attaching separate packet-processing logic of its own; Falco's own kprobe-based syscall/file monitoring operates at an entirely different set of hook points (syscall entry/exit) unrelated to Cilium's own network-focused hooks. This composite, multi-tool picture is precisely what this chapter's own closing reference architecture described as the realistic, full-complexity state of a modern production node.

Problem 10: A team profiles a slow service using perf_event-based sampling and receives a flame graph where one particular function's frame is unusually wide, but positioned near the right edge of the graph. A junior engineer assumes this means the function ran late in the request's execution. Is this assumption correct, per this chapter's own material?

Answer: Not necessarily — a flame graph's horizontal position is not a chronological timeline at all; only the WIDTH of a frame is meaningful, representing the proportion of total samples (and therefore CPU time) spent in that function. The frame's left-right position within the graph carries no timing information whatsoever. The correct conclusion from an unusually wide frame is simply that this function accounts for a large share of total CPU time — its horizontal position should be ignored entirely when reasoning about when, chronologically, it executed.


eBPF's Kernel Version Requirements — a Practical Adoption Note#

Worth a closing, practical note for any team evaluating eBPF adoption on infrastructure they don't fully control the kernel version of: eBPF's own capability set has expanded significantly across kernel versions since its 2014 introduction, and not every feature this chapter has covered is available on every kernel a real fleet might still be running.

Diagram

This is worth stating directly as a real, practical adoption consideration rather than a purely theoretical caveat: a platform team evaluating an eBPF-based tool (Cilium, Falco, a custom bpftrace script relying on a specific, newer feature) for a fleet running a mix of kernel versions should explicitly check that tool's own stated minimum kernel requirement against their actual fleet's real, current kernel versions — not assume every kernel in a real, heterogeneous production fleet supports every eBPF capability this chapter has described. This is precisely the kind of practical verification this course's own research discipline argues for throughout — checking a specific, current claim against the actual target environment, rather than assuming a general technology description applies uniformly everywhere it might be deployed.


Summary and What's Next#

eBPF lets a platform engineer run custom, kernel-resident programs triggered by real kernel events — network packets, syscalls, kernel function calls — without the crash risk of a traditional kernel module, because every program passes the verifier's static safety proof (no unbounded loops, no out-of-bounds memory access, bounded instruction count) before it's ever allowed to load, and then runs at near-native speed via JIT compilation to real machine code. Programs are typed by their hook point (XDP for the earliest possible network path, TC/TCX for Cilium's own richer-context dataplane logic, kprobes/tracepoints for arbitrary kernel-level tracing, seccomp-BPF for syscall filtering — directly explaining Part 6's own SystemCallFilter= reference), and eBPF maps provide the shared, kernel-userspace state that makes both stateful packet processing and live, aggregated tracing possible at all. CO-RE and BTF solve the genuine, practical problem of distributing one compiled eBPF program across a fleet running different kernel versions, without needing a separate build per kernel. bpftrace is this chapter's own practical, hands-on entry point — compiling concise, high-level tracing scripts into real eBPF programs automatically, delivering genuinely useful latency histograms, connection tracing, and off-CPU analysis with dramatically lower overhead than ptrace()-based tools like strace, and with zero application code changes required for anything observable at the kernel boundary. Cilium composes exactly these same primitives — XDP/TC hook points, eBPF maps, identity-based policy state — into a complete CNI dataplane, replacing kube-proxy's O(n) iptables rule chains with genuinely O(1) hash-map Service lookups and iptables-generated NetworkPolicy rules with identity-based, in-kernel enforcement that survives Pod IP churn far more gracefully than IP-address-based rules ever could. None of this makes eBPF the correct default choice for every kernel-level need — routine firewall rules remain Part 4's own netfilter/nftables domain, and a one-off debugging need strace/tcpdump already answers well doesn't require reaching for a heavier tool.

This closes the full seven-part Linux & Networking Fundamentals series. Parts 1-3 established process/memory internals, TCP/IP/DNS, and the core troubleshooting toolkit; Parts 4-7 went from packet filtering and NAT, through the virtual networking primitives containers and Kubernetes are literally built from, through systemd's own service/dependency/resource-management model, to eBPF's own kernel-programmability frontier — the transferable thread running through every part of this series is the same one: nothing in Kubernetes, in a container runtime, or in a service mesh is unexplainable magic. Every abstraction this course's other series build on top of — a Kubernetes Service, a Pod's own IP, a systemd-managed node component, an eBPF-based CNI plugin — reduces, concretely and reproducibly, to the Linux kernel primitives this series has covered directly, hands-on, from the very first chapter to this final one.

The single practical habit worth carrying forward past every individual technical detail in this series: when an abstraction from this course's Kubernetes, CI/CD, or DevSecOps material behaves unexpectedly, the diagnostic path is always available to drop down a layer — from the Kubernetes object, to the CNI plugin's own implementation, to the actual kernel primitive (netfilter rule, network namespace, systemd unit, or eBPF program) underneath it — rather than treating any layer of the stack as a black box beyond investigation. That habit, built hands-on across this series' seven parts, is worth more than any single command or configuration flag this series has covered along the way.

Every worked example across all seven parts of this series was, deliberately, runnable directly on a real Linux host — the same discipline this course applies throughout: verified, hands-on material, not description alone.