<?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>CoreDNS Archives - Linuxcent</title>
	<atom:link href="https://linuxcent.com/tag/coredns/feed/" rel="self" type="application/rss+xml" />
	<link>https://linuxcent.com/tag/coredns/</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>CoreDNS Archives - Linuxcent</title>
	<link>https://linuxcent.com/tag/coredns/</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>EKS 1.33 Upgrade Blocker: Fixing Dead Nodes &#038; NetworkManager on Rocky Linux</title>
		<link>https://linuxcent.com/eks-1-33-networkmanager-systemd-networkd-migration-fix/</link>
					<comments>https://linuxcent.com/eks-1-33-networkmanager-systemd-networkd-migration-fix/#respond</comments>
		
		<dc:creator><![CDATA[Vamshi Krishna Santhapuri]]></dc:creator>
		<pubDate>Tue, 17 Feb 2026 19:42:18 +0000</pubDate>
				<category><![CDATA[Bash]]></category>
		<category><![CDATA[Devops]]></category>
		<category><![CDATA[Linux Tutorials]]></category>
		<category><![CDATA[SRE]]></category>
		<category><![CDATA[Troubleshooting]]></category>
		<category><![CDATA[AMI Build]]></category>
		<category><![CDATA[AWS EKS]]></category>
		<category><![CDATA[AWS networking]]></category>
		<category><![CDATA[cloud-init]]></category>
		<category><![CDATA[CoreDNS]]></category>
		<category><![CDATA[Kubernetes 1.33]]></category>
		<category><![CDATA[NetworkManager]]></category>
		<category><![CDATA[Packer]]></category>
		<category><![CDATA[resolv.conf]]></category>
		<category><![CDATA[Rocky Linux EKS]]></category>
		<category><![CDATA[systemd-networkd]]></category>
		<category><![CDATA[systemd-networkd migration]]></category>
		<category><![CDATA[systemd-resolved]]></category>
		<category><![CDATA[VPC]]></category>
		<guid isPermaLink="false">https://linuxcent.com/?p=1400</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"> 5</span> <span class="rt-label rt-postfix">minutes</span></span>The EKS 1.33+ NetworkManager Trap: A Complete systemd-networkd Migration Guide for Rocky &#038; Alma Linux TL;DR: The Blocker: Upgrading to EKS 1.33+ is breaking worker nodes, especially on free community distributions like Rocky Linux and AlmaLinux. Boot times are spiking past 6 minutes, and nodes are failing to get IPs. The Root Cause: AWS is ... <a title="EKS 1.33 Upgrade Blocker: Fixing Dead Nodes &#038; NetworkManager on Rocky Linux" class="read-more" href="https://linuxcent.com/eks-1-33-networkmanager-systemd-networkd-migration-fix/" aria-label="Read more about EKS 1.33 Upgrade Blocker: Fixing Dead Nodes &#038; NetworkManager on Rocky Linux">Read more</a></p>
