<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Eunji</title>
    <description>The latest articles on DEV Community by Eunji (@bianbbc87).</description>
    <link>https://dev.to/bianbbc87</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3909179%2F37f1affe-3ab8-407b-aa3f-55f02dc61bf9.png</url>
      <title>DEV Community: Eunji</title>
      <link>https://dev.to/bianbbc87</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/bianbbc87"/>
    <language>en</language>
    <item>
      <title>Why Lowering ndots Breaks Alpine Pods (But Not Debian) — A Deep Dive into glibc vs musl Resolvers</title>
      <dc:creator>Eunji</dc:creator>
      <pubDate>Sat, 02 May 2026 15:05:50 +0000</pubDate>
      <link>https://dev.to/bianbbc87/why-lowering-ndots-breaks-alpine-pods-but-not-debian-a-deep-dive-into-glibc-vs-musl-resolvers-1lhb</link>
      <guid>https://dev.to/bianbbc87/why-lowering-ndots-breaks-alpine-pods-but-not-debian-a-deep-dive-into-glibc-vs-musl-resolvers-1lhb</guid>
      <description>&lt;p&gt;If you're running Alpine-based pods in Kubernetes and someone tells you to lower &lt;code&gt;ndots&lt;/code&gt; for better DNS performance — don't. Or at least, read this first.&lt;/p&gt;

&lt;p&gt;We had 5 DNS queries firing for every external domain lookup. The fix seemed obvious: drop &lt;code&gt;ndots:5&lt;/code&gt; to &lt;code&gt;ndots:2&lt;/code&gt;. An AI reviewer warned me it might break internal service resolution. The reasoning didn't hold up when I read the resolver code, so I went ahead — and broke things in a way I didn't expect.&lt;/p&gt;

&lt;p&gt;The AI was right about the symptom but wrong about the cause. The breakage is real, but it lives in &lt;strong&gt;libc&lt;/strong&gt;, not in the search algorithm.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lowering &lt;code&gt;ndots&lt;/code&gt; reduces DNS query amplification, but breaks internal service resolution &lt;strong&gt;on Alpine pods&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;The cause isn't CoreDNS or Kubernetes — it's that &lt;strong&gt;musl libc skips the search list when &lt;code&gt;dots ≥ ndots&lt;/code&gt;&lt;/strong&gt;, while glibc falls back gracefully.&lt;/li&gt;
&lt;li&gt;If you're on Alpine: switch base images, use FQDNs with a trailing dot, or roll out per-workload via &lt;code&gt;dnsConfig&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The starting point: 5 queries for one domain
&lt;/h2&gt;

&lt;p&gt;Every external DNS lookup in our cluster was producing 4-5 queries. This is well-known behavior — it's caused by the default &lt;code&gt;ndots:5&lt;/code&gt; combined with Kubernetes' three-entry search list.&lt;/p&gt;

&lt;p&gt;The path of least resistance is to lower &lt;code&gt;ndots&lt;/code&gt;. Most external domains have &lt;code&gt;dots ≥ 2&lt;/code&gt; (&lt;code&gt;google.com&lt;/code&gt;, &lt;code&gt;api.example.com&lt;/code&gt;), so &lt;code&gt;ndots:2&lt;/code&gt; skips the search-list traversal for them entirely.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;dnsConfig&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ndots&lt;/span&gt;
        &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before shipping the change, I asked an AI assistant to review it. It warned that internal service resolution might break, with this reasoning:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;With ndots:5 and a query for "my-svc.default" (1 dot):
  dots 1 &amp;lt; ndots 5 → search first
  my-svc.default.&amp;lt;ns&amp;gt;.svc.cluster.local  → NXDOMAIN
  my-svc.default.svc.cluster.local       → NOERROR  ✓

