<?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: Sai Pisey</title>
    <description>The latest articles on DEV Community by Sai Pisey (@sai_pisey_02).</description>
    <link>https://dev.to/sai_pisey_02</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%2F4174831%2Fec21c07e-4849-4365-816e-6f52a37e2b60.png</url>
      <title>DEV Community: Sai Pisey</title>
      <link>https://dev.to/sai_pisey_02</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sai_pisey_02"/>
    <language>en</language>
    <item>
      <title>One header in the config. A thousand on the wire.</title>
      <dc:creator>Sai Pisey</dc:creator>
      <pubDate>Sat, 24 Oct 2026 10:22:08 +0000</pubDate>
      <link>https://dev.to/sai_pisey_02/one-header-in-the-config-a-thousand-on-the-wire-28ip</link>
      <guid>https://dev.to/sai_pisey_02/one-header-in-the-config-a-thousand-on-the-wire-28ip</guid>
      <description>&lt;p&gt;An Alloy user had one header configured on &lt;code&gt;pyroscope.scrape&lt;/code&gt;, an &lt;code&gt;X-Api-Key&lt;/code&gt;. About two hours in, profiling stopped. Their packet capture showed a single GET carrying roughly &lt;strong&gt;1,000 copies of that header&lt;/strong&gt;, and the target answering &lt;strong&gt;431 Request Header Fields Too Large&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The bug was in &lt;a href="https://github.com/prometheus/common" rel="noopener noreferrer"&gt;prometheus/common&lt;/a&gt;, the library behind &lt;code&gt;http_config&lt;/code&gt; in Prometheus, Alertmanager and the exporters. The reporter had already traced it to &lt;code&gt;cloneRequest&lt;/code&gt;. I wrote the upstream fix, &lt;a href="https://github.com/prometheus/common/pull/982" rel="noopener noreferrer"&gt;#982&lt;/a&gt;, which shipped in &lt;strong&gt;v0.72.1&lt;/strong&gt;.&lt;/p&gt;

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

&lt;p&gt;One header in the config, the same request sent four times:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;go run &lt;span class="nb"&gt;.&lt;/span&gt;   &lt;span class="c"&gt;# prometheus/common v0.71.0&lt;/span&gt;
&lt;span class="go"&gt;server saw X-Api-Key x1
server saw X-Api-Key x2
server saw X-Api-Key x3
server saw X-Api-Key x4

&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;go run &lt;span class="nb"&gt;.&lt;/span&gt;   &lt;span class="c"&gt;# prometheus/common v0.72.1&lt;/span&gt;
&lt;span class="go"&gt;server saw X-Api-Key x1
server saw X-Api-Key x1
server saw X-Api-Key x1
server saw X-Api-Key x1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhqkpjtik2q6bfuryi4bu.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhqkpjtik2q6bfuryi4bu.png" alt="The same request sent four times: on v0.71.0 the header count grows from 1 to 4, on v0.72.1 it stays at 1" width="800" height="185"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The copy that copied nothing
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;r2&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;new&lt;/span&gt;&lt;span class="p"&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="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;r2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;
&lt;span class="c"&gt;// Deep copy of the Header.&lt;/span&gt;
&lt;span class="n"&gt;maps&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Copy&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;Header&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The comment says deep copy. It isn't one. &lt;code&gt;*r2 = *r&lt;/code&gt; leaves both requests pointing at the same header map, and &lt;code&gt;maps.Copy&lt;/code&gt; copies that map into itself. Anything the library adds afterwards lands in the caller's request. &lt;code&gt;http_headers&lt;/code&gt; uses &lt;code&gt;Header.Add&lt;/code&gt;, so a request that gets reused picks up one more copy per send.&lt;/p&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fprtp82451bczkfau8tlx.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fprtp82451bczkfau8tlx.png" alt="A struct copy shares the header map; Header.Clone gives the clone its own map" width="799" height="223"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Checking your binaries
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;go version &lt;span class="nt"&gt;-m&lt;/span&gt; ./alloy | &lt;span class="nb"&gt;grep &lt;/span&gt;prometheus/common
&lt;span class="go"&gt;    dep github.com/prometheus/common    v0.71.0 h1:...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;v0.69.0 to v0.72.0 are affected. v0.72.1 or later is fixed. You only hit it with an affected version, an &lt;code&gt;http_headers&lt;/code&gt; block, and code that sends the same request object twice.&lt;/p&gt;

