<?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: Rey Shazni</title>
    <description>The latest articles on DEV Community by Rey Shazni (@reyshazni17).</description>
    <link>https://dev.to/reyshazni17</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4016238%2Fd2c46656-521b-4f56-aa0b-b4dffade1abb.JPG</url>
      <title>DEV Community: Rey Shazni</title>
      <link>https://dev.to/reyshazni17</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/reyshazni17"/>
    <language>en</language>
    <item>
      <title>Your Kubernetes Controller Is Silently Dead and Nobody Knows</title>
      <dc:creator>Rey Shazni</dc:creator>
      <pubDate>Sun, 05 Jul 2026 16:18:04 +0000</pubDate>
      <link>https://dev.to/reyshazni17/your-kubernetes-controller-is-silently-dead-and-nobody-knows-2fml</link>
      <guid>https://dev.to/reyshazni17/your-kubernetes-controller-is-silently-dead-and-nobody-knows-2fml</guid>
      <description>&lt;p&gt;Kubernetes controllers built on client-go have a silent failure mode that's easy to miss. When a projected ServiceAccount token becomes permanently invalid, your controller keeps running, passes health checks, shows Ready, and does absolutely nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;For in-cluster auth, client-go reads the projected ServiceAccount token from file via &lt;a href="https://github.com/kubernetes/client-go/blob/master/transport/token_source.go#L69" rel="noopener noreferrer"&gt;&lt;code&gt;CachedFileTokenSource&lt;/code&gt;&lt;/a&gt; and re-reads it on a fixed interval. But it does not check the response status. If the API server returns 401, the same token gets sent again. The error is logged, but the process never exits. The pod stays Running/Ready.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// client-go's default: log and retry. Forever.&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;DefaultWatchErrorHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Reflector&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// logs the error&lt;/span&gt;
    &lt;span class="c"&gt;// returns (triggering a retry with backoff)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Where I Found It
&lt;/h2&gt;

&lt;p&gt;During a Helm upgrade via ArgoCD on KAI-Scheduler (CNCF Sandbox GPU scheduler), the scheduler's ServiceAccount got deleted and recreated. Running pods still held tokens bound to the old SA UID. The scheduler kept retrying 401s indefinitely while showing Running/Ready. ArgoCD reported the application as Synced/Healthy the entire time.&lt;/p&gt;

&lt;p&gt;Full incident: &lt;a href="https://github.com/kai-scheduler/KAI-Scheduler/issues/1751" rel="noopener noreferrer"&gt;#1751&lt;/a&gt;. The 401 handling gap was tracked separately in &lt;a href="https://github.com/kai-scheduler/KAI-Scheduler/issues/1817" rel="noopener noreferrer"&gt;#1817&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Fix
&lt;/h2&gt;

&lt;p&gt;Submitted a &lt;a href="https://github.com/kai-scheduler/KAI-Scheduler/pull/1823" rel="noopener noreferrer"&gt;PR&lt;/a&gt; that adds a transport wrapper on &lt;code&gt;rest.Config&lt;/code&gt; that exits on 401:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;unauthorizedRoundTripper&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;rt&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RoundTripper&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;unauthorizedRoundTripper&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;RoundTrip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&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;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RoundTrip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusCode&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusUnauthorized&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InfraLogger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"API server returned 401 Unauthorized, exiting to trigger pod restart"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One call in the startup path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;clientconfig&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetConfigOrDie&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Wrap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wrapExitOnUnauthorized&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every client created from this config goes through the wrapper. On 401, the process exits, kubelet restarts it, fresh token is mounted.&lt;/p&gt;

&lt;p&gt;Why &lt;code&gt;os.Exit&lt;/code&gt; and not graceful shutdown? If your token is invalid, you can't release your leader election lease either (that's an API call too). The lease expires on its own in 15 seconds. &lt;code&gt;os.Exit&lt;/code&gt; matches what kube-scheduler upstream does for unrecoverable errors.&lt;/p&gt;




&lt;p&gt;If you build controllers on client-go, worth checking whether yours handles 401. Without an exit or a failed health probe, the pod stays Running and nothing self-heals.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>go</category>
      <category>opensource</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