With ndots:1:
  dots 1 ≥ ndots 1 → original first
  my-svc.default                         → NXDOMAIN (doesn't exist externally)
  ... then what?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The "then what" was the question. I read the resolver source and concluded the AI was wrong: after the original query fails, the resolver should fall back to search-list traversal. &lt;strong&gt;The lookup should still succeed.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I was reading the wrong source.&lt;/p&gt;




&lt;h2&gt;
  
  
  The three config files that matter
&lt;/h2&gt;

&lt;p&gt;Before going deeper, the surface area of "Kubernetes DNS" lives in three files. Knowing which one controls what saves a lot of pain.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;File&lt;/th&gt;
&lt;th&gt;What it controls&lt;/th&gt;
&lt;th&gt;Where it lives&lt;/th&gt;
&lt;th&gt;Reload&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;Corefile&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Zones, plugin chain, fallthrough — &lt;strong&gt;all CoreDNS behavior&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;coredns&lt;/code&gt; ConfigMap → &lt;code&gt;/etc/coredns/Corefile&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Runtime&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;CoreDNS's &lt;code&gt;resolv.conf&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Upstream DNS that CoreDNS forwards to&lt;/td&gt;
&lt;td&gt;CoreDNS Pod's &lt;code&gt;/etc/resolv.conf&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Pod creation only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;App Pod's &lt;code&gt;resolv.conf&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;ndots&lt;/code&gt;, search list — the part this post is about&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;App Pod's &lt;code&gt;/etc/resolv.conf&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Pod creation only&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A Pod's DNS settings come from &lt;code&gt;spec.dnsPolicy&lt;/code&gt;, default &lt;code&gt;ClusterFirst&lt;/code&gt;, which inherits the pod's &lt;code&gt;resolv.conf&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nameserver 10.96.0.10
search &amp;lt;namespace&amp;gt;.svc.cluster.local svc.cluster.local cluster.local
options ndots:5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the file libc reads. And &lt;strong&gt;libc is the part that decides whether to walk the search list or skip it.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  How a query travels (and where the retry loop lives)
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm8oz5pb4rxkb5z1gml77.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm8oz5pb4rxkb5z1gml77.png" alt="A single external lookup with  raw `ndots:5` endraw  produces multiple queries. Each NXDOMAIN triggers a retry with the next entry from the search list. CoreDNS doesn't decide this — the libc resolver inside your Pod does." width="800" height="856"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The key thing to notice in the flow: when a Pod's resolver gets &lt;code&gt;NXDOMAIN&lt;/code&gt;, it retries with the next FQDN from the search list. &lt;strong&gt;That retry loop is where query amplification comes from.&lt;/strong&gt; Lowering &lt;code&gt;ndots&lt;/code&gt; is appealing because it skips this loop for high-dot names.&lt;/p&gt;

&lt;p&gt;CoreDNS itself doesn't care about &lt;code&gt;ndots&lt;/code&gt;. It just answers whatever FQDN arrives. The retry decision happens entirely on the client side, inside libc.&lt;/p&gt;




&lt;h2&gt;
  
  
  glibc vs musl: same input, different behavior
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6eax2ono1i8ggu03wvs1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6eax2ono1i8ggu03wvs1.png" alt="Same input, same  raw `ndots:2` endraw , same Kubernetes config. The only difference is the libc inside the container. glibc treats the search list as a fallback; musl treats it as mutually exclusive with  raw `dots ≥ ndots` endraw ." width="800" height="1492"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's the part the AI got right (in spirit) and I missed: &lt;strong&gt;the resolver isn't part of CoreDNS, Kubernetes, or even your app. It's the libc shipped in your container image.&lt;/strong&gt; Different libcs implement &lt;code&gt;search&lt;/code&gt;/&lt;code&gt;ndots&lt;/code&gt; differently.&lt;/p&gt;

&lt;h3&gt;
  
  
  glibc — falls back gracefully
&lt;/h3&gt;

&lt;p&gt;Distros: Debian, Ubuntu, CentOS, RHEL, Amazon Linux.&lt;/p&gt;

&lt;p&gt;When &lt;code&gt;dots ≥ ndots&lt;/code&gt;, glibc tries the original first. If that returns NXDOMAIN, &lt;strong&gt;it walks the search list anyway as a fallback.&lt;/strong&gt; One or two extra queries, but resolution succeeds.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;my-svc.default                         → NXDOMAIN
↓ search fallback
my-svc.default.&amp;lt;ns&amp;gt;.svc.cluster.local  → NXDOMAIN
my-svc.default.svc.cluster.local       → NOERROR  ✓
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The fallback logic lives in &lt;a href="https://github.com/bminor/glibc/blob/master/resolv/res_query.c" rel="noopener noreferrer"&gt;&lt;code&gt;__res_context_search()&lt;/code&gt;&lt;/a&gt;. The relevant part:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// dots ≥ ndots OR trailing dot → try as-is first&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dots&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;statp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;ndots&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;trailing_dot&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;__res_context_querydomain&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ret&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;trailing_dot&lt;/span&gt; &lt;span class="p"&gt;...)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ret&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;saved_herrno&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;h_errno&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;tried_as_is&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;// ... falls through to search loop below&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Run search list when at least one entry might apply&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;dots&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;statp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;RES_DEFNAMES&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dots&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;trailing_dot&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;statp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;RES_DNSRCH&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;domain_index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;done&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="n"&gt;domain_index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;dname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;__resolv_context_search_list&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;domain_index&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dname&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;ret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;__res_context_querydomain&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...);&lt;/span&gt;
        &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The critical detail: the as-is attempt and the search loop are &lt;strong&gt;sequential&lt;/strong&gt;, not exclusive. Failure of the first does not prevent the second.&lt;/p&gt;

&lt;h3&gt;
  
  
  musl — stops on the first miss
&lt;/h3&gt;

&lt;p&gt;Distros: Alpine.&lt;/p&gt;

&lt;p&gt;musl is intentionally minimal. When &lt;code&gt;dots ≥ ndots&lt;/code&gt;, it sets &lt;code&gt;*search = 0&lt;/code&gt; and &lt;strong&gt;never enters the search loop.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;my-svc.default            → NXDOMAIN                           ✗
End. No search attempted.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From &lt;a href="https://github.com/kraj/musl/blob/master/src/network/lookup_name.c" rel="noopener noreferrer"&gt;&lt;code&gt;name_from_dns_search()&lt;/code&gt;&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Count dots. If dots ≥ ndots OR trailing dot → zero out the search list.&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dots&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="sc"&gt;'.'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;dots&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dots&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;conf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ndots&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="sc"&gt;'.'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;search&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// ...&lt;/span&gt;

&lt;span class="c1"&gt;// Walk the search list, splitting on whitespace.&lt;/span&gt;
&lt;span class="c1"&gt;// When *search = 0 above: *p == 0, z == p, break on the first iteration.&lt;/span&gt;
&lt;span class="c1"&gt;// → search is attempted ZERO times.&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(;&lt;/span&gt; &lt;span class="n"&gt;isspace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;z&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;isspace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;// ... query combined FQDN&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Final fallback: query the original as-is, exactly once.&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;name_from_dns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;canon&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;family&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;conf&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Setting &lt;code&gt;*search = 0&lt;/code&gt; isn't a bug. It's deliberate. The next question is &lt;strong&gt;why&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why doesn't musl just add the fallback?
&lt;/h2&gt;

&lt;p&gt;This has come up on the musl mailing list more than once. Maintainer Rich Felker rejects it consistently. The clearest example is from &lt;a href="https://www.openwall.com/lists/musl/2019/09/13/4" rel="noopener noreferrer"&gt;Andrey Arapov's 2019 thread&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The ask:&lt;/strong&gt; A small DNS misconfiguration causes musl's resolver to stop on a single SERVFAIL. It doesn't even try the FQDN. Is this intentional?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Felker's answer:&lt;/strong&gt; "If a lookup ends in SERVFAIL, the result is indeterminate. That should be reported to the caller as an error, not silently fallen back from. Otherwise the lookup result depends on transient nameserver failures."
The principle is &lt;strong&gt;determinism&lt;/strong&gt;. The moment fallback is allowed, the same query can return different answers between runs. An attacker can induce transient failures to manipulate which answer wins. From day one, musl's stance has been: "we don't reproduce the dangerous behavior of other implementations."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're on musl and want to avoid this entirely: &lt;strong&gt;set &lt;code&gt;ndots:1&lt;/code&gt; and don't depend on short names.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is a values disagreement, not a bug. Both libcs are doing what they intended. The mismatch only becomes a Kubernetes problem because Kubernetes hands every Pod a search list and assumes the resolver will use it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Reproducing it with kind
&lt;/h2&gt;

&lt;p&gt;Four pods, two libcs, two &lt;code&gt;ndots&lt;/code&gt; values.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pod&lt;/th&gt;
&lt;th&gt;libc&lt;/th&gt;
&lt;th&gt;ndots&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;alpine-ndots5&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;musl&lt;/td&gt;
&lt;td&gt;5 (default)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;alpine-ndots2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;musl&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;debian-ndots5&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;glibc&lt;/td&gt;
&lt;td&gt;5 (default)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;debian-ndots2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;glibc&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Setup
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# kind-config.yaml&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Cluster&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kind.x-k8s.io/v1alpha4&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dns-poc&lt;/span&gt;
&lt;span class="na"&gt;nodes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;control-plane&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kind create cluster &lt;span class="nt"&gt;--config&lt;/span&gt; kind-config.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Patch CoreDNS to log every query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# coredns-log-patch.yaml&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ConfigMap&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;coredns&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kube-system&lt;/span&gt;
&lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Corefile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;.:53 {&lt;/span&gt;
        &lt;span class="s"&gt;log&lt;/span&gt;
        &lt;span class="s"&gt;errors&lt;/span&gt;
        &lt;span class="s"&gt;health { lameduck 5s }&lt;/span&gt;
        &lt;span class="s"&gt;ready&lt;/span&gt;
        &lt;span class="s"&gt;kubernetes cluster.local in-addr.arpa ip6.arpa {&lt;/span&gt;
           &lt;span class="s"&gt;pods insecure&lt;/span&gt;
           &lt;span class="s"&gt;fallthrough in-addr.arpa ip6.arpa&lt;/span&gt;
           &lt;span class="s"&gt;ttl 30&lt;/span&gt;
        &lt;span class="s"&gt;}&lt;/span&gt;
        &lt;span class="s"&gt;prometheus :9153&lt;/span&gt;
        &lt;span class="s"&gt;forward . /etc/resolv.conf { max_concurrent 1000 }&lt;/span&gt;
        &lt;span class="s"&gt;cache 30&lt;/span&gt;
        &lt;span class="s"&gt;loop&lt;/span&gt;
        &lt;span class="s"&gt;reload&lt;/span&gt;
        &lt;span class="s"&gt;loadbalance&lt;/span&gt;
    &lt;span class="s"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; coredns-log-patch.yaml
kubectl rollout restart deployment/coredns &lt;span class="nt"&gt;-n&lt;/span&gt; kube-system
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The four test pods (full manifest &lt;a href="https://bianbbc87.github.io/posts/coredns-01/" rel="noopener noreferrer"&gt;in the original Korean post&lt;/a&gt;). Key bits:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# alpine-ndots2: musl + ndots:2&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;dnsConfig&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ndots&lt;/span&gt;
        &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2"&lt;/span&gt;
  &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;shell&lt;/span&gt;
      &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;alpine:3.20&lt;/span&gt;
      &lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Test: resolve &lt;code&gt;kubernetes.default.svc&lt;/code&gt; (2 dots)
&lt;/h3&gt;

&lt;p&gt;This name has 2 dots. Under &lt;code&gt;ndots:5&lt;/code&gt;, &lt;code&gt;dots &amp;lt; ndots&lt;/code&gt; → search first. Under &lt;code&gt;ndots:2&lt;/code&gt;, &lt;code&gt;dots ≥ ndots&lt;/code&gt; → original first. &lt;strong&gt;The libc difference only surfaces in the &lt;code&gt;ndots:2&lt;/code&gt; case.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;kubernetes.default.svc
&lt;span class="k"&gt;for &lt;/span&gt;p &lt;span class="k"&gt;in &lt;/span&gt;alpine-ndots5 alpine-ndots2 debian-ndots5 debian-ndots2&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s2"&gt;"===== [%s] =====&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$p&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  kubectl &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$p&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; sh &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"getent hosts &lt;/span&gt;&lt;span class="nv"&gt;$NAME&lt;/span&gt;&lt;span class="s2"&gt;; echo exit=&lt;/span&gt;&lt;span class="se"&gt;\$&lt;/span&gt;&lt;span class="s2"&gt;?"&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Result
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;===== [alpine-ndots5] =====
10.96.0.1       kubernetes.default.svc.cluster.local
exit=0

===== [alpine-ndots2] =====
exit=2                          # ← musl, no search fallback → fails

===== [debian-ndots5] =====
10.96.0.1       kubernetes.default.svc.cluster.local
exit=0

===== [debian-ndots2] =====
10.96.0.1       kubernetes.default.svc.cluster.local
exit=0                          # ← glibc, NXDOMAIN then search fallback → succeeds
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same query. Same cluster. Same &lt;code&gt;ndots:2&lt;/code&gt;. The only thing that changed is the libc.&lt;/p&gt;

&lt;h3&gt;
  
  
  CoreDNS logs confirm it
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl logs &lt;span class="nt"&gt;-n&lt;/span&gt; kube-system &lt;span class="nt"&gt;-l&lt;/span&gt; k8s-app&lt;span class="o"&gt;=&lt;/span&gt;kube-dns &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="nt"&gt;--tail&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;20 &lt;span class="nt"&gt;--prefix&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;alpine-ndots2&lt;/code&gt;&lt;/strong&gt; — only the original name arrives at CoreDNS. No search-expanded queries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;... kubernetes.default.svc.   AAAA  NXDOMAIN
... kubernetes.default.svc.   A     NXDOMAIN
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;debian-ndots2&lt;/code&gt;&lt;/strong&gt; — original first, then the entire search list, then success:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;... kubernetes.default.svc.                                A  NXDOMAIN
... kubernetes.default.svc.default.svc.cluster.local.      A  NXDOMAIN
... kubernetes.default.svc.svc.cluster.local.              A  NXDOMAIN
... kubernetes.default.svc.cluster.local.                  A  NOERROR  10.96.0.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is exactly what the source code predicted. musl exits the search loop on the first iteration; glibc walks every entry.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# cleanup&lt;/span&gt;
kind delete cluster &lt;span class="nt"&gt;--name&lt;/span&gt; dns-poc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Resolver behavior, side by side
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Condition&lt;/th&gt;
&lt;th&gt;glibc&lt;/th&gt;
&lt;th&gt;musl&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;dots &amp;lt; ndots&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;search first → on failure, original&lt;/td&gt;
&lt;td&gt;search first → on failure, original&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;dots ≥ ndots&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;original first → on failure, &lt;strong&gt;search fallback&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;original only → on failure, &lt;strong&gt;stop&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Under the default &lt;code&gt;ndots:5&lt;/code&gt;, most names have fewer than 5 dots, so both libcs try search first and the difference doesn't surface. &lt;strong&gt;The moment you lower &lt;code&gt;ndots&lt;/code&gt;, more names cross into &lt;code&gt;dots ≥ ndots&lt;/code&gt; territory — and that's where musl's missing fallback turns into a real outage.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What to do about it
&lt;/h2&gt;

&lt;p&gt;If you want to lower &lt;code&gt;ndots&lt;/code&gt; and you have any musl-based workloads:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Reconsider your base image.&lt;/strong&gt; &lt;code&gt;alpine&lt;/code&gt; → &lt;code&gt;debian-slim&lt;/code&gt; or &lt;code&gt;distroless&lt;/code&gt;. The biggest hammer, but it solves the class of problem, not just this one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use FQDNs at the application level.&lt;/strong&gt; &lt;code&gt;my-svc.default.svc.cluster.local.&lt;/code&gt; (with the trailing dot) skips the search list regardless of libc.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Roll out per-workload.&lt;/strong&gt; Apply &lt;code&gt;dnsConfig&lt;/code&gt; to specific deployments first, not the whole cluster.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run NodeLocal DNSCache in parallel.&lt;/strong&gt; Independent of &lt;code&gt;ndots&lt;/code&gt;, a cache layer dramatically cuts CoreDNS load and softens the cost of the search loop on glibc workloads.
## The bigger lesson&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The thing I keep coming back to: &lt;strong&gt;the abstraction you're tuning&lt;/strong&gt; (Kubernetes' &lt;code&gt;ndots&lt;/code&gt;) &lt;strong&gt;and the layer where the behavior actually lives&lt;/strong&gt; (libc resolver) can be miles apart. The Kubernetes docs talk about &lt;code&gt;ndots&lt;/code&gt;. The Pod spec exposes &lt;code&gt;ndots&lt;/code&gt;. CoreDNS configures things adjacent to &lt;code&gt;ndots&lt;/code&gt;. And none of them are the layer that decides what happens when &lt;code&gt;dots ≥ ndots&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The AI reviewer wasn't wrong to flag the risk. It just couldn't see one layer down. Neither could I, until the test pods told me.&lt;/p&gt;

&lt;p&gt;When something in a layered system behaves unexpectedly, "why" usually doesn't have a clean answer at the layer you're operating in. Tracing the call all the way down to the C source is, surprisingly often, faster than reading another blog post.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published in Korean on &lt;a href="https://bianbbc87.github.io/posts/coredns-01/" rel="noopener noreferrer"&gt;my blog&lt;/a&gt;. Part 2 will cover NodeLocal DNSCache as an alternative path — getting most of the latency win without touching &lt;code&gt;ndots&lt;/code&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/" rel="noopener noreferrer"&gt;DNS for Services and Pods (Kubernetes docs)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://kubernetes.io/docs/tasks/administer-cluster/dns-debugging-resolution/" rel="noopener noreferrer"&gt;Kubernetes DNS debugging guide&lt;/a&gt; — includes warnings for Alpine 3.17 and earlier&lt;/li&gt;
&lt;li&gt;&lt;a href="https://wiki.musl-libc.org/functional-differences-from-glibc.html" rel="noopener noreferrer"&gt;musl wiki — Functional differences from glibc&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pracucci.com/kubernetes-dns-resolution-ndots-options-and-why-it-may-affect-application-performances.html" rel="noopener noreferrer"&gt;Pracucci — ndots:5 and application performance&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://kubernetes.io/docs/tasks/administer-cluster/nodelocaldns/" rel="noopener noreferrer"&gt;NodeLocal DNSCache&lt;/a&gt;
The fuller archaeology — every relevant GitHub issue across CoreDNS, kubernetes/dns, cert-manager, openwhisk, kind, and others — is in &lt;a href="https://bianbbc87.github.io/posts/coredns-01/" rel="noopener noreferrer"&gt;the original Korean post's appendix&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>kubernetes</category>
      <category>devops</category>
      <category>networking</category>
    </item>
  </channel>
</rss>
