<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Observability Archives - Linuxcent</title>
	<atom:link href="https://linuxcent.com/tag/observability/feed/" rel="self" type="application/rss+xml" />
	<link>https://linuxcent.com/tag/observability/</link>
	<description>Infrastructure security, from the kernel up.</description>
	<lastBuildDate>Mon, 06 Jul 2026 03:36:46 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.0.4</generator>

<image>
	<url>https://linuxcent.com/wp-content/uploads/2026/04/favicon-512x512-1-150x150.png</url>
	<title>Observability Archives - Linuxcent</title>
	<link>https://linuxcent.com/tag/observability/</link>
	<width>32</width>
	<height>32</height>
</image> 
<site xmlns="com-wordpress:feed-additions:1">211632295</site>	<item>
		<title>DNS at the Kernel Level — What Your Pods Are Actually Resolving</title>
		<link>https://linuxcent.com/ebpf-dns-observability-kubernetes/</link>
					<comments>https://linuxcent.com/ebpf-dns-observability-kubernetes/#respond</comments>
		
		<dc:creator><![CDATA[Vamshi Krishna Santhapuri]]></dc:creator>
		<pubDate>Sat, 06 Jun 2026 02:00:00 +0000</pubDate>
				<category><![CDATA[eBPF]]></category>
		<category><![CDATA[CoreDNS]]></category>
		<category><![CDATA[DNS]]></category>
		<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[Observability]]></category>
		<category><![CDATA[SRE]]></category>
		<category><![CDATA[Tracing]]></category>
		<guid isPermaLink="false">https://linuxcent.com/?p=1840</guid>

					<description><![CDATA[<p><span class="span-reading-time rt-reading-time" style="display: block;"><span class="rt-label rt-prefix">Reading Time: </span> <span class="rt-time"> 9</span> <span class="rt-label rt-postfix">minutes</span></span>DNS observability Kubernetes needs eBPF tracepoints, not CoreDNS metrics — trace every pod DNS query in real time with no sidecar, restart, or sampling.</p>
<p>The post <a href="https://linuxcent.com/ebpf-dns-observability-kubernetes/">DNS at the Kernel Level — What Your Pods Are Actually Resolving</a> appeared first on <a href="https://linuxcent.com">Linuxcent</a>.</p>
]]></description>
										<content:encoded><![CDATA[<span class="span-reading-time rt-reading-time" style="display: block;"><span class="rt-label rt-prefix">Reading Time: </span> <span class="rt-time"> 9</span> <span class="rt-label rt-postfix">minutes</span></span><style>
pre{position:relative;background:#1e1e1e;color:#d4d4d4;
    padding:16px 16px 16px 20px;border-radius:6px;overflow-x:auto;
    font-family:'JetBrains Mono','Fira Code','Cascadia Code',Consolas,'Courier New',monospace;
    font-size:.88em;line-height:1.6;border-left:4px solid #555}
code{background:#f4f4f4;padding:2px 5px;border-radius:3px;font-size:.9em}
pre code{background:transparent;padding:0;color:inherit}
pre[data-lang="bash"],pre[data-lang="sh"],
pre[data-lang="shell"],pre[data-lang="zsh"]{border-left-color:#4ec9b0}
pre[data-lang="yaml"],pre[data-lang="json"],
pre[data-lang="toml"],pre[data-lang="xml"]{border-left-color:#569cd6}
pre[data-lang="python"],pre[data-lang="go"],pre[data-lang="rust"],
pre[data-lang="java"],pre[data-lang="c"],pre[data-lang="cpp"]{border-left-color:#c586c0}
pre[data-lang="text"],pre[data-lang="output"],
pre[data-lang="console"]{border-left-color:#888}
.lc-copy-btn{position:absolute;top:8px;right:8px;background:#2d2d2d;color:#ccc;
    border:1px solid #444;border-radius:4px;padding:3px 9px;font-size:.75em;
    font-family:system-ui,sans-serif;cursor:pointer;opacity:0;
    transition:opacity .15s,background .15s;line-height:1.6}
pre:hover .lc-copy-btn{opacity:1}
.lc-copy-btn:hover{background:#3a3a3a;color:#fff}
.lc-copy-btn.copied{color:#4ec9b0;border-color:#4ec9b0}
.lc-lang-badge{position:absolute;top:8px;left:20px;font-family:system-ui,sans-serif;
    font-size:.7em;color:#666;text-transform:uppercase;letter-spacing:.04em;
    line-height:1;pointer-events:none;opacity:0;transition:opacity .15s}
pre:hover .lc-lang-badge{opacity:1}
table{border-collapse:collapse;width:100%;margin:16px 0}
th,td{border:1px solid #ddd;padding:10px 14px;text-align:left}
th{background:#f0f0f0;font-weight:600}
tr:nth-child(even){background:#fafafa}
</style>
<p><script>
(function(){
  if(window.__lcCodeEnhanced)return;
  window.__lcCodeEnhanced=true;
  function enhance(){
    document.querySelectorAll('pre').forEach(function(pre){
      var code=pre.querySelector('code');
      var lang='';
      if(code){var m=(code.className||'').match(/language-(\S+)/);if(m)lang=m[1].toLowerCase();}
      if(lang)pre.setAttribute('data-lang',lang);
      if(lang){var badge=document.createElement('span');badge.className='lc-lang-badge';badge.textContent=lang;pre.insertBefore(badge,pre.firstChild);}
      var btn=document.createElement('button');
      btn.className='lc-copy-btn';btn.textContent='Copy';btn.setAttribute('aria-label','Copy code to clipboard');
      pre.appendChild(btn);
      btn.addEventListener('click',function(){
        var text=code?code.innerText:pre.innerText;
        if(navigator.clipboard&&window.isSecureContext){
          navigator.clipboard.writeText(text).then(function(){ok(btn);}).catch(function(){fb(text,btn);});
        }else{fb(text,btn);}
      });
    });
  }
  function ok(btn){btn.textContent='Copied!';btn.classList.add('copied');setTimeout(function(){btn.textContent='Copy';btn.classList.remove('copied');},2000);}
  function fb(text,btn){
    try{var ta=document.createElement('textarea');ta.value=text;ta.style.cssText='position:fixed;left:-9999px;top:-9999px;opacity:0';document.body.appendChild(ta);ta.select();document.execCommand('copy');document.body.removeChild(ta);ok(btn);}
    catch(e){btn.textContent='✗ Failed';setTimeout(function(){btn.textContent='Copy';},2000);}
  }
  if(document.readyState==='loading'){document.addEventListener('DOMContentLoaded',enhance);}else{enhance();}
})();
</script></p>
<p><em>eBPF: From Kernel to Cloud, Episode 11</em><br />
<a href="/what-is-ebpf-linux-kubernetes/">What Is eBPF?</a> · <a href="/bpf-verifier-kubernetes-safety/">The BPF Verifier</a> · <a href="/ebpf-vs-kernel-modules-kubernetes/">eBPF vs Kernel Modules</a> · <a href="/ebpf-program-types-kubernetes/">eBPF Program Types</a> · <a href="/ebpf-maps-explained/">eBPF Maps</a> · <a href="/ebpf-co-re-libbpf-portable-programs/">CO-RE and libbpf</a> · <a href="/ebpf-xdp-kubernetes-networking/">XDP</a> · <a href="/tc-ebpf-kubernetes-network-policy/">TC eBPF</a> · <a href="/bpftrace-kernel-observability/">bpftrace</a> · <a href="/ebpf-network-flow-observability/">Network Flow Observability</a> · <strong>DNS Observability</strong></p>
<hr />
<h2 id="tldr">TL;DR</h2>
<ul>
<li>DNS observability in Kubernetes with eBPF hooks the kernel&#8217;s DNS syscall path — giving you per-pod query visibility without sidecars, restarts, or CoreDNS log scraping<br />
  <em>(tracepoint = a stable, versioned hook placed deliberately in the Linux kernel source; unlike kprobes, tracepoints survive kernel upgrades without breakage)</em></li>
<li>CoreDNS metrics tell you aggregate query rates; eBPF tracepoints tell you which pod queried what domain, when, and what was returned</li>
<li>A compromised workload&#8217;s first observable action is almost always an unexpected DNS query — infrastructure no legitimate process should ever resolve</li>
<li>The DNS syscall path in Linux goes: application calls <code class="" data-line="">getaddrinfo()</code> → glibc → <code class="" data-line="">sendto()</code> syscall → kernel network stack → UDP packet to CoreDNS resolver</li>
<li>You hook the <code class="" data-line="">sendto</code> tracepoint to catch the query leaving the pod and the <code class="" data-line="">recvfrom</code> tracepoint to catch the response arriving</li>
<li>Production note: DNS query payloads cross the kernel as raw UDP — parsing the DNS wire format in a bpftrace one-liner requires reading past the UDP header; Tetragon and Pixie do this parsing in the eBPF program itself</li>
</ul>
<hr />
<p>EP10 showed eBPF flow telemetry as the ground truth for what connections your pods are making. DNS observability with eBPF goes one layer beneath that: the name resolution step that happens before any connection is established. Every domain a pod resolves is visible at the kernel level. That visibility is what a security scan alert is missing when it flags &#8220;unexpected DNS queries&#8221; — it can see the traffic on the wire, but it can&#8217;t tell you which pod sent it without restarting or deploying an agent into the pod.</p>
<h2 id="quick-check-what-dns-traffic-is-leaving-your-pods-right-now">Quick Check: What DNS Traffic Is Leaving Your Pods Right Now?</h2>
<p>Without installing anything, you can see DNS queries crossing any node in under 30 seconds:</p>
<pre><code class="" data-line=""># SSH into a worker node, then:

# Watch all UDP port 53 traffic — which processes are making DNS queries?
bpftrace -e &#039;
tracepoint:syscalls:sys_enter_sendto {
    $port = (uint16)((uint8*)args-&gt;addr)[3] &lt;&lt; 8 |
            (uint16)((uint8*)args-&gt;addr)[2];
    if ($port == 53) {
        printf(&quot;%-20s %-6d DNS query (UDP sendto)\n&quot;, comm, pid);
    }
}&#039; --timeout 30
</code></pre>
<p>Expected output:</p>
<pre><code class="" data-line="">coredns              1842   DNS query (UDP sendto)   # ← CoreDNS forwarding upstream
nginx                9231   DNS query (UDP sendto)   # ← nginx resolving upstream
payment-svc          11043  DNS query (UDP sendto)   # ← your service making queries
curl                 14829  DNS query (UDP sendto)   # ← kubectl exec / debug session
</code></pre>
<pre><code class="" data-line=""># How many DNS queries per process in the last 30 seconds?
bpftrace -e &#039;
tracepoint:syscalls:sys_enter_sendto {
    $port = (uint16)((uint8*)args-&gt;addr)[3] &lt;&lt; 8 |
            (uint16)((uint8*)args-&gt;addr)[2];
    if ($port == 53) { @dns_queries[comm] = count(); }
}
interval:s:30 { print(@dns_queries); exit(); }
&#039;
</code></pre>
<p>Expected output:</p>
<pre><code class="" data-line="">@dns_queries[coredns]:       1203   # ← upstream forwarder traffic
@dns_queries[payment-svc]:    847   # ← legitimate service queries
@dns_queries[unknown]:         12   # ← investigate this one
</code></pre>
<blockquote>
<p><strong>On EKS or GKE managed nodes:</strong> You may not be able to SSH directly to worker nodes, but you can run a privileged debug pod: <code class="" data-line="">kubectl debug node/&lt;node-name&gt; -it --image=quay.io/iovisor/bpftrace</code>. The bpftrace program runs on the host kernel and sees all pods&#8217; DNS queries. GKE Autopilot restricts privileged pods — use GKE&#8217;s built-in eBPF-based DNS observability instead (enabled via Cloud Logging with DNS policy logging).</p>
</blockquote>
<hr />
<p>A security scan flagged unexpected DNS queries from <code class="" data-line="">payment-svc</code> in the production namespace. The query domains didn&#8217;t match anything in the service&#8217;s known dependency list. The scan tool showed the traffic on the wire — destination port 53, from the pod&#8217;s IP — but couldn&#8217;t tell us which process inside the pod was responsible or what domain was being queried without pulling the pod&#8217;s DNS logs.</p>
<p>The pod had no DNS logging enabled. CoreDNS showed the queries in its aggregate metrics but with no attribution below namespace level. Restarting the pod to add a DNS sidecar would wipe any in-memory state the process had accumulated.</p>
<p>I ran bpftrace with a <code class="" data-line="">recvfrom</code> hook to catch the DNS response payloads coming back into the pod:</p>
<pre><code class="" data-line="">bpftrace -e &#039;
tracepoint:syscalls:sys_exit_recvfrom {
    if (retval &gt; 0) {
        printf(&quot;%-20s PID %-6d received %d bytes (possible DNS response)\n&quot;,
               comm, pid, retval);
    }
}&#039; --timeout 60
</code></pre>
<p>Then cross-referenced the PIDs to container processes via <code class="" data-line="">/proc/&lt;pid&gt;/cgroup</code>. The unexpected queries were coming from a sidecar process that had been injected by a recent Helm chart change — not from the main application container at all. A misconfigured Datadog agent injected into the wrong namespace was querying its intake endpoint.</p>
<p>No restart. No sidecar deployment. Found in under two minutes.</p>
<hr />
<h2 id="why-coredns-metrics-dont-give-you-this">Why CoreDNS Metrics Don&#8217;t Give You This</h2>
<p>CoreDNS exposes DNS query metrics via Prometheus. Those metrics tell you:<br />
&#8211; Total queries per second across the cluster<br />
&#8211; Query latency histograms<br />
&#8211; Error rates (NXDOMAIN, SERVFAIL)<br />
&#8211; Upstream forwarder health</p>
<p>What they don&#8217;t tell you:<br />
&#8211; Which specific pod sent a query to a specific domain<br />
&#8211; Which process inside that pod made the <code class="" data-line="">getaddrinfo()</code> call<br />
&#8211; Whether the query came from the main container or an injected sidecar<br />
&#8211; The timing relationship between a DNS query and the connection that followed it</p>
<p>CoreDNS sees the query after it arrives at the resolver. eBPF tracepoints see the query at the moment the pod&#8217;s process issues the <code class="" data-line="">sendto()</code> syscall — before it leaves the node. The difference is attribution.</p>
<hr />
<h2 id="the-dns-syscall-path-in-linux">The DNS Syscall Path in Linux</h2>
<p>Understanding where the hook fires helps you reason about what you can observe:</p>
<pre><code class="" data-line="">Application code
    ↓
getaddrinfo(&quot;api.example.com&quot;) ← glibc resolver function
    ↓
glibc reads /etc/resolv.conf → finds nameserver 10.96.0.10 (CoreDNS ClusterIP)
    ↓
glibc builds DNS wire-format query packet
    ↓
sendto(sockfd, buf, len, 0, &amp;resolver_addr, addrlen)
    ↓                     ← eBPF tracepoint fires here: sys_enter_sendto
Linux kernel: udp_sendmsg()
    ↓
Packet leaves pod veth interface
    ↓
TC eBPF on veth sees UDP packet (flow telemetry picks this up too)
    ↓
CoreDNS receives query, resolves, sends response
    ↓
Packet arrives back at pod veth
    ↓
recvfrom(sockfd, buf, len, 0, &amp;src_addr, &amp;src_len)
    ↓                     ← eBPF tracepoint fires here: sys_exit_recvfrom
glibc parses DNS response
    ↓
getaddrinfo() returns IP addresses to application
</code></pre>
<blockquote>
<p><strong><code class="" data-line="">getaddrinfo</code></strong> — the standard POSIX function applications call to resolve a hostname to IP addresses. It lives in glibc, not in the kernel. The kernel never sees the domain name string directly — it only sees the UDP packet carrying the DNS wire-format query. To read the actual domain name in an eBPF program, you parse the DNS packet payload at the <code class="" data-line="">sendto</code> tracepoint.</p>
<p><strong><code class="" data-line="">tracepoint</code></strong> — a stable, versioned hook deliberately placed in Linux kernel source code by kernel developers. Unlike kprobes (which attach to arbitrary kernel functions and break when those functions change), tracepoints are part of the kernel&#8217;s stable interface. The <code class="" data-line="">syscalls:sys_enter_sendto</code> tracepoint has been present and stable since kernel 3.x. You can rely on it across Ubuntu 20.04 through the latest kernels without version checks.</p>
</blockquote>
<hr />
<h2 id="reading-dns-queries-at-the-tracepoint">Reading DNS Queries at the Tracepoint</h2>
<p>The <code class="" data-line="">sendto</code> tracepoint fires when any process sends data on a socket. Filtering to port 53 gives you DNS queries. Parsing the payload gives you the domain name.</p>
<p>The DNS wire format for a query:</p>
<pre><code class="" data-line="">Bytes 0-11:   DNS header (12 bytes)
              - Transaction ID (2 bytes)
              - Flags (2 bytes)
              - QDCount, ANCount, NSCount, ARCount (2 bytes each)
Byte 12+:     Question section
              - QNAME (variable length, label-encoded)
              - QTYPE (2 bytes)
              - QCLASS (2 bytes)
</code></pre>
<p>The QNAME is length-prefixed labels: <code class="" data-line="">\x03api\x07example\x03com\x00</code> for <code class="" data-line="">api.example.com</code>. bpftrace can read the raw bytes but parsing label encoding inline in a one-liner is awkward. For raw query detection (flag any DNS query from a specific process), the tracepoint is enough:</p>
<pre><code class="" data-line=""># Watch DNS queries from a specific process name — replace &quot;payment-svc&quot;
bpftrace -e &#039;
tracepoint:syscalls:sys_enter_sendto /comm == &quot;payment-svc&quot;/ {
    printf(&quot;PID %-6d sending %d bytes to DNS\n&quot;, pid, args-&gt;len);
}
&#039;
</code></pre>
<p>For full domain name extraction, use a tool that implements DNS wire-format parsing in its eBPF layer. Tetragon and Pixie both do this. On a Tetragon-instrumented cluster:</p>
<pre><code class="" data-line=""># Watch DNS queries with domain names — Tetragon (all pods)
kubectl exec -n kube-system -it $(kubectl get pod -n kube-system -l app.kubernetes.io/name=tetragon -o name | head -1) \
  -- tetra getevents --event-types PROCESS_KPROBE \
  | grep -i dns
</code></pre>
<p>Sample Tetragon output:</p>
<pre><code class="" data-line="">{
  &quot;process&quot;: {
    &quot;pod&quot;: {&quot;name&quot;: &quot;payment-svc-7d4b9f-xk2p1&quot;, &quot;namespace&quot;: &quot;production&quot;},
    &quot;binary&quot;: &quot;/usr/bin/payment-service&quot;,
    &quot;pid&quot;: 11043
  },
  &quot;function_name&quot;: &quot;__sys_sendto&quot;,
  &quot;args&quot;: [
    {&quot;sock_arg&quot;: {&quot;family&quot;: &quot;AF_INET&quot;, &quot;protocol&quot;: &quot;UDP&quot;,
                  &quot;daddr&quot;: &quot;10.96.0.10&quot;, &quot;dport&quot;: 53}},
    {&quot;bytes_arg&quot;: &quot;&lt;DNS query for metrics.datadoghq.com&gt;&quot;}
  ]
}
</code></pre>
<p>Pod name, namespace, binary, PID, and the domain being queried — all from a kernel tracepoint, no sidecar, no pod restart.</p>
<hr />
<h2 id="building-pod-level-dns-attribution-without-tetragon">Building Pod-Level DNS Attribution Without Tetragon</h2>
<p>If you&#8217;re not running Tetragon, you can build pod-level attribution from the PID. When bpftrace reports a PID making a DNS query, map it to a container:</p>
<pre><code class="" data-line=""># Get the PID from bpftrace, then:
PID=11043

# Which cgroup does this PID belong to? (maps to container/pod)
cat /proc/$PID/cgroup | grep kubepods
# 12:cpu:/kubepods/burstable/pod3f8a21bc-4e7d-4b91-a3c2-8b947f6e3d12/a4c8f1e2b3d4...
# The pod UID is embedded: pod3f8a21bc-4e7d-4b91-a3c2-8b947f6e3d12

# Map pod UID to pod name
kubectl get pods -A -o jsonpath=&#039;{range .items[*]}{.metadata.uid}{&quot; &quot;}{.metadata.name}{&quot; &quot;}{.metadata.namespace}{&quot;\n&quot;}{end}&#039; \
  | grep 3f8a21bc-4e7d-4b91-a3c2-8b947f6e3d12
# 3f8a21bc-4e7d-4b91-a3c2-8b947f6e3d12  payment-svc-7d4b9f-xk2p1  production
</code></pre>
<p>That&#8217;s the full chain: kernel tracepoint → host PID → cgroup path → pod UID → pod name + namespace. Automatable. No agents required inside the pod.</p>
<hr />
<h2 id="detecting-anomalous-dns-what-to-watch-for">Detecting Anomalous DNS: What to Watch For</h2>
<p>DNS is the first observable action in most attack chains. A process that has been compromised or injected typically cannot establish a C2 connection without first resolving the C2 domain.</p>
<p>Signals worth watching at the kernel DNS layer:</p>
<p><strong>Queries to non-cluster domains from unexpected processes</strong></p>
<pre><code class="" data-line=""># Flag any DNS query to a non-cluster domain (not .cluster.local or .svc.cluster.local)
bpftrace -e &#039;
tracepoint:syscalls:sys_enter_sendto {
    $port = (uint16)((uint8*)args-&gt;addr)[3] &lt;&lt; 8 |
            (uint16)((uint8*)args-&gt;addr)[2];
    if ($port == 53) {
        printf(&quot;%-20s %-6d DNS sendto\n&quot;, comm, pid);
    }
}&#039; --timeout 60
</code></pre>
<p><strong>High-frequency DNS queries from a single process</strong> (DNS tunneling fingerprint)</p>
<pre><code class="" data-line=""># Processes making more than N DNS queries per second
bpftrace -e &#039;
tracepoint:syscalls:sys_enter_sendto {
    $port = (uint16)((uint8*)args-&gt;addr)[3] &lt;&lt; 8 |
            (uint16)((uint8*)args-&gt;addr)[2];
    if ($port == 53) { @[pid, comm] = count(); }
}
interval:s:1 {
    print(@);
    clear(@);
}
&#039;
</code></pre>
<p>DNS tunneling exfiltrates data by encoding it in subdomains of queries. A process making 50+ DNS queries per second to varied subdomains of the same parent domain is a strong signal. CoreDNS aggregate metrics will show elevated query volume; the kernel tracepoint tells you which PID is responsible.</p>
<p><strong>Queries immediately followed by a connection</strong> (normal vs anomalous pattern)</p>
<p>Legitimate services resolve a known set of domains. A process that resolves a new, never-before-seen domain and immediately opens a TCP connection to the returned IP is structurally different from normal service behavior. The combination of DNS tracepoint + TCP connect kprobe lets you correlate these events by PID and timestamp — without any application instrumentation.</p>
<hr />
<h2 id="production-gotchas"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Production Gotchas</h2>
<p><strong>DNS payload parsing is not trivial in bpftrace.</strong> Reading the domain name from the UDP payload requires byte-level parsing of the DNS wire format inside an eBPF program. bpftrace can read raw bytes with <code class="" data-line="">buf()</code>, but the label-encoded domain name format requires a loop that the verifier may reject for complexity reasons. Tools like Tetragon and Pixie implement this parsing in C within their eBPF programs where they have more control over verifier limits. For raw detection (flag DNS queries from unexpected processes), the sendto tracepoint without payload parsing is enough.</p>
<p><strong><code class="" data-line="">sendto</code> fires for all UDP, not just DNS.</strong> Filter on the destination port. The destination address structure is at <code class="" data-line="">args-&gt;addr</code> — port is in network byte order at bytes 2–3 of the <code class="" data-line="">sockaddr_in</code> structure. The filtering in the examples above is correct for port 53; double-check if you&#8217;re on a cluster that uses a non-standard DNS port.</p>
<p><strong>CoreDNS pods will appear in your DNS query trace — that&#8217;s expected.</strong> CoreDNS makes upstream DNS queries to resolve non-cluster domains. Filter on namespace/cgroup if you want to exclude CoreDNS from your trace.</p>
<p><strong>DNS over TCP is a separate code path.</strong> Most DNS queries are UDP. Large responses (&gt;512 bytes) or DNSSEC responses may trigger TCP fallback. The <code class="" data-line="">sendto</code> tracepoint catches UDP; for TCP DNS, you&#8217;d need <code class="" data-line="">tcp_sendmsg</code> with port 53 filtering. In practice, within-cluster DNS resolution is almost entirely UDP.</p>
<p><strong>glibc caching means not every <code class="" data-line="">getaddrinfo()</code> generates a DNS query.</strong> glibc caches resolved hostnames in the process&#8217;s memory. A service that calls <code class="" data-line="">getaddrinfo(&quot;api.example.com&quot;)</code> every 100ms may only generate a DNS query every 30 seconds (the TTL). If you&#8217;re looking for which pods are resolving a domain and see only occasional tracepoint hits, that&#8217;s expected — it&#8217;s the cache miss rate, not the access rate.</p>
<hr />
<h2 id="quick-reference">Quick Reference</h2>
<table>
<thead>
<tr>
<th>What you want</th>
<th>Command</th>
</tr>
</thead>
<tbody>
<tr>
<td>All DNS queries on a node</td>
<td><code class="" data-line="">bpftrace -e &#039;tracepoint:syscalls:sys_enter_sendto { if (port == 53) ... }&#039;</code></td>
</tr>
<tr>
<td>DNS query count per process</td>
<td><code class="" data-line="">bpftrace -e &#039;... { @[comm] = count(); }&#039;</code></td>
</tr>
<tr>
<td>DNS queries from a specific process</td>
<td><code class="" data-line="">bpftrace -e &#039;... /comm == &quot;my-svc&quot;/ { ... }&#039;</code></td>
</tr>
<tr>
<td>Map PID to pod</td>
<td><code class="" data-line="">cat /proc/&lt;pid&gt;/cgroup</code> → extract pod UID → <code class="" data-line="">kubectl get pods</code></td>
</tr>
<tr>
<td>DNS events with domain names (Tetragon)</td>
<td><code class="" data-line="">tetra getevents --event-types PROCESS_KPROBE</code></td>
</tr>
<tr>
<td>DNS policy violations (Cilium)</td>
<td><code class="" data-line="">hubble observe --verdict DROPPED --protocol DNS</code></td>
</tr>
<tr>
<td>CoreDNS query logs</td>
<td><code class="" data-line="">kubectl logs -n kube-system -l k8s-app=kube-dns</code></td>
</tr>
</tbody>
</table>
<table>
<thead>
<tr>
<th>DNS signal</th>
<th>What it indicates</th>
</tr>
</thead>
<tbody>
<tr>
<td>New domain, immediate TCP connect</td>
<td>Possible C2 resolution</td>
</tr>
<tr>
<td>50+ queries/second from one PID</td>
<td>DNS tunneling candidate</td>
</tr>
<tr>
<td>Query to non-cluster domain from batch job</td>
<td>Unusual — investigate</td>
</tr>
<tr>
<td>NXDOMAIN responses at high rate</td>
<td>Misconfiguration or DGA</td>
</tr>
<tr>
<td>Queries from PID not matching any known binary</td>
<td>Injected process</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="key-takeaways">Key Takeaways</h2>
<ul>
<li>DNS observability in Kubernetes with eBPF uses the <code class="" data-line="">sendto</code> tracepoint — the hook fires when the process issues the syscall, before the packet leaves the node, giving you PID-level attribution with no sidecar</li>
<li>CoreDNS metrics show aggregate DNS health; kernel tracepoints show which pod and which process made each query — the attribution gap between the two is where anomaly detection lives</li>
<li>The DNS syscall path goes: <code class="" data-line="">getaddrinfo()</code> → glibc → <code class="" data-line="">sendto()</code> syscall → kernel UDP stack → CoreDNS. eBPF hooks fire at the <code class="" data-line="">sendto()</code> boundary</li>
<li>A compromised workload&#8217;s first observable action is almost always a DNS query; tracepoint-based DNS observability catches it at the kernel level, ahead of any application log</li>
<li>glibc caches resolved names, so tracepoint hit rate reflects cache misses, not <code class="" data-line="">getaddrinfo()</code> call rate — account for this when baselining</li>
<li>Full domain name extraction requires DNS wire-format parsing; Tetragon and Pixie do this in their eBPF programs; bpftrace one-liners detect the query event without the domain string</li>
</ul>
<hr />
<h2 id="whats-next">What&#8217;s Next</h2>
<p>DNS observability tells you what a workload is resolving. EP12 answers what happens when you want to stop a workload from doing something — not detect it after the fact, but prevent it at the syscall boundary before it completes.</p>
<p>LSM hooks and Tetragon&#8217;s kill path enforce at the kernel level. When the kernel enforces, the process never gets the return value from the syscall. There is no &#8220;detect and respond&#8221; window — the action simply does not complete. That is a structurally different security posture from anything a sidecar or userspace agent can provide.</p>
<p><em>Next: <a href="/ebpf-lsm-tetragon-runtime-security/">LSM and Tetragon — when the kernel says no</a></em></p>
<p>Get EP12 in your inbox when it publishes → <a href="https://linuxcent.com/subscribe">linuxcent.com/subscribe</a></p>
<p><a class="a2a_button_mastodon" href="https://www.addtoany.com/add_to/mastodon?linkurl=https%3A%2F%2Flinuxcent.com%2Febpf-dns-observability-kubernetes%2F&amp;linkname=DNS%20at%20the%20Kernel%20Level%20%E2%80%94%20What%20Your%20Pods%20Are%20Actually%20Resolving" title="Mastodon" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_email" href="https://www.addtoany.com/add_to/email?linkurl=https%3A%2F%2Flinuxcent.com%2Febpf-dns-observability-kubernetes%2F&amp;linkname=DNS%20at%20the%20Kernel%20Level%20%E2%80%94%20What%20Your%20Pods%20Are%20Actually%20Resolving" title="Email" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_whatsapp" href="https://www.addtoany.com/add_to/whatsapp?linkurl=https%3A%2F%2Flinuxcent.com%2Febpf-dns-observability-kubernetes%2F&amp;linkname=DNS%20at%20the%20Kernel%20Level%20%E2%80%94%20What%20Your%20Pods%20Are%20Actually%20Resolving" title="WhatsApp" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_reddit" href="https://www.addtoany.com/add_to/reddit?linkurl=https%3A%2F%2Flinuxcent.com%2Febpf-dns-observability-kubernetes%2F&amp;linkname=DNS%20at%20the%20Kernel%20Level%20%E2%80%94%20What%20Your%20Pods%20Are%20Actually%20Resolving" title="Reddit" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_x" href="https://www.addtoany.com/add_to/x?linkurl=https%3A%2F%2Flinuxcent.com%2Febpf-dns-observability-kubernetes%2F&amp;linkname=DNS%20at%20the%20Kernel%20Level%20%E2%80%94%20What%20Your%20Pods%20Are%20Actually%20Resolving" title="X" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_linkedin" href="https://www.addtoany.com/add_to/linkedin?linkurl=https%3A%2F%2Flinuxcent.com%2Febpf-dns-observability-kubernetes%2F&amp;linkname=DNS%20at%20the%20Kernel%20Level%20%E2%80%94%20What%20Your%20Pods%20Are%20Actually%20Resolving" title="LinkedIn" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_copy_link" href="https://www.addtoany.com/add_to/copy_link?linkurl=https%3A%2F%2Flinuxcent.com%2Febpf-dns-observability-kubernetes%2F&amp;linkname=DNS%20at%20the%20Kernel%20Level%20%E2%80%94%20What%20Your%20Pods%20Are%20Actually%20Resolving" title="Copy Link" rel="nofollow noopener" target="_blank"></a><a class="a2a_dd addtoany_share_save addtoany_share" href="https://www.addtoany.com/share#url=https%3A%2F%2Flinuxcent.com%2Febpf-dns-observability-kubernetes%2F&#038;title=DNS%20at%20the%20Kernel%20Level%20%E2%80%94%20What%20Your%20Pods%20Are%20Actually%20Resolving" data-a2a-url="https://linuxcent.com/ebpf-dns-observability-kubernetes/" data-a2a-title="DNS at the Kernel Level — What Your Pods Are Actually Resolving"></a></p><p>The post <a href="https://linuxcent.com/ebpf-dns-observability-kubernetes/">DNS at the Kernel Level — What Your Pods Are Actually Resolving</a> appeared first on <a href="https://linuxcent.com">Linuxcent</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://linuxcent.com/ebpf-dns-observability-kubernetes/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1840</post-id>	</item>
		<item>
		<title>bpftrace — Kernel Answers in One Line</title>
		<link>https://linuxcent.com/bpftrace-kernel-observability/</link>
					<comments>https://linuxcent.com/bpftrace-kernel-observability/#respond</comments>
		
		<dc:creator><![CDATA[Vamshi Krishna Santhapuri]]></dc:creator>
		<pubDate>Sun, 10 May 2026 02:00:00 +0000</pubDate>
				<category><![CDATA[eBPF]]></category>
		<category><![CDATA[bpftrace]]></category>
		<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[Observability]]></category>
		<category><![CDATA[Performance]]></category>
		<category><![CDATA[SRE]]></category>
		<category><![CDATA[Tracing]]></category>
		<guid isPermaLink="false">https://linuxcent.com/?p=1839</guid>

					<description><![CDATA[<p><span class="span-reading-time rt-reading-time" style="display: block;"><span class="rt-label rt-prefix">Reading Time: </span> <span class="rt-time"> 8</span> <span class="rt-label rt-postfix">minutes</span></span>bpftrace gives you kernel observability in a one-liner — trace syscalls, connections, and process spawns on any node with no app changes or restarts.</p>
<p>The post <a href="https://linuxcent.com/bpftrace-kernel-observability/">bpftrace — Kernel Answers in One Line</a> appeared first on <a href="https://linuxcent.com">Linuxcent</a>.</p>
]]></description>
										<content:encoded><![CDATA[<span class="span-reading-time rt-reading-time" style="display: block;"><span class="rt-label rt-prefix">Reading Time: </span> <span class="rt-time"> 8</span> <span class="rt-label rt-postfix">minutes</span></span><style>
pre{position:relative;background:#1e1e1e;color:#d4d4d4;
    padding:16px 16px 16px 20px;border-radius:6px;overflow-x:auto;
    font-family:'JetBrains Mono','Fira Code','Cascadia Code',Consolas,'Courier New',monospace;
    font-size:.88em;line-height:1.6;border-left:4px solid #555}
code{background:#f4f4f4;padding:2px 5px;border-radius:3px;font-size:.9em}
pre code{background:transparent;padding:0;color:inherit}
pre[data-lang="bash"],pre[data-lang="sh"],
pre[data-lang="shell"],pre[data-lang="zsh"]{border-left-color:#4ec9b0}
pre[data-lang="yaml"],pre[data-lang="json"],
pre[data-lang="toml"],pre[data-lang="xml"]{border-left-color:#569cd6}
pre[data-lang="python"],pre[data-lang="go"],pre[data-lang="rust"],
pre[data-lang="java"],pre[data-lang="c"],pre[data-lang="cpp"]{border-left-color:#c586c0}
pre[data-lang="text"],pre[data-lang="output"],
pre[data-lang="console"]{border-left-color:#888}
.lc-copy-btn{position:absolute;top:8px;right:8px;background:#2d2d2d;color:#ccc;
    border:1px solid #444;border-radius:4px;padding:3px 9px;font-size:.75em;
    font-family:system-ui,sans-serif;cursor:pointer;opacity:0;
    transition:opacity .15s,background .15s;line-height:1.6}
pre:hover .lc-copy-btn{opacity:1}
.lc-copy-btn:hover{background:#3a3a3a;color:#fff}
.lc-copy-btn.copied{color:#4ec9b0;border-color:#4ec9b0}
.lc-lang-badge{position:absolute;top:8px;left:20px;font-family:system-ui,sans-serif;
    font-size:.7em;color:#666;text-transform:uppercase;letter-spacing:.04em;
    line-height:1;pointer-events:none;opacity:0;transition:opacity .15s}
pre:hover .lc-lang-badge{opacity:1}
table{border-collapse:collapse;width:100%;margin:16px 0}
th,td{border:1px solid #ddd;padding:10px 14px;text-align:left}
th{background:#f0f0f0;font-weight:600}
tr:nth-child(even){background:#fafafa}
</style>
<p><script>
(function(){
  if(window.__lcCodeEnhanced)return;
  window.__lcCodeEnhanced=true;
  function enhance(){
    document.querySelectorAll('pre').forEach(function(pre){
      var code=pre.querySelector('code');
      var lang='';
      if(code){var m=(code.className||'').match(/language-(\S+)/);if(m)lang=m[1].toLowerCase();}
      if(lang)pre.setAttribute('data-lang',lang);
      if(lang){var badge=document.createElement('span');badge.className='lc-lang-badge';badge.textContent=lang;pre.insertBefore(badge,pre.firstChild);}
      var btn=document.createElement('button');
      btn.className='lc-copy-btn';btn.textContent='Copy';btn.setAttribute('aria-label','Copy code to clipboard');
      pre.appendChild(btn);
      btn.addEventListener('click',function(){
        var text=code?code.innerText:pre.innerText;
        if(navigator.clipboard&&window.isSecureContext){
          navigator.clipboard.writeText(text).then(function(){ok(btn);}).catch(function(){fb(text,btn);});
        }else{fb(text,btn);}
      });
    });
  }
  function ok(btn){btn.textContent='Copied!';btn.classList.add('copied');setTimeout(function(){btn.textContent='Copy';btn.classList.remove('copied');},2000);}
  function fb(text,btn){
    try{var ta=document.createElement('textarea');ta.value=text;ta.style.cssText='position:fixed;left:-9999px;top:-9999px;opacity:0';document.body.appendChild(ta);ta.select();document.execCommand('copy');document.body.removeChild(ta);ok(btn);}
    catch(e){btn.textContent='✗ Failed';setTimeout(function(){btn.textContent='Copy';},2000);}
  }
  if(document.readyState==='loading'){document.addEventListener('DOMContentLoaded',enhance);}else{enhance();}
})();
</script></p>
<p><em>eBPF: From Kernel to Cloud, Episode 9</em><br />
<em><a href="https://linuxcent.com/what-is-ebpf-linux-kubernetes/">What Is eBPF?</a> · <a href="https://linuxcent.com/bpf-verifier-kubernetes-safety/">The BPF Verifier</a> · <a href="https://linuxcent.com/ebpf-vs-kernel-modules-kubernetes/">eBPF vs Kernel Modules</a> · <a href="https://linuxcent.com/ebpf-program-types-kubernetes/">eBPF Program Types</a> · <a href="https://linuxcent.com/ebpf-maps-explained/">eBPF Maps</a> · <a href="https://linuxcent.com/ebpf-co-re-libbpf-portable-programs/">CO-RE and libbpf</a> · <a href="https://linuxcent.com/ebpf-xdp-kubernetes-networking/">XDP</a> · <a href="https://linuxcent.com/tc-ebpf-kubernetes-network-policy/">TC eBPF</a> · </em><em>bpftrace</em>**</p>
<hr />
<h2 id="tldr">TL;DR</h2>
<ul>
<li>bpftrace is an eBPF compiler, not a monitoring agent — every one-liner compiles, loads, runs, and cleans up a complete kernel program<br />
<em>(think of it like <code class="" data-line="">kubectl exec</code> — but for asking the kernel a direct question, with no agent, no sidecar, no prior setup)</em></li>
<li>kretprobe and tracepoint cover most production debugging needs; use tracepoints for stability across kernel versions</li>
<li>The security use cases are unique: kernel-level observation that an attacker inside a container cannot suppress</li>
<li>Every connection, every file open, every process spawn — observable in real time with a single command, no prior instrumentation</li>
<li>Production caution: high-frequency probes on hot paths add overhead; filter by pid/comm, use <code class="" data-line="">--timeout</code>, watch <code class="" data-line="">%si</code></li>
<li>Container PIDs are host-namespace PIDs in bpftrace — use <code class="" data-line="">curtask-&gt;real_parent-&gt;tgid</code> to correlate to container activity</li>
</ul>
<hr />
<p>bpftrace turns any kernel question into a one-liner — compiling, loading, and attaching a complete eBPF program in seconds, with no agents, no restarts, and no prior instrumentation on the node. When something is wrong on a node right now and you don&#8217;t know where to look, it&#8217;s how you ask the kernel a direct question. That&#8217;s what EP09 is about.</p>
<h2 id="quick-check-is-bpftrace-available-on-your-node">Quick Check: Is bpftrace Available on Your Node?</h2>
<p>Before the one-liner toolkit — verify bpftrace is installed and working on a cluster node:</p>
<pre><code class="" data-line=""># SSH into a worker node, then:
bpftrace --version
# bpftrace v0.19.0   ← any version ≥ 0.16 supports the patterns in this episode

# Verify BTF is available (required for struct access one-liners)
ls /sys/kernel/btf/vmlinux &amp;&amp; echo &quot;BTF available&quot;

# The simplest possible one-liner — count syscalls for 5 seconds
bpftrace -e &#039;tracepoint:raw_syscalls:sys_enter { @[comm] = count(); }&#039; --timeout 5
</code></pre>
<p>Expected output (abridged):</p>
<pre><code class="" data-line="">Attaching 1 probe...

@[containerd]: 312
@[kubelet]:    841
@[node_exporter]: 203
@[sshd]:       47
</code></pre>
<p>Each line is a process name and how many syscalls it made in 5 seconds. If this runs and produces output, everything in this episode will work on your node.</p>
<blockquote>
<p><strong>Not on a self-managed node?</strong> EKS managed nodes and GKE nodes don&#8217;t have bpftrace pre-installed, but you can run it from a privileged debug pod: <code class="" data-line="">kubectl debug node/&lt;node-name&gt; -it --image=quay.io/iovisor/bpftrace</code>. The tool runs on the host kernel — you get full kernel visibility even from a pod.</p>
</blockquote>
<hr />
<p>A node in production started showing elevated TCP latency — p99 at 180ms, where p99 was normally under 10ms. The application logs were clean. The APM dashboard showed nothing unusual at the service level. CPU, memory, disk: all normal. The load balancer health checks were passing.</p>
<p>I had 12 minutes before the on-call escalation would have gone to the application team and started a war room.</p>
<p>I ran one command:</p>
<pre><code class="" data-line="">bpftrace -e &#039;kretprobe:tcp_recvmsg { @bytes[comm] = hist(retval); }&#039; --timeout 10
</code></pre>
<p>Ten seconds of sampling. The histogram output showed a single process — <code class="" data-line="">backup-agent</code> — receiving 4MB chunks at irregular intervals. Not the application. Not the service mesh. A backup agent that runs at the infrastructure layer, saturating the receive path with large reads during its scheduled window.</p>
<p>Found in 9 seconds. War room averted.</p>
<p>What made that possible is something most engineers don&#8217;t know about bpftrace: that one-liner is not a monitoring query. It&#8217;s a complete eBPF program — compiled, loaded into the kernel, attached to the <code class="" data-line="">tcp_recvmsg</code> kernel return probe, run, and cleaned up — all in ten seconds. bpftrace is a compiler that happens to have a very convenient command-line interface.</p>
<hr />
<h2 id="what-bpftrace-actually-is">What bpftrace Actually Is</h2>
<p>bpftrace is not a monitoring tool. It&#8217;s an eBPF compiler with a high-level scripting language designed for one-shot investigation.</p>
<p>When you run <code class="" data-line="">bpftrace -e &#039;kretprobe:tcp_recvmsg { ... }&#039;</code>, this is what happens:</p>
<pre><code class="" data-line="">Your one-liner
      ↓
bpftrace&#039;s built-in LLVM/Clang frontend
      ↓
eBPF bytecode (.bpf.o in memory)
      ↓
Kernel verifier validates the program
      ↓
JIT compiler compiles to native machine code
      ↓
Program attaches to tcp_recvmsg kretprobe
      ↓
Runs until Ctrl-C or --timeout
      ↓
Output printed, maps freed, program detached
</code></pre>
<p>The kernel doesn&#8217;t know bpftrace wrote the program. It&#8217;s the same path as Falco, Cilium, Tetragon — kernel program loaded via the BPF syscall, verified, JIT-compiled, attached to a probe. bpftrace just wraps that entire process in a scripting language that takes 30 seconds to write instead of an afternoon.</p>
<p>This is why bpftrace can answer questions that no other tool can: it compiles to a kernel-level observer that fires on any event in the kernel, on any process, on any container — without any prior instrumentation.</p>
<hr />
<h2 id="the-four-probe-types-youll-use-most">The Four Probe Types You&#8217;ll Use Most</h2>
<p>bpftrace supports 20+ probe types. These four cover 90% of production debugging:</p>
<h3 id="kprobe-kretprobe-kernel-functions">kprobe / kretprobe — Kernel Functions</h3>
<p>Attaches to the entry (<code class="" data-line="">kprobe</code>) or return (<code class="" data-line="">kretprobe</code>) of any kernel function. The most powerful probes for understanding what the kernel is actually doing.</p>
<pre><code class="" data-line=""># Fire on every call to tcp_connect — who&#039;s making new TCP connections?
bpftrace -e &#039;kprobe:tcp_connect { printf(&quot;%s PID %d connecting\n&quot;, comm, pid); }&#039;

# On return from tcp_recvmsg — how large are the reads per process?
bpftrace -e &#039;kretprobe:tcp_recvmsg { @[comm] = hist(retval); }&#039;

# Count calls to vfs_write per process (file write activity)
bpftrace -e &#039;kprobe:vfs_write { @[comm] = count(); }&#039;
</code></pre>
<p>Limitation: kernel functions are internal and can change between kernel versions. Use tracepoints (below) for stability when you can.</p>
<blockquote>
<p><strong>kprobe instability:</strong> A function targeted by a kprobe can be <em>inlined</em> by the kernel compiler — the compiler embeds the function&#8217;s code at its call sites with no separate entry point. When that happens, the kprobe silently fires on nothing. Verify before relying on one: <code class="" data-line="">bpftrace -l &#039;kprobe:function_name&#039;</code> — empty response means it was inlined. Use a tracepoint equivalent instead.</p>
</blockquote>
<h3 id="tracepoint-stable-kernel-trace-points">tracepoint — Stable Kernel Trace Points</h3>
<p>Tracepoints are stable, versioned hooks explicitly placed in the kernel source. Unlike kprobes, they are part of the kernel&#8217;s public interface and guaranteed not to disappear between versions. Use these for anything you need to work reliably across a fleet with mixed kernel versions.</p>
<pre><code class="" data-line=""># Every file open — process name + filename
bpftrace -e &#039;tracepoint:syscalls:sys_enter_openat {
    printf(&quot;%s %s\n&quot;, comm, str(args-&gt;filename));
}&#039;

# Every outbound connect — process, destination IP and port
bpftrace -e &#039;tracepoint:syscalls:sys_enter_connect {
    printf(&quot;%-16s %-6d\n&quot;, comm, pid);
}&#039;

# List all available tracepoints (hundreds)
bpftrace -l &#039;tracepoint:syscalls:*&#039; | head -30
</code></pre>
<h3 id="uprobe-userspace-function-probes">uprobe — Userspace Function Probes</h3>
<p>Attaches to a specific function in a userspace binary or library. Useful for observing application behaviour without recompiling.</p>
<pre><code class="" data-line=""># What bash commands are being typed on this node?
bpftrace -e &#039;uprobe:/bin/bash:readline { printf(&quot;%s\n&quot;, str(arg0)); }&#039;

# Python function calls
bpftrace -e &#039;uprobe:/usr/bin/python3:PyObject_Call { printf(&quot;Python call: pid %d\n&quot;, pid); }&#039;
</code></pre>
<p>From a security standpoint: this is how you observe what an attacker is typing in an interactive shell they&#8217;ve obtained on your node — in real time, from the kernel, without touching the terminal session.</p>
<h3 id="interval-periodic-sampling">interval — Periodic Sampling</h3>
<p>Runs a block of code on a fixed interval. Used for aggregation and periodic stats.</p>
<pre><code class="" data-line=""># Print the top file-opening processes every 5 seconds
bpftrace -e &#039;
kprobe:vfs_open { @[comm] = count(); }
interval:s:5  { print(@); clear(@); }
&#039;
</code></pre>
<hr />
<h2 id="the-one-liner-toolkit-runnable-right-now">The One-Liner Toolkit: Runnable Right Now</h2>
<p>These run on any Linux node with BTF (kernel 5.8+, Ubuntu 20.04+, most managed K8s nodes):</p>
<pre><code class="" data-line=""># What files is every process opening right now? (30-second view)
bpftrace -e &#039;tracepoint:syscalls:sys_enter_openat {
    printf(&quot;%-16s %s\n&quot;, comm, str(args-&gt;filename));
}&#039; --timeout 30

# Who is making DNS queries? (catches queries from any container, no sidecar needed)
bpftrace -e &#039;tracepoint:net:net_dev_xmit {
    if (args-&gt;skbaddr-&gt;protocol == 0x0800) printf(&quot;%s\n&quot;, comm);
}&#039;

# Latency histogram for all read() syscalls — find the slow process
bpftrace -e &#039;
tracepoint:syscalls:sys_enter_read { @start[tid] = nsecs; }
tracepoint:syscalls:sys_exit_read  {
    $latency = nsecs - @start[tid];
    @latency[comm] = hist($latency);
    delete(@start[tid]);
}&#039; --timeout 15

# Which process is using the most CPU right now? (99Hz sampling)
bpftrace -e &#039;profile:hz:99 { @[comm] = count(); }&#039; --timeout 10

# Real-time syscall frequency — find unusual process activity
bpftrace -e &#039;tracepoint:raw_syscalls:sys_enter { @[comm, args-&gt;id] = count(); }&#039; --timeout 10 \
  | sort -k3 -rn | head -20

# New TCP connections in the last 30 seconds — source and dest
bpftrace -e &#039;kprobe:tcp_connect {
    $sk = (struct sock *)arg0;
    printf(&quot;%-16s → %s:%d\n&quot;, comm,
           ntop(AF_INET, $sk-&gt;__sk_common.skc_daddr),
           $sk-&gt;__sk_common.skc_dport &gt;&gt; 8);
}&#039; --timeout 30

# What is a specific PID doing? (replace 12345)
bpftrace -e &#039;tracepoint:syscalls:sys_enter_openat /pid == 12345/ {
    printf(&quot;%s\n&quot;, str(args-&gt;filename));
}&#039;
</code></pre>
<p>Each of these compiles and loads in under 2 seconds. They leave no persistent state. When they exit, the kernel reverts to exactly the state it was in before.</p>
<hr />
<h2 id="the-security-use-cases">The Security Use Cases</h2>
<h3 id="watching-an-active-session">Watching an Active Session</h3>
<p>If you suspect a process is running commands you didn&#8217;t deploy:</p>
<pre><code class="" data-line=""># See every bash command on this node in real time
bpftrace -e &#039;uprobe:/bin/bash:readline { printf(&quot;%s %s\n&quot;, comm, str(arg0)); }&#039;

# Every process spawn — PID, parent, command
bpftrace -e &#039;tracepoint:syscalls:sys_enter_execve {
    printf(&quot;%-6d %-6d %s\n&quot;, pid, curtask-&gt;real_parent-&gt;tgid, str(args-&gt;filename));
}&#039;
</code></pre>
<p>This is the kernel-level version of watching <code class="" data-line="">/var/log/auth.log</code> — except it can&#8217;t be suppressed by an attacker who has root, because the probe runs in kernel space. An attacker who has compromised a container with root inside the container cannot prevent a bpftrace program on the host from observing their syscalls.</p>
<h3 id="detecting-unexpected-network-activity">Detecting Unexpected Network Activity</h3>
<pre><code class="" data-line=""># Any process making a connection to a non-standard port
bpftrace -e &#039;kprobe:tcp_connect {
    $sk = (struct sock *)arg0;
    $port = $sk-&gt;__sk_common.skc_dport &gt;&gt; 8;
    if ($port != 80 &amp;&amp; $port != 443 &amp;&amp; $port != 53) {
        printf(&quot;%-16s port %d\n&quot;, comm, $port);
    }
}&#039;

# DNS queries to non-standard resolvers (anything not on port 53)
bpftrace -e &#039;tracepoint:syscalls:sys_enter_sendto {
    if (args-&gt;addr-&gt;sa_family == 2) {
        printf(&quot;%-16s → %s\n&quot;, comm, str(args-&gt;addr));
    }
}&#039;
</code></pre>
<h3 id="watching-file-access-on-sensitive-paths">Watching File Access on Sensitive Paths</h3>
<pre><code class="" data-line=""># Any access to /etc/passwd, /etc/shadow, /root/
bpftrace -e &#039;tracepoint:syscalls:sys_enter_openat {
    if (str(args-&gt;filename) == &quot;/etc/passwd&quot; ||
        str(args-&gt;filename) == &quot;/etc/shadow&quot;) {
        printf(&quot;%-16s PID %-6d opened %s\n&quot;, comm, pid, str(args-&gt;filename));
    }
}&#039;
</code></pre>
<hr />
<h2 id="production-gotchas">Production Gotchas</h2>
<p><strong>CPU overhead:</strong> bpftrace probes fire synchronously in the traced context. High-frequency probes on hot kernel paths (<code class="" data-line="">vfs_read</code>, <code class="" data-line="">sys_enter_*</code> without filtering) can add 10–20% overhead. Always test with <code class="" data-line="">--timeout</code> and watch <code class="" data-line="">%si</code> before running on a production node.</p>
<p><strong>Maps grow unbounded by default:</strong> <code class="" data-line="">@[comm] = count()</code> will accumulate an entry per unique <code class="" data-line="">comm</code> value forever in the current session. Use <code class="" data-line="">clear(@)</code> in an interval block, or set a key limit: <code class="" data-line="">@[comm] = count(); if (@[comm] &gt; 100) { clear(@comm); }</code>.</p>
<p><strong>kprobe instability:</strong> Functions targeted by kprobes can be inlined by the compiler between kernel versions, making the probe silently ineffective. If a kprobe isn&#8217;t firing, verify the function exists: <code class="" data-line="">bpftrace -l &#039;kprobe:function_name&#039;</code>. If it returns nothing, the function was inlined. Use a tracepoint equivalent instead.</p>
<p><strong>Container PIDs:</strong> PIDs inside a container are different from host PIDs. <code class="" data-line="">pid</code> in bpftrace is the host namespace PID.</p>
<blockquote>
<p><strong>Container PID semantics:</strong> When a container shows PID 1 internally, the host kernel sees it as PID 8432 (or whatever was assigned). bpftrace&#8217;s <code class="" data-line="">pid</code> built-in always gives you the <em>host-namespace</em> PID. To map a container&#8217;s PID to the host PID: <code class="" data-line="">cat /proc/&lt;host-pid&gt;/status | grep NSpid</code> — the second value is the PID inside the container. Or use <code class="" data-line="">curtask-&gt;real_parent-&gt;tgid</code> in your probe to walk the process tree. This matters when you filter by <code class="" data-line="">pid</code> in a one-liner and get no output — you may be filtering on the container-namespace PID instead of the host one.</p>
</blockquote>
<p><strong>BTF requirement:</strong> bpftrace requires BTF for struct field access (<code class="" data-line="">$sk-&gt;__sk_common.skc_daddr</code>). If BTF is unavailable, struct access fails. Check <code class="" data-line="">/sys/kernel/btf/vmlinux</code> exists before running struct-access one-liners.</p>
<hr />
<h2 id="quick-reference">Quick Reference</h2>
<table>
<thead>
<tr>
<th>Probe type</th>
<th>Syntax</th>
<th>Use for</th>
</tr>
</thead>
<tbody>
<tr>
<td>kernel function entry</td>
<td><code class="" data-line="">kprobe:function_name</code></td>
<td>Function arguments</td>
</tr>
<tr>
<td>kernel function return</td>
<td><code class="" data-line="">kretprobe:function_name</code></td>
<td>Return value, latency</td>
</tr>
<tr>
<td>kernel tracepoint</td>
<td><code class="" data-line="">tracepoint:subsys:name</code></td>
<td>Stable, versioned hooks</td>
</tr>
<tr>
<td>userspace function</td>
<td><code class="" data-line="">uprobe:/path/to/bin:function</code></td>
<td>App-level observation</td>
</tr>
<tr>
<td>CPU sampling</td>
<td><code class="" data-line="">profile:hz:99</code></td>
<td>Flamegraphs, hot code</td>
</tr>
<tr>
<td>interval</td>
<td><code class="" data-line="">interval:s:N</code></td>
<td>Periodic aggregation</td>
</tr>
<tr>
<td>process start</td>
<td><code class="" data-line="">tracepoint:syscalls:sys_enter_execve</code></td>
<td>New process detection</td>
</tr>
</tbody>
</table>
<table>
<thead>
<tr>
<th>Built-in variable</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<tr>
<td><code class="" data-line="">pid</code></td>
<td>Process ID (host namespace)</td>
</tr>
<tr>
<td><code class="" data-line="">tid</code></td>
<td>Thread ID</td>
</tr>
<tr>
<td><code class="" data-line="">comm</code></td>
<td>Process name (15 chars)</td>
</tr>
<tr>
<td><code class="" data-line="">nsecs</code></td>
<td>Nanoseconds since boot</td>
</tr>
<tr>
<td><code class="" data-line="">curtask</code></td>
<td>Pointer to <code class="" data-line="">task_struct</code></td>
</tr>
<tr>
<td><code class="" data-line="">retval</code></td>
<td>Return value (kretprobe/tracepoint exit)</td>
</tr>
<tr>
<td><code class="" data-line="">args</code></td>
<td>Probe arguments struct</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="key-takeaways">Key Takeaways</h2>
<ul>
<li>bpftrace is an eBPF compiler, not a monitoring agent — every one-liner compiles, loads, runs, and cleans up a complete kernel program</li>
<li>kretprobe and tracepoint cover most production debugging needs; use tracepoints for stability across kernel versions</li>
<li>The security use cases are unique: kernel-level observation that an attacker inside a container cannot suppress, because the probe runs on the host in kernel space</li>
<li>Every connection, every file open, every process spawn — observable in real time with a single command, no prior instrumentation</li>
<li>Production caution: high-frequency probes on hot paths add overhead; filter by pid/comm, use <code class="" data-line="">--timeout</code>, watch <code class="" data-line="">%si</code></li>
</ul>
<hr />
<h2 id="whats-next">What&#8217;s Next</h2>
<p>bpftrace answers questions you ask in the moment. EP10 covers what happens when you need those answers continuously — not as a one-shot investigation tool, but as persistent telemetry recording every network connection across your entire cluster.</p>
<p>Flow observability from TC hooks is the always-on version: a persistent eBPF program recording every connection attempt, every retransmit, every dropped packet — the ground truth layer that everything above it interprets. When your APM says &#8220;timeout&#8221; and the kernel says &#8220;retransmit storm to one specific endpoint,&#8221; the kernel is right.</p>
<p><em>Next: <a href="/ebpf-network-flow-observability/">network flow observability at the kernel level</a></em></p>
<p>Get EP10 in your inbox when it publishes → <a href="https://linuxcent.com/subscribe">linuxcent.com/subscribe</a></p>
<p><a class="a2a_button_mastodon" href="https://www.addtoany.com/add_to/mastodon?linkurl=https%3A%2F%2Flinuxcent.com%2Fbpftrace-kernel-observability%2F&amp;linkname=bpftrace%20%E2%80%94%20Kernel%20Answers%20in%20One%20Line" title="Mastodon" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_email" href="https://www.addtoany.com/add_to/email?linkurl=https%3A%2F%2Flinuxcent.com%2Fbpftrace-kernel-observability%2F&amp;linkname=bpftrace%20%E2%80%94%20Kernel%20Answers%20in%20One%20Line" title="Email" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_whatsapp" href="https://www.addtoany.com/add_to/whatsapp?linkurl=https%3A%2F%2Flinuxcent.com%2Fbpftrace-kernel-observability%2F&amp;linkname=bpftrace%20%E2%80%94%20Kernel%20Answers%20in%20One%20Line" title="WhatsApp" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_reddit" href="https://www.addtoany.com/add_to/reddit?linkurl=https%3A%2F%2Flinuxcent.com%2Fbpftrace-kernel-observability%2F&amp;linkname=bpftrace%20%E2%80%94%20Kernel%20Answers%20in%20One%20Line" title="Reddit" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_x" href="https://www.addtoany.com/add_to/x?linkurl=https%3A%2F%2Flinuxcent.com%2Fbpftrace-kernel-observability%2F&amp;linkname=bpftrace%20%E2%80%94%20Kernel%20Answers%20in%20One%20Line" title="X" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_linkedin" href="https://www.addtoany.com/add_to/linkedin?linkurl=https%3A%2F%2Flinuxcent.com%2Fbpftrace-kernel-observability%2F&amp;linkname=bpftrace%20%E2%80%94%20Kernel%20Answers%20in%20One%20Line" title="LinkedIn" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_copy_link" href="https://www.addtoany.com/add_to/copy_link?linkurl=https%3A%2F%2Flinuxcent.com%2Fbpftrace-kernel-observability%2F&amp;linkname=bpftrace%20%E2%80%94%20Kernel%20Answers%20in%20One%20Line" title="Copy Link" rel="nofollow noopener" target="_blank"></a><a class="a2a_dd addtoany_share_save addtoany_share" href="https://www.addtoany.com/share#url=https%3A%2F%2Flinuxcent.com%2Fbpftrace-kernel-observability%2F&#038;title=bpftrace%20%E2%80%94%20Kernel%20Answers%20in%20One%20Line" data-a2a-url="https://linuxcent.com/bpftrace-kernel-observability/" data-a2a-title="bpftrace — Kernel Answers in One Line"></a></p><p>The post <a href="https://linuxcent.com/bpftrace-kernel-observability/">bpftrace — Kernel Answers in One Line</a> appeared first on <a href="https://linuxcent.com">Linuxcent</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://linuxcent.com/bpftrace-kernel-observability/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1839</post-id>	</item>
		<item>
		<title>eBPF Maps — The Persistent Data Layer Between Kernel and Userspace</title>
		<link>https://linuxcent.com/ebpf-maps-explained/</link>
					<comments>https://linuxcent.com/ebpf-maps-explained/#respond</comments>
		
		<dc:creator><![CDATA[Vamshi Krishna Santhapuri]]></dc:creator>
		<pubDate>Thu, 16 Apr 2026 17:57:45 +0000</pubDate>
				<category><![CDATA[eBPF]]></category>
		<category><![CDATA[bpftool]]></category>
		<category><![CDATA[Cilium]]></category>
		<category><![CDATA[eBPF maps]]></category>
		<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[Observability]]></category>
		<category><![CDATA[SRE]]></category>
		<guid isPermaLink="false">https://linuxcent.com/ebpf-maps-explained/</guid>

					<description><![CDATA[<p><span class="span-reading-time rt-reading-time" style="display: block;"><span class="rt-label rt-prefix">Reading Time: </span> <span class="rt-time"> 11</span> <span class="rt-label rt-postfix">minutes</span></span>eBPF maps are the persistent data layer between kernel and userspace — hash maps, ring buffers, and LRU maps explained for SREs running Cilium and Falco.</p>
<p>The post <a href="https://linuxcent.com/ebpf-maps-explained/">eBPF Maps — The Persistent Data Layer Between Kernel and Userspace</a> appeared first on <a href="https://linuxcent.com">Linuxcent</a>.</p>
]]></description>
										<content:encoded><![CDATA[<span class="span-reading-time rt-reading-time" style="display: block;"><span class="rt-label rt-prefix">Reading Time: </span> <span class="rt-time"> 11</span> <span class="rt-label rt-postfix">minutes</span></span><style>
pre{position:relative;background:#1e1e1e;color:#d4d4d4;
    padding:16px 16px 16px 20px;border-radius:6px;overflow-x:auto;
    font-family:'JetBrains Mono','Fira Code','Cascadia Code',Consolas,'Courier New',monospace;
    font-size:.88em;line-height:1.6;border-left:4px solid #555}
code{background:#f4f4f4;padding:2px 5px;border-radius:3px;font-size:.9em}
pre code{background:transparent;padding:0;color:inherit}
pre[data-lang="bash"],pre[data-lang="sh"],
pre[data-lang="shell"],pre[data-lang="zsh"]{border-left-color:#4ec9b0}
pre[data-lang="yaml"],pre[data-lang="json"],
pre[data-lang="toml"],pre[data-lang="xml"]{border-left-color:#569cd6}
pre[data-lang="python"],pre[data-lang="go"],pre[data-lang="rust"],
pre[data-lang="java"],pre[data-lang="c"],pre[data-lang="cpp"]{border-left-color:#c586c0}
pre[data-lang="text"],pre[data-lang="output"],
pre[data-lang="console"]{border-left-color:#888}
.lc-copy-btn{position:absolute;top:8px;right:8px;background:#2d2d2d;color:#ccc;
    border:1px solid #444;border-radius:4px;padding:3px 9px;font-size:.75em;
    font-family:system-ui,sans-serif;cursor:pointer;opacity:0;
    transition:opacity .15s,background .15s;line-height:1.6}
pre:hover .lc-copy-btn{opacity:1}
.lc-copy-btn:hover{background:#3a3a3a;color:#fff}
.lc-copy-btn.copied{color:#4ec9b0;border-color:#4ec9b0}
.lc-lang-badge{position:absolute;top:8px;left:20px;font-family:system-ui,sans-serif;
    font-size:.7em;color:#666;text-transform:uppercase;letter-spacing:.04em;
    line-height:1;pointer-events:none;opacity:0;transition:opacity .15s}
pre:hover .lc-lang-badge{opacity:1}
table{border-collapse:collapse;width:100%;margin:16px 0}
th,td{border:1px solid #ddd;padding:10px 14px;text-align:left}
th{background:#f0f0f0;font-weight:600}
tr:nth-child(even){background:#fafafa}
</style>
<p><script>
(function(){
  if(window.__lcCodeEnhanced)return;
  window.__lcCodeEnhanced=true;
  function enhance(){
    document.querySelectorAll('pre').forEach(function(pre){
      var code=pre.querySelector('code');
      var lang='';
      if(code){var m=(code.className||'').match(/language-(\S+)/);if(m)lang=m[1].toLowerCase();}
      if(lang)pre.setAttribute('data-lang',lang);
      if(lang){var badge=document.createElement('span');badge.className='lc-lang-badge';badge.textContent=lang;pre.insertBefore(badge,pre.firstChild);}
      var btn=document.createElement('button');
      btn.className='lc-copy-btn';btn.textContent='Copy';btn.setAttribute('aria-label','Copy code to clipboard');
      pre.appendChild(btn);
      btn.addEventListener('click',function(){
        var text=code?code.innerText:pre.innerText;
        if(navigator.clipboard&&window.isSecureContext){
          navigator.clipboard.writeText(text).then(function(){ok(btn);}).catch(function(){fb(text,btn);});
        }else{fb(text,btn);}
      });
    });
  }
  function ok(btn){btn.textContent='Copied!';btn.classList.add('copied');setTimeout(function(){btn.textContent='Copy';btn.classList.remove('copied');},2000);}
  function fb(text,btn){
    try{var ta=document.createElement('textarea');ta.value=text;ta.style.cssText='position:fixed;left:-9999px;top:-9999px;opacity:0';document.body.appendChild(ta);ta.select();document.execCommand('copy');document.body.removeChild(ta);ok(btn);}
    catch(e){btn.textContent='✗ Failed';setTimeout(function(){btn.textContent='Copy';},2000);}
  }
  if(document.readyState==='loading'){document.addEventListener('DOMContentLoaded',enhance);}else{enhance();}
})();
</script></p>
<p><em>eBPF: From Kernel to Cloud, Episode 5</em><br />
<em><a href="https://linuxcent.com/what-is-ebpf-linux-kubernetes/">What Is eBPF?</a> · <a href="https://linuxcent.com/bpf-verifier-kubernetes-safety/">The BPF Verifier</a> · <a href="https://linuxcent.com/ebpf-vs-kernel-modules-kubernetes/">eBPF vs Kernel Modules</a> · <a href="https://linuxcent.com/ebpf-program-types-kubernetes/">eBPF Program Types</a> · </em><em>eBPF Maps</em>**</p>
<hr />
<p style="font-size:0.72em;font-weight:700;letter-spacing:0.12em;color:#f59e0b;text-transform:uppercase;margin:2em 0 0.75em 0;text-align:center;">Architecture Overview</p>
<figure class="wp-block-image size-full" style="margin:0 0 0.5em 0;">
<img fetchpriority="high" decoding="async" width="1190" height="2560" src="https://linuxcent.com/wp-content/uploads/2026/05/ep05-ebpf-maps-og-2-scaled.png" alt="eBPF Maps — the persistent data layer between kernel eBPF programs and userspace tools" class="wp-image-2112" style="width:100%;height:auto;display:block;border-radius:8px;" srcset="https://linuxcent.com/wp-content/uploads/2026/05/ep05-ebpf-maps-og-2-scaled.png 1190w, https://linuxcent.com/wp-content/uploads/2026/05/ep05-ebpf-maps-og-2-139x300.png 139w, https://linuxcent.com/wp-content/uploads/2026/05/ep05-ebpf-maps-og-2-476x1024.png 476w, https://linuxcent.com/wp-content/uploads/2026/05/ep05-ebpf-maps-og-2-768x1652.png 768w, https://linuxcent.com/wp-content/uploads/2026/05/ep05-ebpf-maps-og-2-714x1536.png 714w, https://linuxcent.com/wp-content/uploads/2026/05/ep05-ebpf-maps-og-2-952x2048.png 952w" sizes="(max-width: 1190px) 100vw, 1190px" /><figcaption style="text-align:center;font-size:0.85em;color:#6b7280;margin-top:0.75em;">eBPF maps are the shared memory between kernel programs and userspace — hash, array, ringbuf, and LRU variants shown.</figcaption></figure>
<hr style="border:none;border-top:1px solid #e5e7eb;margin:0.5em 0 2em 0;"/>
<h2 id="tldr">TL;DR</h2>
<ul>
<li>eBPF programs are stateless — maps are where all state lives, between invocations and between kernel and userspace<br />
<em>(&#8220;stateless&#8221; here means each program invocation starts with no memory of previous runs — like a function with no global variables)</em></li>
<li>Every production eBPF tool (Cilium, Falco, Tetragon, Datadog NPM) is a map-based architecture — <code class="" data-line="">bpftool map list</code> shows you what it&#8217;s actually holding</li>
<li>Per-CPU maps eliminate write contention for high-frequency counters; the tool aggregates per-CPU values at export time</li>
<li>LRU maps handle unbounded key spaces (IPs, PIDs, connections) without hard errors when full — but eviction is silent, so size generously</li>
<li>Ring buffer (kernel 5.8+) is the correct event streaming primitive — Falco and Tetragon both use it</li>
<li>Map memory is kernel-locked and invisible to standard memory metrics — account for it explicitly on eBPF-heavy nodes</li>
<li>Pinned maps survive restarts; Cilium uses this for zero-disruption connection tracking through upgrades</li>
</ul>
<hr />
<h2 id="the-big-picture">The Big Picture</h2>
<pre><code class="" data-line="">  HOW eBPF MAPS CONNECT KERNEL PROGRAMS TO USERSPACE TOOLS

  ┌─────────────────────────────────────────────────────────────┐
  │  Kernel space                                               │
  │                                                             │
  │  [XDP program]  [TC program]  [kprobe]  [tracepoint]        │
  │        │              │           │           │             │
  │        └──────────────┴───────────┴───────────┘             │
  │                              │                              │
  │                   bpf_map_update_elem()                     │
  │                              │                              │
  │                              ▼                              │
  │  ┌─────────────────────────────────────────────────────┐    │
  │  │             eBPF MAP (kernel object)                │    │
  │  │  hash · percpu_hash · lru_hash · ringbuf · lpm_trie │    │
  │  │  Lives outside program invocations.                 │    │
  │  │  Pinned maps (/sys/fs/bpf/) survive restarts.       │    │
  │  └────────────────────┬────────────────────────────────┘    │
  └───────────────────────│─────────────────────────────────────┘
                          │  read / write via file descriptor
                          ▼
  ┌─────────────────────────────────────────────────────────────┐
  │  Userspace tools                                            │
  │                                                             │
  │  Cilium agent  Falco engine  Tetragon  bpftool map dump     │
  └─────────────────────────────────────────────────────────────┘
</code></pre>
<hr />
<p>eBPF maps are the persistent data layer between kernel programs and the tools that consume their output. eBPF programs fire and exit — there&#8217;s no memory between invocations. Yet Cilium tracks TCP connections across millions of packets, and Falco correlates a process exec from five minutes ago with a suspicious network connection happening now. The mechanism between stateless kernel programs and the stateful production tools you depend on is what this episode is about — and understanding it changes what you see when you run <code class="" data-line="">bpftool map list</code>.</p>
<hr />
<p>I was trying to identify the noisy neighbor saturating a cluster&#8217;s egress link. I had an eBPF program loading cleanly, events firing, everything confirming it was working. But when I read back the per-port connection counters from userspace, everything was zero.</p>
<p>I spent an hour on it before posting to the BCC mailing list. The reply came back fast: eBPF programs don&#8217;t hold state between invocations. Every time the kprobe fires, the program starts fresh. The counter I was incrementing existed only for that single call — created, incremented to one, then discarded. On every single invocation. I was counting events one at a time, throwing the count away, and reading nothing.</p>
<p>That&#8217;s what eBPF maps solve.</p>
<h2 id="quick-check-what-maps-are-running-on-your-node">Quick Check: What Maps Are Running on Your Node?</h2>
<p>Before the map types walkthrough — see the live state of maps on any cluster node right now:</p>
<pre><code class="" data-line=""># SSH into a worker node, then:
bpftool map list
</code></pre>
<p>On a node running Cilium + Falco, you&#8217;ll see something like:</p>
<pre><code class="" data-line="">12: hash          name cilium_ct4_glo    key 24B  value 56B  max_entries 65536  memlock 5767168B
13: lpm_trie      name cilium_ipcache    key 40B  value 32B  max_entries 512000 memlock 327680B
14: percpu_hash   name cilium_metrics    key 8B   value 32B  max_entries 65536  memlock 2097152B
28: ringbuf       name falco_events      max_entries 8388608
</code></pre>
<p>Reading this output:<br />
&#8211; <code class="" data-line="">hash</code>, <code class="" data-line="">lpm_trie</code>, <code class="" data-line="">percpu_hash</code>, <code class="" data-line="">ringbuf</code> — the map <em>type</em> (each optimised for a different access pattern)<br />
&#8211; <code class="" data-line="">key 24B value 56B</code> — sizes of a single entry&#8217;s key and value in bytes<br />
&#8211; <code class="" data-line="">max_entries</code> — the hard ceiling; when the map is full, behaviour depends on type (see LRU section below)<br />
&#8211; <code class="" data-line="">memlock</code> — non-pageable kernel memory this map consumes (invisible to <code class="" data-line="">free</code> and container metrics)</p>
<blockquote>
<p><strong>Not running Cilium?</strong> On EKS with <code class="" data-line="">aws-vpc-cni</code> or GKE with <code class="" data-line="">kubenet</code>, there are far fewer maps here — primarily kube-proxy uses iptables rather than BPF maps. Running <code class="" data-line="">bpftool map list</code> still works; you&#8217;ll just see fewer entries. On a pure iptables-based cluster, most of the maps you see come from the system kernel itself, not a CNI.</p>
</blockquote>
<h2 id="maps-are-the-architecture-not-an-afterthought">Maps Are the Architecture, Not an Afterthought</h2>
<p>Maps are kernel objects that live outside any individual program invocation. They&#8217;re shared between multiple eBPF programs, readable and writable from userspace, and persistent for the lifetime of the map — which can outlive both the program that created them and the userspace process that loaded them.</p>
<p>Every production eBPF tool is fundamentally a map-based architecture:</p>
<ul>
<li>Cilium stores connection tracking state in BPF hash maps</li>
<li>Falco uses ring buffers to stream syscall events to its userspace rule engine</li>
<li>Tetragon maintains process tree state across exec events using maps</li>
<li>Datadog NPM stores per-connection flow stats in per-CPU maps for lock-free metric accumulation</li>
</ul>
<p>Run <code class="" data-line="">bpftool map list</code> on a Cilium node:</p>
<pre><code class="" data-line="">$ bpftool map list
ID 12: hash          name cilium_ct4_glo    key 24B  value 56B   max_entries 65536
#      ^^^^           ^^^^^^^^^^^^^^^^       ^^^^^^   ^^^^^^^     ^^^^^^^^^^^^^^^^
#      type           map name               key size value size  max concurrent entries

ID 13: lpm_trie      name cilium_ipcache    key 40B  value 32B   max_entries 512000
#      longest-prefix-match trie — for IP address + CIDR lookups

ID 14: percpu_hash   name cilium_metrics    key 8B   value 32B   max_entries 65536
#      one copy of this map per CPU — no write contention for high-frequency counters

ID 28: ringbuf       name falco_events      max_entries 8388608
#                                           ^^^^^^^^^^^ 8MB ring buffer for event streaming
</code></pre>
<p>Connection tracking, IP policy cache, per-CPU metrics, event stream. Every one of these is a different map type, chosen for a specific reason.</p>
<h2 id="map-types-and-what-theyre-actually-used-for">Map Types and What They&#8217;re Actually Used For</h2>
<h3 id="hash-maps">Hash Maps</h3>
<p>The general-purpose key-value store. A key maps to a value — lookup is O(1) average. Cilium&#8217;s connection tracking map (<code class="" data-line="">cilium_ct4_glo</code>) is a hash map: the key is a 5-tuple (source IP, destination IP, ports, protocol), the value is the connection state.</p>
<pre><code class="" data-line="">$ bpftool map show id 12
12: hash  name cilium_ct4_glo  flags 0x0
        key 24B  value 56B  max_entries 65536  memlock 5767168B
</code></pre>
<p>The <code class="" data-line="">key 24B</code> is the 5-tuple. The <code class="" data-line="">value 56B</code> is the connection state record. <code class="" data-line="">max_entries 65536</code> is the upper bound — Cilium can track 65,536 active connections in this map before hitting the limit.</p>
<p>Hash maps are shared across all CPUs on the node. When multiple CPUs try to update the same entry simultaneously — which happens constantly on busy nodes — writes need to be coordinated. For most use cases this is fine. For high-frequency counters updated on every packet, it&#8217;s a bottleneck. That&#8217;s when you reach for a per-CPU hash map.</p>
<p><strong>Where you see them:</strong> connection tracking, per-IP statistics, process-to-identity mapping, policy verdict caching.</p>
<h3 id="per-cpu-hash-maps">Per-CPU Hash Maps</h3>
<p>Per-CPU hash maps solve the write coordination problem by giving each CPU its own independent copy of every entry. There&#8217;s no sharing, no contention, no waiting — each CPU writes its own copy without touching any other.</p>
<p>The tradeoff: reading from userspace means collecting one value per CPU and summing them up. That aggregation happens in the tool, not the kernel.</p>
<pre><code class="" data-line=""># Cilium&#039;s per-CPU metrics map — one counter value per CPU
bpftool map dump id 14
key: 0x00000001
  value (CPU 00): 12345
  value (CPU 01): 8901
  value (CPU 02): 3421
  value (CPU 03): 7102
# total bytes for this metric: 31769
</code></pre>
<p>Cilium&#8217;s <code class="" data-line="">cilium_metrics</code> map uses this pattern for exactly this reason — it&#8217;s updated on every packet across every CPU on the node. Forcing all CPUs to coordinate writes to a single shared entry at that rate would hurt throughput. Instead: each CPU writes locally, Cilium&#8217;s userspace agent sums the values at export time.</p>
<p><strong>Where you see them:</strong> packet counters, byte counters, syscall frequency metrics — anywhere updates happen on every event at high volume.</p>
<h3 id="lru-hash-maps">LRU Hash Maps</h3>
<p>LRU hash maps add automatic eviction. Same key-value semantics as a regular hash map, but when the map hits its entry limit, the least recently accessed entry is dropped to make room for the new one.</p>
<p>This matters for any map tracking dynamic state with an unpredictable number of keys: TCP connections, process IDs, DNS queries, pod IPs. Without LRU semantics, a full map returns an error on insert — and in production, that means your tool silently stops tracking new entries. Not a crash, not an alert — just missing data.</p>
<p>Cilium&#8217;s connection tracking map is LRU-bounded at 65,536 entries. On a node handling high-connection-rate workloads, this can fill up. When it does, Cilium starts evicting old connections to make room for new ones — and if it&#8217;s evicting too aggressively, you&#8217;ll see connection resets.</p>
<pre><code class="" data-line=""># Check current CT map usage vs its limit
bpftool map show id 12
# max_entries tells you the ceiling
# count entries to see current usage
bpftool map dump id 12 | grep -c &quot;^key&quot;
</code></pre>
<p>Size LRU maps at 2× your expected concurrent active entries. Aggressive eviction under pressure introduces gaps — not crashes, but missing or incorrect state.</p>
<p><strong>Where you see them:</strong> connection tracking, process lineage, anything where the key space is dynamic and unbounded.</p>
<h3 id="ring-buffers">Ring Buffers</h3>
<p>Ring buffers are how eBPF tools stream events from the kernel to a userspace consumer. Falco reads syscall events from a ring buffer. Tetragon streams process execution and network events through ring buffers. The pattern is the same across all of them:</p>
<pre><code class="" data-line="">kernel eBPF program
  → sees event (syscall, network packet, process exec)
  → writes record to ring buffer
  → userspace tool reads it and processes (Falco rules, Tetragon policies)
</code></pre>
<p>What makes ring buffers the right primitive for event streaming:</p>
<ul>
<li><strong>Single buffer shared across all CPUs</strong> — unlike the older <code class="" data-line="">perf_event_array</code> approach which required one buffer per CPU, a ring buffer is one allocation, one file descriptor, one consumer</li>
<li><strong>Lock-free</strong> — the kernel writes, the userspace tool reads, they don&#8217;t block each other</li>
<li><strong>Backpressure when full</strong> — if the userspace tool can&#8217;t keep up, new events are dropped rather than queued indefinitely. The tool can detect and count drops. Falco reports these as <code class="" data-line="">Dropped events</code> in its stats output.</li>
</ul>
<pre><code class="" data-line=""># Falco&#039;s ring buffer — 8MB
bpftool map list | grep ringbuf
# ID 28: ringbuf  name falco_events  max_entries 8388608
</code></pre>
<p>8,388,608 bytes = 8MB. That&#8217;s the buffer between Falco&#8217;s kernel hooks and its rule engine. If there&#8217;s a burst of syscall activity and Falco&#8217;s rule evaluation can&#8217;t keep up, events drop into that window and are lost.</p>
<p>Sizing matters operationally. Too small and you drop events during normal burst. Too large and you&#8217;re holding non-pageable kernel memory that doesn&#8217;t show up in standard memory metrics.</p>
<pre><code class="" data-line=""># Check Falco&#039;s drop rate
falcoctl stats
# or check the Falco logs
journalctl -u falco | grep -i &quot;drop&quot;
</code></pre>
<p>Most production deployments run 8–32MB. Start at 8MB, monitor drop rates under load, size up if needed.</p>
<p><strong>Where you see them:</strong> Falco event streaming, Tetragon audit events, any tool that needs to move high-volume event data from kernel to userspace.</p>
<h3 id="array-maps">Array Maps</h3>
<p>Array maps are fixed-size, integer-indexed, and entirely pre-allocated at creation time. Think of them as lookup tables with integer keys — constant-time access, no hash overhead, no dynamic allocation.</p>
<p>Cilium uses array maps for policy configuration: a fixed set of slots indexed by endpoint identity number. When a packet arrives and Cilium needs to check policy, it indexes into the array directly rather than doing a hash lookup. For read-heavy, write-rare data, this is faster.</p>
<p>The constraint: you can&#8217;t delete entries from an array map. Every slot exists for the lifetime of the map. If you need to track state that comes and goes — connections, processes, pods — use a hash map instead.</p>
<p><strong>Where you see them:</strong> policy configuration, routing tables with fixed indices, per-CPU stats indexed by CPU number.</p>
<h3 id="lpm-trie-maps">LPM Trie Maps</h3>
<p>LPM (Longest Prefix Match) trie maps handle IP prefix lookups — the same operation that a hardware router does when deciding which interface to send a packet out of.</p>
<p>You can store a mix of specific host addresses (/32) and CIDR ranges (/16, /24) in the same map, and a lookup returns the most specific match. If <code class="" data-line="">10.0.1.15/32</code> and <code class="" data-line="">10.0.0.0/8</code> are both in the map, a lookup for <code class="" data-line="">10.0.1.15</code> returns the /32 entry.</p>
<p>Cilium&#8217;s <code class="" data-line="">cilium_ipcache</code> map is an LPM trie. It maps every IP in the cluster to its security identity — the identifier Cilium uses for policy enforcement. When a packet arrives, Cilium does a trie lookup on the source IP to find out which endpoint sent it, then checks policy against that identity.</p>
<pre><code class="" data-line=""># Inspect the ipcache map
bpftool map show id 13
# lpm_trie  name cilium_ipcache  key 40B  value 32B  max_entries 512000

# Look up which security identity owns a pod IP
bpftool map lookup id 13 key hex 20 00 00 00 0a 00 01 0f 00 00 00 00 00 00 00 00 00 00 00 00
</code></pre>
<p><strong>Where you see them:</strong> IP-to-identity mapping (Cilium), CIDR-based policy enforcement, IP blocklists.</p>
<hr />
<h2 id="pinned-maps-state-that-survives-restarts">Pinned Maps — State That Survives Restarts</h2>
<p>By default, a map&#8217;s lifetime is tied to the tool that created it. When the tool exits, the kernel garbage-collects the map.</p>
<p>Pinning writes a reference to the BPF filesystem at <code class="" data-line="">/sys/fs/bpf</code>, which keeps the map alive even after the creating process exits:</p>
<pre><code class="" data-line=""># See all maps Cilium has pinned
ls /sys/fs/bpf/tc/globals/
# cilium_ct4_global  cilium_ipcache  cilium_metrics  cilium_policy ...

# Inspect a pinned map directly — no Cilium process needed
bpftool map dump pinned /sys/fs/bpf/tc/globals/cilium_ct4_global

# Pin any map by ID for manual inspection
bpftool map pin id 12 /sys/fs/bpf/my_conn_tracker
bpftool map dump pinned /sys/fs/bpf/my_conn_tracker
</code></pre>
<p>Cilium pins all its maps under <code class="" data-line="">/sys/fs/bpf/tc/globals/</code>. When Cilium restarts — rolling upgrade, crash, OOM kill — it reopens its pinned maps and resumes with existing state intact. Pods maintain established TCP connections through a Cilium restart without disruption.</p>
<p>This is operationally significant: if you&#8217;re evaluating eBPF-based tools for production, check whether they pin their maps. A tool that doesn&#8217;t loses all its tracked state on every restart — connection tracking resets, process lineage gaps, policy state rebuilt from scratch.</p>
<hr />
<h2 id="map-memory-a-production-consideration">Map Memory: A Production Consideration</h2>
<p>Map memory is kernel-locked — it cannot be paged out, and it doesn&#8217;t show up in standard memory pressure metrics. Your node&#8217;s <code class="" data-line="">free</code> output and container memory limits don&#8217;t account for it.</p>
<blockquote>
<p><strong>Kernel-locked memory</strong> is memory the OS guarantees will never be swapped to disk — it stays in RAM permanently. The kernel requires this for eBPF maps because a kernel program running during a network interrupt cannot wait for a page fault. The side effect: it doesn&#8217;t appear in <code class="" data-line="">top</code>, <code class="" data-line="">free</code>, or container memory metrics, so it&#8217;s easy to accidentally provision nodes without accounting for it.</p>
</blockquote>
<pre><code class="" data-line=""># Total eBPF map memory locked on this node
bpftool map list -j | python3 -c &quot;
import json,sys
maps=json.load(sys.stdin)
total=sum(m.get(&#039;bytes_memlock&#039;,0) for m in maps)
print(f&#039;Total map memory: {total/1024/1024:.1f} MB&#039;)
&quot;

# Check system memlock limit (unlimited is correct for eBPF tools)
ulimit -l

# Check what Cilium&#039;s systemd unit sets
systemctl show cilium | grep -i memlock
</code></pre>
<p>On a node running Cilium + Falco + Datadog NPM, I&#8217;ve seen 200–400MB of map memory locked. That&#8217;s real, non-pageable kernel memory. If you&#8217;re sizing nodes for eBPF-heavy workloads, account for this separately from your pod workload memory.</p>
<p>If an eBPF tool fails to load with a permission error despite having enough free memory, the root cause is usually the <code class="" data-line="">memlock</code> ulimit for the process. Cilium, Falco, and most production tools set <code class="" data-line="">LimitMEMLOCK=infinity</code> in their systemd units. Verify this if you&#8217;re deploying a new eBPF-based tool and seeing unexpected load failures.</p>
<hr />
<h2 id="inspecting-maps-in-production">Inspecting Maps in Production</h2>
<pre><code class="" data-line=""># List all maps: type, name, key/value sizes, memory usage
bpftool map list

# Dump all entries in a map (careful with large maps)
bpftool map dump id 12

# Look up a specific entry by key
bpftool map lookup id 12 key hex 0a 00 01 0f 00 00 00 00

# Watch map stats live
watch -n1 &#039;bpftool map show id 12&#039;

# See all maps for a specific tool by checking its pinned path
ls /sys/fs/bpf/tc/globals/                    # Cilium
ls /sys/fs/bpf/falco/                         # Falco (if pinned)

# Cross-reference map IDs with the programs using them
bpftool prog list
bpftool map list
</code></pre>
<hr />
<h2 id="production-gotchas"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Production Gotchas</h2>
<p><strong>A full LRU map drops state silently, not loudly</strong><br />
When Cilium&#8217;s CT map fills up, it starts evicting the least recently used connections — not returning an error. You see connection resets, not a tool alert. Check map utilisation (<code class="" data-line="">bpftool map dump id X | grep -c key</code>) against <code class="" data-line="">max_entries</code> on nodes with high connection rates.</p>
<p><strong>Ring buffer drops don&#8217;t stop the tool — they create gaps</strong><br />
When Falco&#8217;s ring buffer fills up, events are dropped. Falco keeps running. The rule engine keeps processing. But you have gaps in your syscall visibility. Monitor <code class="" data-line="">Dropped events</code> in Falco&#8217;s stats and size the ring buffer accordingly.</p>
<p><strong>Map memory is invisible to standard monitoring</strong><br />
200–400MB of kernel-locked memory on a Cilium + Falco node doesn&#8217;t appear in <code class="" data-line="">top</code>, container memory metrics, or memory pressure alerts. Size eBPF-heavy nodes with this in mind and add explicit map memory monitoring via <code class="" data-line="">bpftool</code>.</p>
<p><strong>Tools that don&#8217;t pin their maps lose state on restart</strong><br />
A Cilium restart with pinned maps = zero-disruption connection tracking. A tool without pinning = all tracked state rebuilt from scratch. This matters for connection tracking tools and any tool maintaining process lineage.</p>
<p><strong><code class="" data-line="">perf_event_array</code> on kernel 5.8+ is the old way</strong><br />
Older eBPF tools use per-CPU <code class="" data-line="">perf_event_array</code> for event streaming. Ring buffer is strictly better — single allocation, lower overhead, simpler consumption. If you&#8217;re running a tool that still uses <code class="" data-line="">perf_event_array</code> on a 5.8+ kernel, it&#8217;s using a legacy path.</p>
<hr />
<h2 id="key-takeaways">Key Takeaways</h2>
<ul>
<li>eBPF programs are stateless — maps are where all state lives, between invocations and between kernel and userspace</li>
<li>Every production eBPF tool (Cilium, Falco, Tetragon, Datadog NPM) is a map-based architecture — <code class="" data-line="">bpftool map list</code> shows you what it&#8217;s actually holding</li>
<li>Per-CPU maps eliminate write contention for high-frequency counters; the tool aggregates per-CPU values at export time</li>
<li>LRU maps handle unbounded key spaces (IPs, PIDs, connections) without hard errors when full — but eviction is silent, so size generously</li>
<li>Ring buffer (kernel 5.8+) is the correct event streaming primitive — Falco and Tetragon both use it</li>
<li>Map memory is kernel-locked and invisible to standard memory metrics — account for it explicitly on eBPF-heavy nodes</li>
<li>Pinned maps survive restarts; Cilium uses this for zero-disruption connection tracking through upgrades</li>
</ul>
<hr />
<h2 id="whats-next">What&#8217;s Next</h2>
<p>You know what program types run in the kernel, and you know how they hold state.</p>
<p>Get EP06 in your inbox when it publishes → <a href="https://linuxcent.com/subscribe">linuxcent.com/subscribe</a> But there&#8217;s a problem anyone running eBPF-based tools eventually runs into: a tool works on one kernel version and breaks on the next. Struct layouts shift between patch versions. Field offsets move. EP06 covers CO-RE (Compile Once, Run Everywhere) and libbpf — the mechanism that makes tools like Cilium and Falco survive your node upgrades without recompilation, and why kernel version compatibility is a solved problem for any tool built on this toolchain.</p>
<p><a class="a2a_button_mastodon" href="https://www.addtoany.com/add_to/mastodon?linkurl=https%3A%2F%2Flinuxcent.com%2Febpf-maps-explained%2F&amp;linkname=eBPF%20Maps%20%E2%80%94%20The%20Persistent%20Data%20Layer%20Between%20Kernel%20and%20Userspace" title="Mastodon" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_email" href="https://www.addtoany.com/add_to/email?linkurl=https%3A%2F%2Flinuxcent.com%2Febpf-maps-explained%2F&amp;linkname=eBPF%20Maps%20%E2%80%94%20The%20Persistent%20Data%20Layer%20Between%20Kernel%20and%20Userspace" title="Email" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_whatsapp" href="https://www.addtoany.com/add_to/whatsapp?linkurl=https%3A%2F%2Flinuxcent.com%2Febpf-maps-explained%2F&amp;linkname=eBPF%20Maps%20%E2%80%94%20The%20Persistent%20Data%20Layer%20Between%20Kernel%20and%20Userspace" title="WhatsApp" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_reddit" href="https://www.addtoany.com/add_to/reddit?linkurl=https%3A%2F%2Flinuxcent.com%2Febpf-maps-explained%2F&amp;linkname=eBPF%20Maps%20%E2%80%94%20The%20Persistent%20Data%20Layer%20Between%20Kernel%20and%20Userspace" title="Reddit" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_x" href="https://www.addtoany.com/add_to/x?linkurl=https%3A%2F%2Flinuxcent.com%2Febpf-maps-explained%2F&amp;linkname=eBPF%20Maps%20%E2%80%94%20The%20Persistent%20Data%20Layer%20Between%20Kernel%20and%20Userspace" title="X" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_linkedin" href="https://www.addtoany.com/add_to/linkedin?linkurl=https%3A%2F%2Flinuxcent.com%2Febpf-maps-explained%2F&amp;linkname=eBPF%20Maps%20%E2%80%94%20The%20Persistent%20Data%20Layer%20Between%20Kernel%20and%20Userspace" title="LinkedIn" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_copy_link" href="https://www.addtoany.com/add_to/copy_link?linkurl=https%3A%2F%2Flinuxcent.com%2Febpf-maps-explained%2F&amp;linkname=eBPF%20Maps%20%E2%80%94%20The%20Persistent%20Data%20Layer%20Between%20Kernel%20and%20Userspace" title="Copy Link" rel="nofollow noopener" target="_blank"></a><a class="a2a_dd addtoany_share_save addtoany_share" href="https://www.addtoany.com/share#url=https%3A%2F%2Flinuxcent.com%2Febpf-maps-explained%2F&#038;title=eBPF%20Maps%20%E2%80%94%20The%20Persistent%20Data%20Layer%20Between%20Kernel%20and%20Userspace" data-a2a-url="https://linuxcent.com/ebpf-maps-explained/" data-a2a-title="eBPF Maps — The Persistent Data Layer Between Kernel and Userspace"></a></p><p>The post <a href="https://linuxcent.com/ebpf-maps-explained/">eBPF Maps — The Persistent Data Layer Between Kernel and Userspace</a> appeared first on <a href="https://linuxcent.com">Linuxcent</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://linuxcent.com/ebpf-maps-explained/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1481</post-id>	</item>
		<item>
		<title>What Is eBPF? A Plain-English Guide for Linux and Kubernetes Engineers</title>
		<link>https://linuxcent.com/what-is-ebpf-linux-kubernetes/</link>
					<comments>https://linuxcent.com/what-is-ebpf-linux-kubernetes/#respond</comments>
		
		<dc:creator><![CDATA[Vamshi Krishna Santhapuri]]></dc:creator>
		<pubDate>Thu, 19 Mar 2026 17:53:41 +0000</pubDate>
				<category><![CDATA[eBPF]]></category>
		<category><![CDATA[bpftool]]></category>
		<category><![CDATA[Cilium]]></category>
		<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[Observability]]></category>
		<category><![CDATA[SRE]]></category>
		<guid isPermaLink="false">https://linuxcent.com/?p=1429</guid>

					<description><![CDATA[<p><span class="span-reading-time rt-reading-time" style="display: block;"><span class="rt-label rt-prefix">Reading Time: </span> <span class="rt-time"> 7</span> <span class="rt-label rt-postfix">minutes</span></span>What is eBPF? A sandboxed way to run programs in the Linux kernel — the technology behind Cilium, Falco, and Tetragon on your Kubernetes nodes.</p>
<p>The post <a href="https://linuxcent.com/what-is-ebpf-linux-kubernetes/">What Is eBPF? A Plain-English Guide for Linux and Kubernetes Engineers</a> appeared first on <a href="https://linuxcent.com">Linuxcent</a>.</p>
]]></description>
										<content:encoded><![CDATA[<span class="span-reading-time rt-reading-time" style="display: block;"><span class="rt-label rt-prefix">Reading Time: </span> <span class="rt-time"> 7</span> <span class="rt-label rt-postfix">minutes</span></span><style>
pre{position:relative;background:#1e1e1e;color:#d4d4d4;
    padding:16px 16px 16px 20px;border-radius:6px;overflow-x:auto;
    font-family:'JetBrains Mono','Fira Code','Cascadia Code',Consolas,'Courier New',monospace;
    font-size:.88em;line-height:1.6;border-left:4px solid #555}
code{background:#f4f4f4;padding:2px 5px;border-radius:3px;font-size:.9em}
pre code{background:transparent;padding:0;color:inherit}
pre[data-lang="bash"],pre[data-lang="sh"],
pre[data-lang="shell"],pre[data-lang="zsh"]{border-left-color:#4ec9b0}
pre[data-lang="yaml"],pre[data-lang="json"],
pre[data-lang="toml"],pre[data-lang="xml"]{border-left-color:#569cd6}
pre[data-lang="python"],pre[data-lang="go"],pre[data-lang="rust"],
pre[data-lang="java"],pre[data-lang="c"],pre[data-lang="cpp"]{border-left-color:#c586c0}
pre[data-lang="text"],pre[data-lang="output"],
pre[data-lang="console"]{border-left-color:#888}
.lc-copy-btn{position:absolute;top:8px;right:8px;background:#2d2d2d;color:#ccc;
    border:1px solid #444;border-radius:4px;padding:3px 9px;font-size:.75em;
    font-family:system-ui,sans-serif;cursor:pointer;opacity:0;
    transition:opacity .15s,background .15s;line-height:1.6}
pre:hover .lc-copy-btn{opacity:1}
.lc-copy-btn:hover{background:#3a3a3a;color:#fff}
.lc-copy-btn.copied{color:#4ec9b0;border-color:#4ec9b0}
.lc-lang-badge{position:absolute;top:8px;left:20px;font-family:system-ui,sans-serif;
    font-size:.7em;color:#666;text-transform:uppercase;letter-spacing:.04em;
    line-height:1;pointer-events:none;opacity:0;transition:opacity .15s}
pre:hover .lc-lang-badge{opacity:1}
table{border-collapse:collapse;width:100%;margin:16px 0}
th,td{border:1px solid #ddd;padding:10px 14px;text-align:left}
th{background:#f0f0f0;font-weight:600}
tr:nth-child(even){background:#fafafa}
</style>
<p><script>
(function(){
  if(window.__lcCodeEnhanced)return;
  window.__lcCodeEnhanced=true;
  function enhance(){
    document.querySelectorAll('pre').forEach(function(pre){
      var code=pre.querySelector('code');
      var lang='';
      if(code){var m=(code.className||'').match(/language-(\S+)/);if(m)lang=m[1].toLowerCase();}
      if(lang)pre.setAttribute('data-lang',lang);
      if(lang){var badge=document.createElement('span');badge.className='lc-lang-badge';badge.textContent=lang;pre.insertBefore(badge,pre.firstChild);}
      var btn=document.createElement('button');
      btn.className='lc-copy-btn';btn.textContent='Copy';btn.setAttribute('aria-label','Copy code to clipboard');
      pre.appendChild(btn);
      btn.addEventListener('click',function(){
        var text=code?code.innerText:pre.innerText;
        if(navigator.clipboard&&window.isSecureContext){
          navigator.clipboard.writeText(text).then(function(){ok(btn);}).catch(function(){fb(text,btn);});
        }else{fb(text,btn);}
      });
    });
  }
  function ok(btn){btn.textContent='Copied!';btn.classList.add('copied');setTimeout(function(){btn.textContent='Copy';btn.classList.remove('copied');},2000);}
  function fb(text,btn){
    try{var ta=document.createElement('textarea');ta.value=text;ta.style.cssText='position:fixed;left:-9999px;top:-9999px;opacity:0';document.body.appendChild(ta);ta.select();document.execCommand('copy');document.body.removeChild(ta);ok(btn);}
    catch(e){btn.textContent='✗ Failed';setTimeout(function(){btn.textContent='Copy';},2000);}
  }
  if(document.readyState==='loading'){document.addEventListener('DOMContentLoaded',enhance);}else{enhance();}
})();
</script><br />
<span class="span-reading-time rt-reading-time" style="display: block;"><span class="rt-label rt-prefix">Reading Time: </span> <span class="rt-time"> 6</span> <span class="rt-label rt-postfix">minutes</span></span></p>
<p><!-- ============================================================
     LINUXCENT.COM — WordPress HTML Post
     Title : What Is eBPF? A Plain-English Guide for Linux and Kubernetes Engineers
     Series: eBPF: From Kernel to Cloud — Episode 1
     Author: Vamshi Krishna Santhapuri
     Paste into: Gutenberg → Custom HTML block
                 OR Classic editor → Text tab
     Font  : WordPress system font stack — no external imports needed
     ============================================================ --></p>
<p><!-- ============================================================
     LINUXCENT.COM — Episode 1
     Paste into: WordPress Classic editor → Text tab
     OR Gutenberg → Custom HTML block
     Style: Matches linuxcent.com native theme — no custom CSS needed
     ============================================================ --></p>
<p><em>~1,900 words &middot; Reading time: 7 min &middot; Series: eBPF: From Kernel to Cloud, Episode 1 of 18</em></p>
<p>Your Linux kernel has had a technology built into it since 2014 that most engineers working with Linux every day have never looked at directly. You have almost certainly been using it &mdash; through Cilium, Falco, Datadog, or even systemd &mdash; without knowing it was there.</p>
<p>This post is the plain-English introduction to eBPF that I wished existed when I first encountered it. No kernel engineering background required. No bytecode, no BPF maps, no JIT compilation. Just a clear answer to the question every Linux admin and DevOps engineer eventually asks: <strong>what actually is eBPF, and why does it matter for the infrastructure I run every day?</strong></p>
<hr />
<div id="ez-toc-container" class="ez-toc-v2_0_82_2 counter-hierarchy ez-toc-counter ez-toc-grey ez-toc-container-direction">
<div class="ez-toc-title-container">
<p class="ez-toc-title" style="cursor:inherit">Table of Contents</p>
<p><span class="ez-toc-title-toggle"><a href="#" class="ez-toc-pull-right ez-toc-btn ez-toc-btn-xs ez-toc-btn-default ez-toc-toggle" aria-label="Toggle Table of Content"><span class="ez-toc-js-icon-con"><span class=""><span class="eztoc-hide" style="display:none;">Toggle</span><span class="ez-toc-icon-toggle-span"><svg style="fill: #999;color:#999" xmlns="http://www.w3.org/2000/svg" class="list-377408" width="20px" height="20px" viewBox="0 0 24 24" fill="none"><path d="M6 6H4v2h2V6zm14 0H8v2h12V6zM4 11h2v2H4v-2zm16 0H8v2h12v-2zM4 16h2v2H4v-2zm16 0H8v2h12v-2z" fill="currentColor"></path></svg><svg style="fill: #999;color:#999" class="arrow-unsorted-368013" xmlns="http://www.w3.org/2000/svg" width="10px" height="10px" viewBox="0 0 24 24" version="1.2" baseProfile="tiny"><path d="M18.2 9.3l-6.2-6.3-6.2 6.3c-.2.2-.3.4-.3.7s.1.5.3.7c.2.2.4.3.7.3h11c.3 0 .5-.1.7-.3.2-.2.3-.5.3-.7s-.1-.5-.3-.7zM5.8 14.7l6.2 6.3 6.2-6.3c.2-.2.3-.5.3-.7s-.1-.5-.3-.7c-.2-.2-.4-.3-.7-.3h-11c-.3 0-.5.1-.7.3-.2.2-.3.5-.3.7s.1.5.3.7z"/></svg></span></span></span></a></span></div>
<nav>
<ul class='ez-toc-list ez-toc-list-level-1 ' >
<li class='ez-toc-page-1 ez-toc-heading-level-2'><a class="ez-toc-link ez-toc-heading-1" href="https://linuxcent.com/what-is-ebpf-linux-kubernetes/#First_Forget_the_Name" >First: Forget the Name</a></li>
<li class='ez-toc-page-1 ez-toc-heading-level-2'><a class="ez-toc-link ez-toc-heading-2" href="https://linuxcent.com/what-is-ebpf-linux-kubernetes/#What_the_Linux_Kernel_Can_See_That_Nothing_Else_Can" >What the Linux Kernel Can See That Nothing Else Can</a></li>
<li class='ez-toc-page-1 ez-toc-heading-level-2'><a class="ez-toc-link ez-toc-heading-3" href="https://linuxcent.com/what-is-ebpf-linux-kubernetes/#The_Problem_eBPF_Solves_%E2%80%94_A_Real_Kubernetes_Scenario" >The Problem eBPF Solves &mdash; A Real Kubernetes Scenario</a>
<ul class='ez-toc-list-level-3' >
<li class='ez-toc-heading-level-3'><a class="ez-toc-link ez-toc-heading-4" href="https://linuxcent.com/what-is-ebpf-linux-kubernetes/#The_old_approaches_and_their_problems" >The old approaches and their problems</a></li>
<li class='ez-toc-page-1 ez-toc-heading-level-3'><a class="ez-toc-link ez-toc-heading-5" href="https://linuxcent.com/what-is-ebpf-linux-kubernetes/#The_eBPF_approach" >The eBPF approach</a></li>
</ul>
</li>
<li class='ez-toc-page-1 ez-toc-heading-level-2'><a class="ez-toc-link ez-toc-heading-6" href="https://linuxcent.com/what-is-ebpf-linux-kubernetes/#Tools_You_Are_Probably_Already_Running_on_eBPF" >Tools You Are Probably Already Running on eBPF</a></li>
<li class='ez-toc-page-1 ez-toc-heading-level-2'><a class="ez-toc-link ez-toc-heading-7" href="https://linuxcent.com/what-is-ebpf-linux-kubernetes/#eBPF_vs_the_Old_Ways" >eBPF vs the Old Ways</a></li>
<li class='ez-toc-page-1 ez-toc-heading-level-2'><a class="ez-toc-link ez-toc-heading-8" href="https://linuxcent.com/what-is-ebpf-linux-kubernetes/#Is_It_Safe_to_Run_in_Production" >Is It Safe to Run in Production?</a></li>
<li class='ez-toc-page-1 ez-toc-heading-level-2'><a class="ez-toc-link ez-toc-heading-9" href="https://linuxcent.com/what-is-ebpf-linux-kubernetes/#Common_Misconceptions" >Common Misconceptions</a></li>
<li class='ez-toc-page-1 ez-toc-heading-level-2'><a class="ez-toc-link ez-toc-heading-10" href="https://linuxcent.com/what-is-ebpf-linux-kubernetes/#Kernel_Version_Requirements" >Kernel Version Requirements</a></li>
<li class='ez-toc-page-1 ez-toc-heading-level-2'><a class="ez-toc-link ez-toc-heading-11" href="https://linuxcent.com/what-is-ebpf-linux-kubernetes/#The_Bottom_Line" >The Bottom Line</a></li>
<li class='ez-toc-page-1 ez-toc-heading-level-2'><a class="ez-toc-link ez-toc-heading-12" href="https://linuxcent.com/what-is-ebpf-linux-kubernetes/#Further_Reading" >Further Reading</a></li>
</ul>
</nav>
</div>
<p style="font-size:0.72em;font-weight:700;letter-spacing:0.12em;color:#f59e0b;text-transform:uppercase;margin:2em 0 0.75em 0;text-align:center;">Architecture Overview</p>
<figure class="wp-block-image size-full" style="margin:0 0 0.5em 0;">
<img decoding="async" width="2400" height="2304" src="https://linuxcent.com/wp-content/uploads/2026/05/ep01-what-is-ebpf-og-2.png" alt="What Is eBPF — architecture diagram showing eBPF program types, verifier, JIT compiler, and kernel hook points" class="wp-image-2108" style="width:100%;height:auto;display:block;border-radius:8px;" srcset="https://linuxcent.com/wp-content/uploads/2026/05/ep01-what-is-ebpf-og-2.png 2400w, https://linuxcent.com/wp-content/uploads/2026/05/ep01-what-is-ebpf-og-2-300x288.png 300w, https://linuxcent.com/wp-content/uploads/2026/05/ep01-what-is-ebpf-og-2-1024x983.png 1024w, https://linuxcent.com/wp-content/uploads/2026/05/ep01-what-is-ebpf-og-2-768x737.png 768w, https://linuxcent.com/wp-content/uploads/2026/05/ep01-what-is-ebpf-og-2-1536x1475.png 1536w, https://linuxcent.com/wp-content/uploads/2026/05/ep01-what-is-ebpf-og-2-2048x1966.png 2048w" sizes="(max-width: 2400px) 100vw, 2400px" /><figcaption style="text-align:center;font-size:0.85em;color:#6b7280;margin-top:0.75em;">eBPF sits between user space and the kernel — attaching programs to hook points without modifying kernel source.</figcaption></figure>
<hr style="border:none;border-top:1px solid #e5e7eb;margin:0.5em 0 2em 0;"/>
<h2 id="tldr">TL;DR</h2>
<ul>
<li>eBPF lets you run small, safe programs inside the Linux kernel — no kernel module, no reboot, no application changes required</li>
<li>The name is a historical artefact; modern eBPF is a general-purpose kernel observability and networking platform, not a packet filter</li>
<li>Programs attach to kernel hook points (tracepoints, kprobes, socket filters) — giving you visibility into every syscall, file open, and network packet</li>
<li>You are probably already running eBPF: Cilium, Falco, Datadog, and systemd all use it under the hood</li>
<li>Safe for production because the BPF verifier rejects any program that could crash or loop — covered in depth in <a href="/bpf-verifier-ebpf-safety/">EP02</a></li>
<li>Full feature set from Linux 5.8+; meaningful production use from Linux 4.14+ (most EKS and GKE defaults qualify)</li>
</ul>
<hr />
<h2><span class="ez-toc-section" id="First_Forget_the_Name"></span>First: Forget the Name<span class="ez-toc-section-end"></span></h2>
<p>eBPF stands for <em>extended Berkeley Packet Filter</em>. It is one of the most misleading names in computing for what the technology actually does.</p>
<p>The original BPF was a 1992 mechanism for filtering network packets &mdash; the engine behind <code class="" data-line="">tcpdump</code>. The extended version, introduced in Linux 3.18 (2014) and significantly matured through Linux 5.x, is a completely different technology. It is no longer just about packets. It is no longer just about filtering.</p>
<p>Forget the name. Here is what eBPF actually is:</p>
<blockquote>
<p>eBPF lets you run small, safe programs directly inside the Linux kernel &mdash; without writing a kernel module, without rebooting, and without modifying your applications.</p>
</blockquote>
<p>That is the complete definition. Everything else is implementation detail. The one-liner above is what matters for how you use it day to day.</p>
<hr />
<h2><span class="ez-toc-section" id="What_the_Linux_Kernel_Can_See_That_Nothing_Else_Can"></span>What the Linux Kernel Can See That Nothing Else Can<span class="ez-toc-section-end"></span></h2>
<p>To understand why eBPF is significant, you need to understand what the Linux kernel already sees on every server and every Kubernetes node you run.</p>
<p>The kernel is the lowest layer of software on your machine. Every action that happens &mdash; every file opened, every process started, every network packet sent &mdash; passes through the kernel. That means it has a complete, real-time view of everything:</p>
<ul>
<li><strong>Every syscall</strong> &mdash; every <code class="" data-line="">open()</code>, <code class="" data-line="">execve()</code>, <code class="" data-line="">connect()</code>, <code class="" data-line="">write()</code> from every process in every container on the node, in real time</li>
<li><strong>Every network packet</strong> &mdash; source, destination, port, protocol, bytes, and latency for every pod-to-pod and pod-to-external connection</li>
<li><strong>Every process event</strong> &mdash; every fork, exec, and exit, including processes spawned inside containers that your container runtime never reports</li>
<li><strong>Every file access</strong> &mdash; which process opened which file, when, and with what permissions, across all workloads on the node simultaneously</li>
<li><strong>CPU and memory usage</strong> &mdash; per-process CPU time, function-level latency, and memory allocation patterns without profiling agents</li>
</ul>
<p>The kernel has always had this visibility. The problem was that there was no safe, practical way to access it without writing kernel modules &mdash; which are complex, kernel version-specific, and genuinely dangerous to run in production. eBPF is the safe, practical way to access it.</p>
<hr />
<h2><span class="ez-toc-section" id="The_Problem_eBPF_Solves_%E2%80%94_A_Real_Kubernetes_Scenario"></span>The Problem eBPF Solves &mdash; A Real Kubernetes Scenario<span class="ez-toc-section-end"></span></h2>
<p>Here is a situation every Kubernetes engineer has faced. A production pod starts behaving strangely &mdash; elevated CPU, slow responses, occasional connection failures. You want to understand what is happening at a low level: what syscalls is it making, what network connections is it opening, is something spawning unexpected processes?</p>
<h3><span class="ez-toc-section" id="The_old_approaches_and_their_problems"></span>The old approaches and their problems<span class="ez-toc-section-end"></span></h3>
<p><strong>Restart the pod with a debug sidecar.</strong> You lose the current state immediately. The issue may not reproduce. You have modified the workload.</p>
<p><strong>Run strace inside the container via <code class="" data-line="">kubectl exec</code>.</strong> strace uses ptrace, which adds 50&ndash;100% CPU overhead to the traced process and is unavailable in hardened containers. You are tracing one process at a time with no cluster-wide view.</p>
<p><strong>Poll <code class="" data-line="">/proc</code> with a monitoring agent.</strong> Snapshot-based. Any event that happens between polls is invisible. A process that starts, does something, and exits between intervals is completely missed.</p>
<h3><span class="ez-toc-section" id="The_eBPF_approach"></span>The eBPF approach<span class="ez-toc-section-end"></span></h3>
<pre><code class="" data-line=""># Use a debug pod on the node — no changes to your workload
$ kubectl debug node/your-node -it --image=cilium/hubble-cli

# Real-time kernel events from every container on this node:
sys_enter_execve  pid=8821  comm=sh    args=[&quot;/bin/sh&quot;,&quot;-c&quot;,&quot;curl http://...&quot;]
sys_enter_connect pid=8821  comm=curl  dst=203.0.113.42:443
sys_enter_openat  pid=8821  comm=curl  path=/etc/passwd

# Something inside the pod spawned a shell, made an outbound connection,
# and read /etc/passwd — all visible without touching the pod.</code></pre>
<p>Real-time visibility. No overhead on your workload. Nothing restarted. Nothing modified. That is what eBPF makes possible.</p>
<hr />
<h2><span class="ez-toc-section" id="Tools_You_Are_Probably_Already_Running_on_eBPF"></span>Tools You Are Probably Already Running on eBPF<span class="ez-toc-section-end"></span></h2>
<p>eBPF is not a standalone product &mdash; it is the foundation that many tools in the cloud-native ecosystem are built on. You may already be running eBPF on your nodes without thinking about it explicitly.</p>
<table>
<thead>
<tr>
<th>Tool</th>
<th>What eBPF does for it</th>
<th>Without eBPF</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Cilium</strong></td>
<td>Replaces kube-proxy and iptables with kernel-level packet routing. 2&ndash;3&times; faster at scale.</td>
<td>iptables rules &mdash; linear lookup, degrades with service count</td>
</tr>
<tr>
<td><strong>Falco</strong></td>
<td>Watches every syscall in every container for security rule violations. Sub-millisecond detection.</td>
<td>Kernel module (risky) or ptrace (high overhead)</td>
</tr>
<tr>
<td><strong>Tetragon</strong></td>
<td>Runtime security enforcement &mdash; can kill a process or drop a network packet at the kernel level.</td>
<td>No practical alternative at this detection speed</td>
</tr>
<tr>
<td><strong>Datadog Agent</strong></td>
<td>Network performance monitoring and universal service monitoring without application code changes.</td>
<td>Language-specific agents injected into application code</td>
</tr>
<tr>
<td><strong>systemd</strong></td>
<td>cgroup resource accounting and network traffic control on your Linux nodes.</td>
<td>Legacy cgroup v1 interfaces with limited visibility</td>
</tr>
</tbody>
</table>
<hr />
<h2><span class="ez-toc-section" id="eBPF_vs_the_Old_Ways"></span>eBPF vs the Old Ways<span class="ez-toc-section-end"></span></h2>
<p>Before eBPF, getting deep visibility into a running Linux system meant choosing between three approaches, each with a significant trade-off:</p>
<table>
<thead>
<tr>
<th>Approach</th>
<th>Visibility</th>
<th>Cost</th>
<th>Production safe?</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Kernel modules</strong></td>
<td>Full kernel access</td>
<td>One bug = kernel panic. Version-specific, must recompile per kernel update.</td>
<td>No</td>
</tr>
<tr>
<td><strong>ptrace / strace</strong></td>
<td>One process at a time</td>
<td>50&ndash;100% CPU overhead on the traced process. Unusable in production.</td>
<td>No</td>
</tr>
<tr>
<td><strong>Polling /proc</strong></td>
<td>Snapshots only</td>
<td>Events between polls are invisible. Short-lived processes are missed entirely.</td>
<td>Partial</td>
</tr>
<tr>
<td><strong>eBPF</strong></td>
<td>Full kernel visibility</td>
<td>1&ndash;3% overhead. Verifier-guaranteed safety. Real-time stream, not polling.</td>
<td>Yes</td>
</tr>
</tbody>
</table>
<hr />
<h2><span class="ez-toc-section" id="Is_It_Safe_to_Run_in_Production"></span>Is It Safe to Run in Production?<span class="ez-toc-section-end"></span></h2>
<p>This is always the first question from any experienced Linux admin, and it is exactly the right question to ask. The answer is yes &mdash; and the reason is the <strong>BPF verifier</strong>.</p>
<p>Before any eBPF program is allowed to run on your node, the Linux kernel runs it through a built-in static safety analyser. This analyser examines every possible execution path and asks: could this program crash the kernel, loop forever, or access memory it should not?</p>
<p>If the answer is yes &mdash; or even <em>maybe</em> &mdash; the program is rejected at load time. It never runs.</p>
<blockquote>
<p><strong>This is fundamentally different from kernel modules.</strong> A kernel module loads immediately with no safety check. If it has a bug, you find out at runtime &mdash; usually as a kernel panic. An eBPF program that would cause a panic is rejected before it ever loads. The safety guarantee is mathematical, not hopeful.</p>
</blockquote>
<p>Episode 2 of this series covers the BPF verifier in full: what it checks, how it makes Cilium and Falco safe on your production nodes, and what questions to ask eBPF tool vendors about their implementation.</p>
<hr />
<h2><span class="ez-toc-section" id="Common_Misconceptions"></span>Common Misconceptions<span class="ez-toc-section-end"></span></h2>
<p><strong>eBPF is not a specific tool or product.</strong> It is a kernel technology &mdash; a platform. Cilium, Falco, Tetragon, and Pixie are tools built on top of it. When a vendor says &ldquo;we use eBPF&rdquo;, they mean they build on this kernel capability, not that they share a single implementation.</p>
<p><strong>eBPF is not only for networking.</strong> The Berkeley Packet Filter name suggests networking, but modern eBPF covers security, observability, performance profiling, and tracing. The networking origin is historical, not a limitation.</p>
<p><strong>eBPF is not only for Kubernetes.</strong> It works on any Linux system running kernel 4.9+, including bare metal servers, Docker hosts, and VMs. K8s is the most popular deployment target because of the observability challenges at scale, but it is not a requirement.</p>
<p><strong>You do not need to write eBPF programs to benefit from eBPF.</strong> Most Linux admins and DevOps engineers will use eBPF through tools like Cilium, Falco, and Datadog &mdash; never writing a line of BPF code themselves. This series covers the writing side later. Understanding what eBPF is makes you a significantly better user of these tools today.</p>
<hr />
<h2><span class="ez-toc-section" id="Kernel_Version_Requirements"></span>Kernel Version Requirements<span class="ez-toc-section-end"></span></h2>
<p>eBPF is a Linux kernel feature. The capabilities available depend directly on the kernel version running on your nodes. Run <code class="" data-line="">uname -r</code> on any node to check.</p>
<table>
<thead>
<tr>
<th>Kernel</th>
<th>What becomes available</th>
</tr>
</thead>
<tbody>
<tr>
<td><code class="" data-line="">4.9+</code></td>
<td>Basic eBPF support. Tracing, socket filtering. Most production systems today meet this minimum.</td>
</tr>
<tr>
<td><code class="" data-line="">5.4+</code></td>
<td>BTF (BPF Type Format) and CO-RE &mdash; programs that adapt to different kernel versions without recompile. Recommended minimum for production tooling.</td>
</tr>
<tr>
<td><code class="" data-line="">5.8+</code></td>
<td>Ring buffers for high-performance event streaming. Global variables. The target kernel for Cilium, Falco, and Tetragon full feature support.</td>
</tr>
<tr>
<td><code class="" data-line="">6.x</code></td>
<td>Open-coded iterators, improved verifier, LSM security enforcement hooks. Amazon Linux 2023 and Ubuntu 22.04+ ship 5.15 or newer and are fully eBPF-ready.</td>
</tr>
</tbody>
</table>
<blockquote>
<p><strong>EKS users:</strong> Amazon Linux 2023 AMIs ship with kernel 6.1+ and support the full modern eBPF feature set out of the box. If you are still on AL2, the migration also resolves the NetworkManager deprecation issues covered in the <a href="https://linuxcent.com/eks-1-33-networkmanager-systemd-networkd-migration-fix/">EKS 1.33 post</a>.</p>
</blockquote>
<hr />
<h2><span class="ez-toc-section" id="The_Bottom_Line"></span>The Bottom Line<span class="ez-toc-section-end"></span></h2>
<p>eBPF is the answer to a question Linux engineers have been asking for years: how do I get deep visibility into what is happening on my servers and Kubernetes nodes &mdash; without adding massive overhead, injecting sidecars, or risking a kernel panic?</p>
<p>The answer is: run small, safe programs at the kernel level, where everything is already visible. Let the BPF verifier guarantee those programs are safe before they run. Stream the results to your observability tools through shared memory maps.</p>
<p>The tools you already use &mdash; Cilium for networking, Falco for security, Datadog for APM &mdash; are built on this foundation. Understanding eBPF means understanding <em>why</em> those tools work the way they do, <em>what</em> they can and cannot see, and <em>how</em> to evaluate new tools that claim to use it.</p>
<blockquote>
<p>Every eBPF-based tool you run on your nodes passed through the BPF verifier before it touched your cluster. Episode 2 covers exactly what that means &mdash; and why it matters for your infrastructure decisions.</p>
</blockquote>
<hr />
<h2><span class="ez-toc-section" id="Further_Reading"></span>Further Reading<span class="ez-toc-section-end"></span></h2>
<ul>
<li><a href="https://ebpf.io/what-is-ebpf/" target="_blank" rel="noopener noreferrer">ebpf.io &mdash; What is eBPF? (official introduction)</a></li>
<li><a href="https://docs.cilium.io/en/stable/concepts/ebpf/" target="_blank" rel="noopener noreferrer">Cilium documentation: eBPF dataplane explained</a></li>
<li><a href="https://falco.org/docs/event-sources/kernel/" target="_blank" rel="noopener noreferrer">Falco: kernel event sources and eBPF driver</a></li>
<li><a href="https://isovalent.com/blog/post/ebpf-documentary/" target="_blank" rel="noopener noreferrer">Isovalent: the story behind eBPF</a></li>
<li><a href="https://www.brendangregg.com/ebpf.html" target="_blank" rel="noopener noreferrer">Brendan Gregg&rsquo;s eBPF reference page</a></li>
</ul>
<hr />
<p><em>Questions or corrections? Reach me on <a href="https://www.linkedin.com/in/vamshikrishnasanthapuri/" target="_blank" rel="noopener noreferrer">LinkedIn</a>. If this was useful, the full series index is on <a href="https://linuxcent.com">linuxcent.com</a> &mdash; search the <strong>eBPF Series</strong> tag for all episodes.</em></p>
<p><a class="a2a_button_mastodon" href="https://www.addtoany.com/add_to/mastodon?linkurl=https%3A%2F%2Flinuxcent.com%2Fwhat-is-ebpf-linux-kubernetes%2F&amp;linkname=What%20Is%20eBPF%3F%20A%20Plain-English%20Guide%20for%20Linux%20and%20Kubernetes%20Engineers" title="Mastodon" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_email" href="https://www.addtoany.com/add_to/email?linkurl=https%3A%2F%2Flinuxcent.com%2Fwhat-is-ebpf-linux-kubernetes%2F&amp;linkname=What%20Is%20eBPF%3F%20A%20Plain-English%20Guide%20for%20Linux%20and%20Kubernetes%20Engineers" title="Email" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_whatsapp" href="https://www.addtoany.com/add_to/whatsapp?linkurl=https%3A%2F%2Flinuxcent.com%2Fwhat-is-ebpf-linux-kubernetes%2F&amp;linkname=What%20Is%20eBPF%3F%20A%20Plain-English%20Guide%20for%20Linux%20and%20Kubernetes%20Engineers" title="WhatsApp" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_reddit" href="https://www.addtoany.com/add_to/reddit?linkurl=https%3A%2F%2Flinuxcent.com%2Fwhat-is-ebpf-linux-kubernetes%2F&amp;linkname=What%20Is%20eBPF%3F%20A%20Plain-English%20Guide%20for%20Linux%20and%20Kubernetes%20Engineers" title="Reddit" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_x" href="https://www.addtoany.com/add_to/x?linkurl=https%3A%2F%2Flinuxcent.com%2Fwhat-is-ebpf-linux-kubernetes%2F&amp;linkname=What%20Is%20eBPF%3F%20A%20Plain-English%20Guide%20for%20Linux%20and%20Kubernetes%20Engineers" title="X" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_linkedin" href="https://www.addtoany.com/add_to/linkedin?linkurl=https%3A%2F%2Flinuxcent.com%2Fwhat-is-ebpf-linux-kubernetes%2F&amp;linkname=What%20Is%20eBPF%3F%20A%20Plain-English%20Guide%20for%20Linux%20and%20Kubernetes%20Engineers" title="LinkedIn" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_copy_link" href="https://www.addtoany.com/add_to/copy_link?linkurl=https%3A%2F%2Flinuxcent.com%2Fwhat-is-ebpf-linux-kubernetes%2F&amp;linkname=What%20Is%20eBPF%3F%20A%20Plain-English%20Guide%20for%20Linux%20and%20Kubernetes%20Engineers" title="Copy Link" rel="nofollow noopener" target="_blank"></a><a class="a2a_dd addtoany_share_save addtoany_share" href="https://www.addtoany.com/share#url=https%3A%2F%2Flinuxcent.com%2Fwhat-is-ebpf-linux-kubernetes%2F&#038;title=What%20Is%20eBPF%3F%20A%20Plain-English%20Guide%20for%20Linux%20and%20Kubernetes%20Engineers" data-a2a-url="https://linuxcent.com/what-is-ebpf-linux-kubernetes/" data-a2a-title="What Is eBPF? A Plain-English Guide for Linux and Kubernetes Engineers"></a></p><p>The post <a href="https://linuxcent.com/what-is-ebpf-linux-kubernetes/">What Is eBPF? A Plain-English Guide for Linux and Kubernetes Engineers</a> appeared first on <a href="https://linuxcent.com">Linuxcent</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://linuxcent.com/what-is-ebpf-linux-kubernetes/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1429</post-id>	</item>
	</channel>
</rss>

<!--
Performance optimized by W3 Total Cache. Learn more: https://www.boldgrid.com/w3-total-cache/?utm_source=w3tc&utm_medium=footer_comment&utm_campaign=free_plugin

Page Caching using Disk: Enhanced 

Served from: linuxcent.com @ 2026-08-25 01:34:50 by W3 Total Cache
-->