<p>The post <a href="https://linuxcent.com/eks-1-33-networkmanager-systemd-networkd-migration-fix/">EKS 1.33 Upgrade Blocker: Fixing Dead Nodes &#038; NetworkManager on Rocky Linux</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"> 5</span> <span class="rt-label rt-postfix">minutes</span></span><h1>The EKS 1.33+ NetworkManager Trap: A Complete systemd-networkd Migration Guide for Rocky &#038; Alma Linux</h1>
<h2>TL;DR:</h2>
<ul>
<li><strong>The Blocker:</strong> Upgrading to EKS 1.33+ is breaking worker nodes, especially on free community distributions like Rocky Linux and AlmaLinux. Boot times are spiking past 6 minutes, and nodes are failing to get IPs.</li>
<li><strong>The Root Cause:</strong> AWS is deprecating <code class="" data-line="">NetworkManager</code> in favor of <code class="" data-line="">systemd-networkd</code>. However, ripping out NetworkManager can leave stale VPC IPs in <code class="" data-line="">/etc/resolv.conf</code>. Combined with the <code class="" data-line="">systemd-resolved</code> stub listener (<code class="" data-line="">127.0.0.53</code>) and a few configuration missteps, it causes a total internal DNS collapse where CoreDNS pods crash and burn.</li>
<li><strong>The Subtext:</strong> AWS is pushing this modern networking standard hard. Subtly, this acts as a major drawback for Rocky/Alma AMIs, silently steering frustrated engineers toward Amazon Linux 2023 (AL2023) as the &#8220;easy&#8221; way out.</li>
<li><strong>The &#8220;Super Hack&#8221;:</strong> Automate the clean removal of NetworkManager, bypass the DNS stub listener by symlinking <code class="" data-line="">/etc/resolv.conf</code> directly to the <code class="" data-line="">systemd</code> uplink, and enforce strict state validation during the AMI build.</li>
</ul>
<hr>
<p>If you’ve been in the DevOps and SRE space long enough, you know that vendor upgrades rarely go exactly as planned. But lately, if you are running enterprise Linux distributions like Rocky Linux or AlmaLinux on AWS EKS, you might have noticed the ground silently shifting beneath your feet.</p>
<p>With the push to EKS 1.33+, AWS is mandating a shift toward modern, cloud-native networking standards. Specifically, they are phasing out the legacy <code class="" data-line="">NetworkManager</code> in favor of <code class="" data-line="">systemd-networkd</code>.</p>
<p>While this makes sense on paper, the transition for community distributions has been incredibly painful. AWS support couldn&#8217;t resolve our issues, and my SRE team had practically given up, officially halting our EKS upgrade process. It’s hard not to notice that this massive, undocumented friction in Rocky Linux and AlmaLinux conveniently positions AWS&#8217;s own Amazon Linux 2023 (AL2023) as the path of least resistance.</p>
<p>I’m hoping the incredible maintainers at free distributions like Rocky Linux and AlmaLinux take note of this architectural shift. But until the official AMIs catch up, we have to fix it ourselves. Here is the exact breakdown of the cascading failure that brought our clusters to their knees, and the &#8220;super hack&#8221; script we used to fix it.</p>
<h2>The Investigation: A Cascading SRE Failure</h2>
<p>When our EKS 1.33+ worker nodes started booting with 6+ minute latencies or outright failing to join the cluster, I pulled apart our Rocky Linux AMIs to monitor the network startup sequence. What I found was a classic cascading failure of services, stale data, and human error.</p>
<h3>Step 1: The Race Condition</h3>
<p>Initially, the problem was a violent tug-of-war. <code class="" data-line="">NetworkManager</code> was not correctly disabled by default, and <code class="" data-line="">cloud-init</code> was still trying to invoke it. This conflicted directly with <code class="" data-line="">systemd-networkd</code>, paralyzing the network stack during boot. To fix this, we initially disabled the <code class="" data-line="">NetworkManager</code> service and removed it from <code class="" data-line="">cloud-init</code>.</p>
<h3>Step 2: The Stale Data Landmine</h3>
<p>Here is where the trap snapped shut. Because <code class="" data-line="">NetworkManager</code> was historically the primary service responsible for dynamically generating and updating <code class="" data-line="">/etc/resolv.conf</code>, completely disabling it stopped that file from being updated.</p>
<p>When we baked the new AMI via Packer, <code class="" data-line="">/etc/resolv.conf</code> was orphaned and preserved the old configuration—specifically, a stale <code class="" data-line="">.2</code> VPC IP address from the temporary subnet where the AMI build ran.</p>
<h3>Step 3: The Human Element</h3>
<p>We&#8217;ve all been there: during a stressful outage, wires get crossed. While troubleshooting the dead nodes, one of our SREs mistakenly stopped the <code class="" data-line="">systemd-resolved</code> service entirely, thinking it was conflicting with something else.</p>
<h3>Step 4: Total DNS Collapse</h3>
<p>When the new AMI booted up and joined the EKS node group, the environment was a disaster zone:</p>
<ol>
<li><code class="" data-line="">NetworkManager</code> was dead (intentional).</li>
<li><code class="" data-line="">systemd-resolved</code> was stopped (accidental).</li>
<li><code class="" data-line="">/etc/resolv.conf</code> contained a dead, stale IP address from a completely different subnet.</li>
</ol>
<p>When <code class="" data-line="">kubelet</code> started, it dutifully read the host&#8217;s broken <code class="" data-line="">/etc/resolv.conf</code> and passed it up to CoreDNS. CoreDNS attempted to route traffic to the stale IP, failed, and started crash-looping. Internal DNS resolution (<code class="" data-line="">pod.namespace.svc.cluster.local</code>) totally collapsed. The cluster was dead in the water.</p>
<figure class="wp-block-image size-large">
<img fetchpriority="high" decoding="async" src="[https://linuxcent.com/wp-content/uploads/2026/02/Untitled-Diagram-EKS-Cascading-Failure.jpg](https://linuxcent.com/wp-content/uploads/2026/02/Untitled-Diagram-EKS-Cascading-Failure.jpg)" alt="Flowchart showing the cascading DNS failure in EKS worker nodes" width="221" height="661" class="alignnone size-full wp-image-1410" srcset="https://linuxcent.com/wp-content/uploads/2026/02/Untitled-Diagram-EKS-Cascading-Failure.jpg 221w, https://linuxcent.com/wp-content/uploads/2026/02/Untitled-Diagram-EKS-Cascading-Failure-100x300.jpg 100w" sizes="(max-width: 221px) 100vw, 221px" /><figcaption>The perfect storm: How stale data and disabled services led to a total CoreDNS collapse.</figcaption><hr>
<h2>Linux Internals: How systemd Manages DNS (And Why CoreDNS Breaks)</h2>
<p>To understand how to permanently fix this, we need to look at how <code class="" data-line="">systemd</code> actually handles DNS under the hood. When using <code class="" data-line="">systemd-networkd</code>, <code class="" data-line="">resolv.conf</code> management is handled through a strict partnership with <code class="" data-line="">systemd-resolved</code>. </p>
<figure class="wp-block-image size-large">
<img decoding="async" src="[https://linuxcent.com/wp-content/uploads/2026/02/Untitled-Diagram-Page-2-1024x299.jpg](https://linuxcent.com/wp-content/uploads/2026/02/Untitled-Diagram-Page-2-1024x299.jpg)" alt="Architecture diagram of systemd-networkd and systemd-resolved D-Bus communication" width="1024" height="299" class="alignnone size-large wp-image-1411" srcset="https://linuxcent.com/wp-content/uploads/2026/02/Untitled-Diagram-Page-2-1024x299.jpg 1024w, https://linuxcent.com/wp-content/uploads/2026/02/Untitled-Diagram-Page-2-300x87.jpg 300w, https://linuxcent.com/wp-content/uploads/2026/02/Untitled-Diagram-Page-2-768x224.jpg 768w, https://linuxcent.com/wp-content/uploads/2026/02/Untitled-Diagram-Page-2.jpg 1101w" sizes="(max-width: 1024px) 100vw, 1024px" /><figcaption>How systemd collects network data and the critical symlink choice that dictates EKS DNS health.</figcaption><p>Here is how the flow works: <code class="" data-line="">systemd-networkd</code> collects network and DNS information (from DHCP, Router Advertisements, or static configs) and pushes it to <code class="" data-line="">systemd-resolved</code> via D-Bus. To manage your DNS resolution effectively, you must configure the <code class="" data-line="">/etc/resolv.conf</code> symbolic link to match your desired mode of operation. You have three choices:</p>
<h3>1. The &#8220;Recommended&#8221; Local DNS Stub (The EKS Killer)</h3>
<p>By default, systemd recommends using <code class="" data-line="">systemd-resolved</code> as a local DNS cache and manager, providing features like DNS-over-TLS and mDNS.</p>
<ul>
<li><strong>The Symlink:</strong> <code class="" data-line="">ln -sf /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf</code></li>
<li><strong>Contents:</strong> Points to <code class="" data-line="">127.0.0.53</code> as the only nameserver.</li>
<li><strong>The Problem:</strong> This is a disaster for Kubernetes. If Kubelet passes <code class="" data-line="">127.0.0.53</code> to CoreDNS, CoreDNS queries its own loopback interface inside the pod network namespace, blackholing all cluster DNS.</li>
</ul>
<h3>2. Direct Uplink DNS (The &#8220;Super Hack&#8221; Solution)</h3>
<p>This mode bypasses the local stub entirely. The system lists the actual upstream DNS servers (e.g., your AWS VPC nameservers) discovered by <code class="" data-line="">systemd-networkd</code> directly in the file.</p>
<ul>
<li><strong>The Symlink:</strong> <code class="" data-line="">ln -sf /run/systemd/resolve/resolv.conf /etc/resolv.conf</code></li>
<li><strong>Contents:</strong> Lists all actual VPC DNS servers currently known to <code class="" data-line="">systemd-resolved</code>.</li>
<li><strong>The Benefit:</strong> CoreDNS gets the real AWS VPC nameservers, allowing it to route external queries correctly while managing internal cluster resolution perfectly.</li>
</ul>
<h3>3. Static Configuration (Manual)</h3>
<p>If you want to manage DNS manually without systemd modifying the file, you break the symlink and create a regular file (<code class="" data-line="">rm /etc/resolv.conf</code>). While <code class="" data-line="">systemd-networkd</code> still receives DNS info from DHCP, it won&#8217;t touch this file. (Not ideal for dynamic cloud environments).</p>
<hr>
<h2>The Solution: A Surgical systemd Cutover</h2>
<p>Knowing the internals, the path forward is clear. We needed to not only remove the legacy stack but explicitly rewire the DNS resolution to the <strong>Direct Uplink</strong> to prevent the stale data trap and bypass the notorious <code class="" data-line="">127.0.0.53</code> stub listener.</p>
<p>Here is the exact state we achieved:</p>
<ol>
<li><strong>Lock down <code class="" data-line="">cloud-init</code></strong> so it stops triggering legacy network services.</li>
<li><strong>Completely mask <code class="" data-line="">NetworkManager</code></strong> to ensure it never wakes up.</li>
<li><strong>Ensure <code class="" data-line="">systemd-resolved</code> is enabled and running</strong>, but with the <code class="" data-line="">DNSStubListener</code> explicitly disabled (<code class="" data-line="">DNSStubListener=no</code>) so it doesn&#8217;t conflict with anything.</li>
<li><strong>Destroy the stale <code class="" data-line="">/etc/resolv.conf</code></strong> and create a symlink to the <strong>Direct Uplink</strong> (<code class="" data-line="">ln -sf /run/systemd/resolve/resolv.conf /etc/resolv.conf</code>).</li>
<li><strong>Reconfigure and restart <code class="" data-line="">systemd-networkd</code></strong>.</li>
</ol>
<blockquote><p>
<strong>Pro-Tip for Debugging:</strong> To ensure <code class="" data-line="">systemd-networkd</code> is successfully pushing DNS info to the resolver, verify your <code class="" data-line="">.network</code> files in <code class="" data-line="">/etc/systemd/network/</code>. Ensure <code class="" data-line="">UseDNS=yes</code> (which is the default) is set in the <code class="" data-line="">[DHCPv4]</code> section. You can always run <code class="" data-line="">resolvectl status</code> to see exactly which DNS servers are currently assigned to each interface over D-Bus!
</p></blockquote>
<h2>The Automation: Production AMI Prep Script</h2>
<p>Manual hacks are great for debugging, but SRE is about repeatable automation. We&#8217;ve open-sourced the <code class="" data-line="">eks-production-ami-prep.sh</code> script to handle this cutover automatically during your Packer or Image Builder pipeline. It standardizes the cutover, wipes out the stale data, and includes a strict validation suite.</p>
<div class="wp-block-buttons">
<div class="wp-block-button"><a class="wp-block-button__link" href="//github.com/rrskris/NetworkManager-systemd-networkd-config/blob/main/eks-production-ami-prep.sh" target="_blank" rel="noreferrer noopener">View Migration Script on GitHub</a></div>
</div>
<p></br></p>
<h2>The Results</h2>
<p>By actively taking control of the <code class="" data-line="">systemd</code> stack and ensuring <code class="" data-line="">/etc/resolv.conf</code> was dynamically linked rather than statically abandoned, we completely unblocked our EKS 1.33+ upgrade.</p>
<p>More impressively, <strong>our system bootup time dropped from a crippling 6+ minutes down to under 2 minutes.</strong> We shouldn&#8217;t have to abandon fantastic, free enterprise distributions just because a cloud provider shifts their networking paradigm. If your team is struggling with AWS EKS upgrades on Rocky Linux or AlmaLinux, integrate this automation into your pipeline and get your clusters back in the fast lane.</p>
<p><a class="a2a_button_mastodon" href="https://www.addtoany.com/add_to/mastodon?linkurl=https%3A%2F%2Flinuxcent.com%2Feks-1-33-networkmanager-systemd-networkd-migration-fix%2F&amp;linkname=EKS%201.33%20Upgrade%20Blocker%3A%20Fixing%20Dead%20Nodes%20%26%20NetworkManager%20on%20Rocky%20Linux" 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%2Feks-1-33-networkmanager-systemd-networkd-migration-fix%2F&amp;linkname=EKS%201.33%20Upgrade%20Blocker%3A%20Fixing%20Dead%20Nodes%20%26%20NetworkManager%20on%20Rocky%20Linux" 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%2Feks-1-33-networkmanager-systemd-networkd-migration-fix%2F&amp;linkname=EKS%201.33%20Upgrade%20Blocker%3A%20Fixing%20Dead%20Nodes%20%26%20NetworkManager%20on%20Rocky%20Linux" 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%2Feks-1-33-networkmanager-systemd-networkd-migration-fix%2F&amp;linkname=EKS%201.33%20Upgrade%20Blocker%3A%20Fixing%20Dead%20Nodes%20%26%20NetworkManager%20on%20Rocky%20Linux" 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%2Feks-1-33-networkmanager-systemd-networkd-migration-fix%2F&amp;linkname=EKS%201.33%20Upgrade%20Blocker%3A%20Fixing%20Dead%20Nodes%20%26%20NetworkManager%20on%20Rocky%20Linux" 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%2Feks-1-33-networkmanager-systemd-networkd-migration-fix%2F&amp;linkname=EKS%201.33%20Upgrade%20Blocker%3A%20Fixing%20Dead%20Nodes%20%26%20NetworkManager%20on%20Rocky%20Linux" 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%2Feks-1-33-networkmanager-systemd-networkd-migration-fix%2F&amp;linkname=EKS%201.33%20Upgrade%20Blocker%3A%20Fixing%20Dead%20Nodes%20%26%20NetworkManager%20on%20Rocky%20Linux" 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%2Feks-1-33-networkmanager-systemd-networkd-migration-fix%2F&#038;title=EKS%201.33%20Upgrade%20Blocker%3A%20Fixing%20Dead%20Nodes%20%26%20NetworkManager%20on%20Rocky%20Linux" data-a2a-url="https://linuxcent.com/eks-1-33-networkmanager-systemd-networkd-migration-fix/" data-a2a-title="EKS 1.33 Upgrade Blocker: Fixing Dead Nodes &amp; NetworkManager on Rocky Linux"></a></p><p>The post <a href="https://linuxcent.com/eks-1-33-networkmanager-systemd-networkd-migration-fix/">EKS 1.33 Upgrade Blocker: Fixing Dead Nodes &#038; NetworkManager on Rocky Linux</a> appeared first on <a href="https://linuxcent.com">Linuxcent</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://linuxcent.com/eks-1-33-networkmanager-systemd-networkd-migration-fix/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1400</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-22 11:33:50 by W3 Total Cache
-->