&lt;p&gt;Prometheus 3.15 pins the same broken version and never shows the bug, because an otelhttp wrapper clones each request first. The full post has the exposure table for five tools and the regression test that went in with the fix.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://saipisey.com/blog/prometheus-common-header-pileup" rel="noopener noreferrer"&gt;Read the full post on saipisey.com →&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>prometheus</category>
      <category>go</category>
      <category>observability</category>
      <category>devops</category>
    </item>
    <item>
      <title>One command tells you if your next Kubernetes upgrade will brick a node</title>
      <dc:creator>Sai Pisey</dc:creator>
      <pubDate>Sat, 17 Oct 2026 07:52:54 +0000</pubDate>
      <link>https://dev.to/sai_pisey_02/one-command-tells-you-if-your-next-kubernetes-upgrade-will-brick-a-node-4g06</link>
      <guid>https://dev.to/sai_pisey_02/one-command-tells-you-if-your-next-kubernetes-upgrade-will-brick-a-node-4g06</guid>
      <description>&lt;p&gt;Since &lt;strong&gt;v1.35&lt;/strong&gt;, the kubelet setting &lt;code&gt;failCgroupV1&lt;/code&gt; defaults to &lt;code&gt;true&lt;/code&gt;. On a node that still runs cgroup v1, the kubelet refuses to start, so the node goes &lt;code&gt;NotReady&lt;/code&gt; and stays there. On a control-plane node it is worse: the kubelet starts the API server and etcd as static pods, so after a reboot they do not come back either.&lt;/p&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzxrvc7756me990aegjxu.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzxrvc7756me990aegjxu.png" alt="After upgrading the kubelet to v1.35: a cgroup v2 node stays Ready, cgroup v1 nodes go NotReady" width="799" height="250"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;kubeadm warns you first: its &lt;code&gt;SystemVerification&lt;/code&gt; preflight check errors on &lt;code&gt;init&lt;/code&gt;, &lt;code&gt;join&lt;/code&gt; and &lt;code&gt;upgrade&lt;/code&gt; once the kubelet is v1.35 or later. Do not skip it with &lt;code&gt;--ignore-preflight-errors&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Under the hood, cgroup v1 and v2 lay out the same pod very differently, which is why one &lt;code&gt;stat&lt;/code&gt; call can tell them apart:&lt;/p&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fckihekhh4j8m7nnhzkhp.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fckihekhh4j8m7nnhzkhp.png" alt="cgroup v1 keeps one tree per controller; cgroup v2 keeps one unified tree" width="800" height="207"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Check one node
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;stat&lt;/span&gt; &lt;span class="nt"&gt;-fc&lt;/span&gt; %T /sys/fs/cgroup/
&lt;span class="go"&gt;cgroup2fs
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;cgroup2fs&lt;/code&gt;: cgroup v2. Nothing to do.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tmpfs&lt;/code&gt;: cgroup v1. After an upgrade to v1.35 or later, its kubelet will not start.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Check every node without SSH
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for &lt;/span&gt;n &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;kubectl get nodes &lt;span class="nt"&gt;-o&lt;/span&gt; name&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="gp"&gt;    printf "%s " "$&lt;/span&gt;n&lt;span class="s2"&gt;"
&lt;/span&gt;&lt;span class="gp"&gt;    kubectl debug "$&lt;/span&gt;&lt;span class="s2"&gt;n"&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; default &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nt"&gt;--profile&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;general &lt;span class="nt"&gt;--image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;busybox &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="go"&gt;      -- chroot /host stat -fc %T /sys/fs/cgroup
  done
node/worker-1 cgroup2fs
node/worker-2 tmpfs
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fowhpzyrpgcbv9ftsonsd.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fowhpzyrpgcbv9ftsonsd.png" alt="kubectl debug mounts the node at /host; chroot /host uses the node's GNU stat" width="800" height="221"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Keep the &lt;code&gt;chroot /host&lt;/code&gt;. Without it the command runs busybox's own &lt;code&gt;stat&lt;/code&gt;, which prints &lt;code&gt;UNKNOWN&lt;/code&gt; instead of &lt;code&gt;cgroup2fs&lt;/code&gt; on a healthy v2 node. I hit that while testing on a v1.35 cluster.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which nodes are likely on cgroup v1
&lt;/h2&gt;

&lt;p&gt;CentOS 7, RHEL 7 and 8, Ubuntu 18.04 and 20.04, and Amazon Linux 2 all default to cgroup v1. Self-managed kubeadm clusters on long-lived VMs are where this bites.&lt;/p&gt;

&lt;p&gt;The full post covers what a node needs for cgroup v2, which nodes can switch in place and which need a new OS image, how to check the kubelet and containerd cgroup drivers, and the &lt;code&gt;failCgroupV1: false&lt;/code&gt; escape hatch with its expiry date.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://saipisey.com/blog/cgroup-v1-kubelet-check" rel="noopener noreferrer"&gt;Read the full post on saipisey.com →&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>devops</category>
      <category>linux</category>
      <category>sre</category>
    </item>
    <item>
      <title>Your cluster says 3/3. It will still go dark with one zone.</title>
      <dc:creator>Sai Pisey</dc:creator>
      <pubDate>Sat, 10 Oct 2026 10:36:48 +0000</pubDate>
      <link>https://dev.to/sai_pisey_02/your-cluster-says-33-it-will-still-go-dark-with-one-zone-3l3k</link>
      <guid>https://dev.to/sai_pisey_02/your-cluster-says-33-it-will-still-go-dark-with-one-zone-3l3k</guid>
      <description>&lt;p&gt;Every deployment in this three-zone cluster reports ready:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;kubectl get deployments
&lt;span class="go"&gt;NAME            READY   UP-TO-DATE   AVAILABLE
checkout-api    3/3     3            3
session-store   1/1     1            1
web             3/3     3            3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now take &lt;code&gt;us-east-1a&lt;/code&gt; away:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;kubectl survive-zone
&lt;span class="gp"&gt;Losing us-east-1a  -&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;2 lost, 0 degraded, 1 impaired by a dependency
&lt;span class="go"&gt;  WORKLOAD       PLACEMENT                               VERDICT
  checkout-api   us-east-1a:3                            LOST
  session-store  us-east-1a:1                            LOST
  web            us-east-1a:1 us-east-1b:1 us-east-1c:1  IMPAIRED
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two workloads are simply gone. The third still has two healthy replicas, and stops serving anyway.&lt;/p&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpw04hszix8hzo5rcb1ah.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpw04hszix8hzo5rcb1ah.png" alt="Five of the seven pods sit in us-east-1a. Losing that zone takes checkout-api and session-store down outright and leaves web without its dependency." width="800" height="352"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Nobody made a mistake
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;checkout-api&lt;/code&gt; was scheduled while &lt;code&gt;us-east-1a&lt;/code&gt; was the only zone. The scheduler put all three replicas there, correctly. Two more zones were added later, and nothing moved them.&lt;/p&gt;

&lt;p&gt;That is not a bug. Topology spread constraints are applied when a pod is scheduled, and never checked again. The &lt;a href="https://kubernetes.io/docs/concepts/scheduling-eviction/topology-spread-constraints/#known-limitations" rel="noopener noreferrer"&gt;Kubernetes docs&lt;/a&gt; say it plainly: there is no guarantee they stay satisfied once pods move.&lt;/p&gt;

&lt;p&gt;So the manifests say multi-AZ, and the cluster quietly says something else.&lt;/p&gt;

&lt;h2&gt;
  
  
  The row a linter would pass
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;web&lt;/code&gt; is the interesting one. It is spread one replica per zone, which is exactly what a spread linter wants to see. It still goes down, because the only &lt;code&gt;session-store&lt;/code&gt; pod lives in the zone that died.&lt;/p&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7or2b822ftg412keyrsf.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7or2b822ftg412keyrsf.png" alt="All three web replicas depend on session-store, whose only replica is in us-east-1a. Two healthy web replicas still cannot serve." width="799" height="311"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Zone survivability is a property of the dependency graph, not of each workload on its own. &lt;code&gt;kubectl survive-zone&lt;/code&gt; follows each Service to its EndpointSlices to build that graph, so a workload can be marked impaired even when its own pods look perfect.&lt;/p&gt;

&lt;h2&gt;
  
  
  It will not hand you a fix that causes an outage
&lt;/h2&gt;

&lt;p&gt;Most zone fixes can make pods unschedulable. Tighten a spread constraint from &lt;code&gt;ScheduleAnyway&lt;/code&gt; to &lt;code&gt;DoNotSchedule&lt;/code&gt; and you can end up with replicas stuck in Pending, which is the outage you were trying to avoid.&lt;/p&gt;

&lt;p&gt;So &lt;code&gt;kubectl survive-zone fix&lt;/code&gt; only prints a patch after proving two things: that it is schedulable on your real nodes, using the upstream kube-scheduler's own Filter plugins, and that it actually survives the zone loss.&lt;/p&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmzigewydzqha8aedjrw2.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmzigewydzqha8aedjrw2.png" alt="A fix is printed only if it is schedulable and survives the zone loss. A PodDisruptionBudget is shown, and marked as not a fix." width="800" height="289"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  And it keeps watching
&lt;/h2&gt;

&lt;p&gt;Placement decays with nothing changed in git. A node drains at night, pods reschedule, and a workload that was spread now sits in one zone. Run as a Prometheus exporter, the tool turns that into an alert while the zone is still up.&lt;/p&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fb1y3y5jjke98xc6cb05m.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fb1y3y5jjke98xc6cb05m.png" alt="survive_workload_survives drops from 1 to 0 after two zones are cordoned, with no deploy and no change in git." width="799" height="335"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Two things the full post covers that surprised me
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;A node drain that hangs forever, behind a disruption budget that looks completely fine.&lt;/strong&gt; Budget arithmetic says the drain is safe. It is not, and the reason only shows up when you simulate the replacement pod against the real scheduler.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;An ordinary rolling update that quietly broke an enforced &lt;code&gt;maxSkew: 1&lt;/code&gt;.&lt;/strong&gt; I found it by accident while recording the demo, and the fix is a one-line field most spread constraints do not set.&lt;/p&gt;

&lt;p&gt;The full write-up also covers how every verdict is checked nightly against a real control plane in 200 randomised scenarios, and what the harness caught that no unit test did.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://saipisey.com/blog/kubectl-survive-zone" rel="noopener noreferrer"&gt;Read the full post on saipisey.com →&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Try it in under a minute. It is read-only and never writes to your cluster:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;kubectl krew &lt;span class="nb"&gt;install &lt;/span&gt;survive-zone
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;kubectl survive-zone
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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