<?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: Roy</title>
    <description>The latest articles on DEV Community by Roy (@roylib).</description>
    <link>https://dev.to/roylib</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%2F4061547%2Fe7832376-d1a0-48b0-a141-6c36e523c022.png</url>
      <title>DEV Community: Roy</title>
      <link>https://dev.to/roylib</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/roylib"/>
    <language>en</language>
    <item>
      <title>Kubernetes 1.34 End of Life: What Actually Happens on EKS, GKE, and AKS</title>
      <dc:creator>Roy</dc:creator>
      <pubDate>Tue, 06 Oct 2026 17:24:19 +0000</pubDate>
      <link>https://dev.to/skyhook-radar/kubernetes-134-end-of-life-what-actually-happens-on-eks-gke-and-aks-g8e</link>
      <guid>https://dev.to/skyhook-radar/kubernetes-134-end-of-life-what-actually-happens-on-eks-gke-and-aks-g8e</guid>
      <description>&lt;p&gt;Kubernetes 1.34 reaches end of life upstream on &lt;a href="https://kubernetes.io/releases/patch-releases/" rel="noopener noreferrer"&gt;October 27, 2026&lt;/a&gt;. If you run it on EKS, GKE, or AKS, that date by itself changes very little. Each provider keeps its own support clock, and each one does something different when that clock runs out. EKS upgrades your control plane without notice. GKE upgrades it on its own schedule, later if the cluster is on the Extended channel. AKS upgrades nothing on the day and stops shipping security patches.&lt;/p&gt;

&lt;p&gt;For 1.34 itself, the provider dates are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;EKS&lt;/strong&gt;: standard support ends December 2, 2026; extended support ends December 2, 2027.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GKE&lt;/strong&gt;: standard support ends January 25, 2027; the Extended channel runs to November 25, 2027.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AKS&lt;/strong&gt;: end of life November 30, 2026; LTS runs to November 2027.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Between now and the end of the year, these are the dates that change what a cluster does:&lt;/p&gt;

&lt;h2&gt;
  
  
  Kubernetes support deadlines, October to December 2026
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Date&lt;/th&gt;
&lt;th&gt;Platform&lt;/th&gt;
&lt;th&gt;What happens&lt;/th&gt;
&lt;th&gt;Affects you if&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Oct 22&lt;/td&gt;
&lt;td&gt;GKE&lt;/td&gt;
&lt;td&gt;1.31 extended support ends&lt;/td&gt;
&lt;td&gt;You still run GKE 1.31 on the Extended channel&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Oct 27&lt;/td&gt;
&lt;td&gt;Upstream&lt;/td&gt;
&lt;td&gt;1.34 end of life: no more patch releases&lt;/td&gt;
&lt;td&gt;You run 1.34 with nobody backporting fixes for you&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Oct 31&lt;/td&gt;
&lt;td&gt;AKS&lt;/td&gt;
&lt;td&gt;Azure Linux 2.0 node images removed; those node pools can't scale&lt;/td&gt;
&lt;td&gt;You have Azure Linux 2.0 node pools&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;November&lt;/td&gt;
&lt;td&gt;AKS&lt;/td&gt;
&lt;td&gt;Security patches end for the NGINX-based application routing add-on&lt;/td&gt;
&lt;td&gt;You use the application routing add-on&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;November&lt;/td&gt;
&lt;td&gt;GKE&lt;/td&gt;
&lt;td&gt;Regular channel auto-upgrades clusters to 1.36 (Google's estimate, moved from October)&lt;/td&gt;
&lt;td&gt;Your cluster is on the Regular channel&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nov 26&lt;/td&gt;
&lt;td&gt;EKS&lt;/td&gt;
&lt;td&gt;1.31 extended support ends; control planes can be upgraded to 1.32 at any time after, without notice&lt;/td&gt;
&lt;td&gt;You still run EKS 1.31&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nov 30&lt;/td&gt;
&lt;td&gt;AKS&lt;/td&gt;
&lt;td&gt;1.34 end of life; clusters drop to platform support&lt;/td&gt;
&lt;td&gt;You run AKS 1.34 without LTS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dec 2&lt;/td&gt;
&lt;td&gt;EKS&lt;/td&gt;
&lt;td&gt;1.34 standard support ends; price goes from $0.10 to $0.60 per cluster-hour&lt;/td&gt;
&lt;td&gt;You run EKS 1.34&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dec 16&lt;/td&gt;
&lt;td&gt;Upstream&lt;/td&gt;
&lt;td&gt;Kubernetes 1.38 released (proposed date); 1.35 enters maintenance Dec 28&lt;/td&gt;
&lt;td&gt;You plan upgrades a quarter ahead&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Where only a month is given, that's all the provider publishes. AKS end-of-life months end on the last day of the month. Every row links to its source in the section below.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "end of support" does on each platform
&lt;/h2&gt;

&lt;p&gt;"End of support" means something different on each platform:&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;Upstream&lt;/th&gt;
&lt;th&gt;EKS&lt;/th&gt;
&lt;th&gt;GKE&lt;/th&gt;
&lt;th&gt;AKS&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;When support ends&lt;/td&gt;
&lt;td&gt;Patch releases stop&lt;/td&gt;
&lt;td&gt;Extended support starts automatically; 12 months later the control plane is upgraded at any time, without notice&lt;/td&gt;
&lt;td&gt;Control plane is upgraded to the next minor&lt;/td&gt;
&lt;td&gt;Version drops to platform support; no upgrade until it's about to leave platform support&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ways to stay longer&lt;/td&gt;
&lt;td&gt;Your distribution's backports, if any&lt;/td&gt;
&lt;td&gt;Extended support, on by default&lt;/td&gt;
&lt;td&gt;Extended channel, or an emergency 90-day exclusion with no support&lt;/td&gt;
&lt;td&gt;LTS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Price of staying&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;$0.60 per cluster-hour instead of $0.10&lt;/td&gt;
&lt;td&gt;$0.60 per cluster-hour on the Extended channel&lt;/td&gt;
&lt;td&gt;LTS requires the Premium tier&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Left behind&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;Managed node groups, self-managed nodes, and add-ons stay on the old version&lt;/td&gt;
&lt;td&gt;Nodes can trail the control plane by up to two minors&lt;/td&gt;
&lt;td&gt;Security patches and Kubernetes component support&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I spent years at Google working with GKE customers, and forced upgrades look different from the provider's side. An unpatched control plane is a liability for the provider as much as for you, which is why every provider eventually moves it for you. They differ in what they move, when, and what they charge you to wait. AKS waits longest: on November 30, an AKS 1.34 cluster keeps serving traffic, keeps scaling, and stops getting security patches.&lt;/p&gt;

&lt;h2&gt;
  
  
  Kubernetes 1.34 end of life upstream: no more patches after October 27
&lt;/h2&gt;

&lt;p&gt;Kubernetes 1.34 entered maintenance mode on August 27, 2026, and reaches end of life on October 27, 2026. After that, the Kubernetes project ships no more 1.34 patch releases. The next monthly patch release is targeted for October 13, before the cutoff.&lt;/p&gt;

&lt;p&gt;The last round landed on September 23: 1.34.12, 1.35.9, 1.36.5, and 1.37.1 fix &lt;a href="https://github.com/kubernetes/kubernetes/issues/142097" rel="noopener noreferrer"&gt;CVE-2026-2270&lt;/a&gt;, where a user who can write StatefulSets and ControllerRevisions in one namespace can get kube-controller-manager to create a Pod in another namespace. If you apply one more 1.34 patch before upgrading, apply that one.&lt;/p&gt;

&lt;p&gt;Who a missing patch affects depends on who runs your control plane. On self-managed clusters (kubeadm, k3s, RKE2), a CVE found after October 27 has no 1.34 fix unless your distribution backports it. EKS &lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/kubernetes-versions.html" rel="noopener noreferrer"&gt;says&lt;/a&gt; it backports security patches to every version it still supports, including extended support. AKS platform support explicitly does not apply security patches.&lt;/p&gt;

&lt;h2&gt;
  
  
  EKS 1.34 and 1.31: the forced upgrade moves only the control plane
&lt;/h2&gt;

&lt;p&gt;EKS gives every version 14 months of standard support and 12 months of extended support. When extended support ends, &lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/kubernetes-versions.html" rel="noopener noreferrer"&gt;EKS upgrades the control plane&lt;/a&gt; to the oldest version still supported. The docs are direct about the timing: automatic updates "can happen at any time after the end of extended support date," and "You won't receive any notification before the update."&lt;/p&gt;

&lt;p&gt;For 1.31, that date is November 26, 2026. After that date the control plane can be upgraded at any time. The forced upgrade only moves the control plane. Self-managed nodes and managed node groups stay on 1.31 (EKS Auto Mode nodes may update on their own), add-ons stay on whatever versions you pinned for 1.31, and an automatically upgraded cluster can't be rolled back. Whenever it happens, you end up with a 1.32 control plane and 1.31 nodes. That's within the version skew policy, so it runs, but it's a state you never tested.&lt;/p&gt;

&lt;p&gt;It also lands you on 1.32, which is itself in extended support until March 23, 2027. However late the forced upgrade comes, the next deadline doesn't move.&lt;/p&gt;

&lt;p&gt;For 1.34, the date is December 2, 2026, and what changes is the bill. Extended support is enabled by default, so a 1.34 cluster keeps running and moves from $0.10 to &lt;a href="https://aws.amazon.com/eks/pricing/" rel="noopener noreferrer"&gt;$0.60 per cluster-hour&lt;/a&gt;. If you disabled extended support, the cluster is upgraded at the end of standard support instead.&lt;/p&gt;

&lt;p&gt;EKS added 1.37 on October 1, 2026.&lt;/p&gt;

&lt;p&gt;To see where each cluster stands:&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;# Version and support policy for every EKS cluster in the current region&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;c &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;aws eks list-clusters &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'clusters[]'&lt;/span&gt; &lt;span class="nt"&gt;--output&lt;/span&gt; text&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;aws eks describe-cluster &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$c&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'cluster.[name, version, upgradePolicy.supportType]'&lt;/span&gt; &lt;span class="nt"&gt;--output&lt;/span&gt; text
&lt;span class="k"&gt;done&lt;/span&gt;

&lt;span class="c"&gt;# The dates EKS will enforce for those versions&lt;/span&gt;
aws eks describe-cluster-versions &lt;span class="nt"&gt;--cluster-versions&lt;/span&gt; 1.31 1.32 1.34 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'clusterVersions[].[clusterVersion, status, endOfStandardSupportDate, endOfExtendedSupportDate]'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output&lt;/span&gt; table
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;EKS also scans for upgrade problems on its own. &lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/cluster-insights.html" rel="noopener noreferrer"&gt;Upgrade insights&lt;/a&gt; refresh every 24 hours and check for deprecated APIs, kubelet and kube-proxy version skew, cluster health, and add-on compatibility (&lt;code&gt;aws eks list-insights --cluster-name "$c"&lt;/code&gt;). They're built around APIs, version skew, and add-ons; the 1.35 changes &lt;a href="https://radarhq.io/blog/kubernetes-1-34-end-of-life#what-breaks-between-kubernetes-134-and-137" rel="noopener noreferrer"&gt;below&lt;/a&gt; live in node configuration, so check those separately.&lt;/p&gt;

&lt;h2&gt;
  
  
  GKE 1.34: standard support runs to January, the 1.36 rollout starts in November
&lt;/h2&gt;

&lt;p&gt;GKE 1.34 stays in standard support until January 25, 2027, when GKE &lt;a href="https://docs.cloud.google.com/kubernetes-engine/versioning" rel="noopener noreferrer"&gt;upgrades clusters&lt;/a&gt; outside the Extended channel to the next minor. Most channel-enrolled clusters won't wait that long. Per the &lt;a href="https://docs.cloud.google.com/kubernetes-engine/docs/release-schedule" rel="noopener noreferrer"&gt;release schedule&lt;/a&gt;, 1.35 became the auto-upgrade target from 1.34 in late April 2026 on Regular and mid-September on Stable. Rollouts are gradual and follow maintenance windows, so a cluster still on 1.34 may just not have been reached yet, but it may also be held back by a maintenance exclusion, a deprecation pause, or the Extended channel.&lt;/p&gt;

&lt;p&gt;For most GKE clusters, what changes this quarter is 1.36. Google's best-effort schedule, revised in early October, has the Regular channel targeting 1.36 around November and Stable in early 2027. That's the 1.35 to 1.36 upgrade in the &lt;a href="https://radarhq.io/blog/kubernetes-1-34-end-of-life#what-breaks-between-kubernetes-134-and-137" rel="noopener noreferrer"&gt;table below&lt;/a&gt;: gitRepo volumes, strict IP validation, renamed metrics. 1.37 is expected on Regular around October 14.&lt;/p&gt;

&lt;p&gt;GKE's deprecation pause can keep exactly the wrong clusters behind. When GKE detects use of an API that the next minor removes, it &lt;a href="https://docs.cloud.google.com/kubernetes-engine/docs/deprecations" rel="noopener noreferrer"&gt;pauses automatic minor upgrades&lt;/a&gt; until it sees 30 consecutive days without that usage, and it says it "cannot guarantee" the pause. It's a sensible safety valve. It also means the clusters with known upgrade blockers are the ones that stay behind, until end of support upgrades them with the blocker still in place. An emergency "No upgrades" maintenance exclusion can hold that off for up to 90 days past end of support, unsupported the whole time, and no longer.&lt;/p&gt;

&lt;p&gt;Two GKE-specific changes on the way to 1.35: GKE &lt;a href="https://docs.cloud.google.com/kubernetes-engine/docs/deprecations/exec-probe-timeouts" rel="noopener noreferrer"&gt;now enforces&lt;/a&gt; &lt;code&gt;timeoutSeconds&lt;/code&gt; on exec probes, so a probe command that takes 3 seconds against the default 1-second timeout, which used to pass, starts failing. Windows Server nodes also move to containerd 2.0.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gcloud container clusters list &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"table(name, location, currentMasterVersion, currentNodeVersion, releaseChannel.channel)"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Extended channel has its own deadline this quarter: GKE 1.31 reaches the end of extended support on October 22, 2026.&lt;/p&gt;

&lt;p&gt;If you need more time, the Extended channel keeps 1.34 until November 25, 2027, at a &lt;a href="https://cloud.google.com/kubernetes-engine/pricing" rel="noopener noreferrer"&gt;total of $0.60 per cluster-hour&lt;/a&gt; once the version is in its extended period.&lt;/p&gt;

&lt;h2&gt;
  
  
  AKS 1.34 end of life: nothing happens on November 30, and that's the risk
&lt;/h2&gt;

&lt;p&gt;AKS 1.34 reaches end of life at the end of November 2026. After that it moves to &lt;a href="https://learn.microsoft.com/en-us/azure/aks/supported-kubernetes-versions" rel="noopener noreferrer"&gt;platform support&lt;/a&gt; until 1.38 reaches GA. In platform support, node pool scaling, upgrades to a supported version, and the infrastructure SLA all still work. "Applying security patches" and "Kubernetes components (including add-ons)" are listed as not supported, and you can't create new 1.34 clusters.&lt;/p&gt;

&lt;p&gt;AKS doesn't upgrade the cluster on November 30. The &lt;a href="https://learn.microsoft.com/en-us/azure/aks/auto-upgrade-cluster" rel="noopener noreferrer"&gt;forced upgrade&lt;/a&gt; comes later: when a non-LTS cluster is about to fall out of platform support, which for 1.34 means when 1.38 reaches GA on AKS, AKS upgrades it to the oldest fully supported minor. Clusters on an auto-upgrade channel move sooner. AKS Automatic is always on &lt;code&gt;stable&lt;/code&gt;, which targets the second-newest minor, and Standard clusters on &lt;code&gt;stable&lt;/code&gt; or &lt;code&gt;rapid&lt;/code&gt; follow their channel. The default for Standard clusters is &lt;code&gt;none&lt;/code&gt;. If you need patches without upgrading, AKS sells a second year as LTS, which requires the Premium tier and explicit LTS enrollment.&lt;/p&gt;

&lt;p&gt;One AKS date this quarter isn't about a Kubernetes version at all. AKS stopped patching Azure Linux 2.0 on November 30, 2025 and froze its node image at &lt;code&gt;202512.06.0&lt;/code&gt;. On &lt;strong&gt;October 31, 2026&lt;/strong&gt;, those images are removed and node pools on them can't scale. Nothing visible happens that day. It breaks the next time the cluster autoscaler, or anyone else, tries to add a node. Azure Linux 3.0 is the default from Kubernetes 1.32, so the pools at risk are &lt;code&gt;AzureLinux&lt;/code&gt; pools on 1.31 or earlier: LTS clusters, and clusters already out of support. &lt;code&gt;az aks nodepool update --os-sku AzureLinux3&lt;/code&gt; &lt;a href="https://learn.microsoft.com/en-us/azure/aks/upgrade-os-version" rel="noopener noreferrer"&gt;migrates a pool in place&lt;/a&gt; on Kubernetes 1.28 and later.&lt;/p&gt;

&lt;p&gt;AKS also stops patching the ingress-nginx-based &lt;a href="https://learn.microsoft.com/en-us/azure/aks/app-routing-nginx-to-gateway-api-migration" rel="noopener noreferrer"&gt;application routing add-on&lt;/a&gt; in November 2026. Microsoft's documented migration target is the Gateway API. Upstream ingress-nginx has been archived since March 2026.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;az aks list &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s2"&gt;"[].{name:name, resourceGroup:resourceGroup, version:currentKubernetesVersion, channel:autoUpgradeProfile.upgradeChannel, plan:supportPlan, tier:sku.tier}"&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; table

az aks nodepool list &lt;span class="nt"&gt;-g&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RG&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--cluster-name&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CLUSTER&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s2"&gt;"[].{pool:name, osSku:osSku, version:currentOrchestratorVersion, image:nodeImageVersion}"&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; table
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  When paying for extended support is the right call
&lt;/h2&gt;

&lt;p&gt;EKS and GKE charge the same for extended support: $0.50 per cluster-hour on top of the $0.10 base, which comes to about $365 per cluster per month. For a single cluster with a known blocker and an upgrade scheduled for January, that's cheaper than the engineering time of rushing the upgrade through in December. Across a fleet, it adds up. Forty clusters left in extended support by default cost an extra $175,000 a year.&lt;/p&gt;

&lt;p&gt;My rule of thumb: pay for extended support when you can name the blocker and the date you'll fix it. If you can't name the blocker, extended support mostly delays the day you find out what it is.&lt;/p&gt;

&lt;h2&gt;
  
  
  What breaks between Kubernetes 1.34 and 1.37
&lt;/h2&gt;

&lt;p&gt;EKS, GKE, and AKS (outside LTS) upgrade the control plane one minor version at a time, so a 1.34 cluster reaches 1.37 through three separate upgrades, each with its own removals:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Upgrade&lt;/th&gt;
&lt;th&gt;Change&lt;/th&gt;
&lt;th&gt;What happens&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1.34 → 1.35&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;failCgroupV1&lt;/code&gt; defaults to true&lt;/td&gt;
&lt;td&gt;Kubelet won't start on cgroup v1 nodes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1.34 → 1.35&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;--pod-infra-container-image&lt;/code&gt; kubelet flag removed&lt;/td&gt;
&lt;td&gt;Kubelet fails to start if the flag is still set&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1.34 → 1.35&lt;/td&gt;
&lt;td&gt;IPVS kube-proxy mode deprecated&lt;/td&gt;
&lt;td&gt;Warnings now; removal planned for 1.40&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1.34 → 1.35 (GKE)&lt;/td&gt;
&lt;td&gt;Exec probe timeouts enforced&lt;/td&gt;
&lt;td&gt;Probe commands slower than &lt;code&gt;timeoutSeconds&lt;/code&gt; start failing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1.35 → 1.36&lt;/td&gt;
&lt;td&gt;gitRepo volumes permanently disabled&lt;/td&gt;
&lt;td&gt;Workloads that mount them stop working&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1.35 → 1.36&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;StrictIPCIDRValidation&lt;/code&gt; on by default&lt;/td&gt;
&lt;td&gt;The API server rejects values like &lt;code&gt;010.000.000.005&lt;/code&gt; in new objects&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1.35 → 1.36&lt;/td&gt;
&lt;td&gt;Service &lt;code&gt;externalIPs&lt;/code&gt; deprecated&lt;/td&gt;
&lt;td&gt;Warnings now; kube-proxy support planned off by default in 1.40, removed in 1.43&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1.35 → 1.36&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;volume_operation_total_errors&lt;/code&gt; → &lt;code&gt;volume_operation_errors_total&lt;/code&gt;, &lt;code&gt;etcd_bookmark_counts&lt;/code&gt; → &lt;code&gt;etcd_bookmark_total&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Alerts on the old names silently stop matching&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1.36 → 1.37&lt;/td&gt;
&lt;td&gt;25 feature gates, 18 kubelet flags, &lt;code&gt;eventRecordQPS: 0&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;See &lt;a href="https://radarhq.io/blog/kubernetes-1-37-breaking-changes" rel="noopener noreferrer"&gt;Kubernetes 1.37 breaking changes&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The 1.35 and 1.36 details are in the &lt;a href="https://kubernetes.io/blog/2025/12/17/kubernetes-v1-35-release/" rel="noopener noreferrer"&gt;v1.35&lt;/a&gt; and &lt;a href="https://kubernetes.io/blog/2026/04/22/kubernetes-v1-36-release/" rel="noopener noreferrer"&gt;v1.36&lt;/a&gt; release announcements. The cgroup v1 change is the one that stops nodes: on 1.35, a kubelet on a cgroup v1 host refuses to start unless you set &lt;code&gt;failCgroupV1: false&lt;/code&gt;. To check a node, run &lt;code&gt;stat -fc %T /sys/fs/cgroup/&lt;/code&gt; on it. &lt;code&gt;cgroup2fs&lt;/code&gt; means v2; &lt;code&gt;tmpfs&lt;/code&gt; means v1.&lt;/p&gt;

&lt;p&gt;containerd 1.7 has a similar caveat. The 1.35 release told users to move to containerd 2.x before the next Kubernetes version, but the kubelet change that would break 1.7 has since been &lt;a href="https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG/CHANGELOG-1.37.md" rel="noopener noreferrer"&gt;deferred to 1.38&lt;/a&gt;. containerd 1.7 itself &lt;a href="https://github.com/containerd/containerd/blob/main/RELEASES.md" rel="noopener noreferrer"&gt;reached end of life&lt;/a&gt; in September 2026, and 2.2 follows on November 6.&lt;/p&gt;

&lt;p&gt;To see every cluster's control plane, kubelet, and runtime versions from one kubeconfig:&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="k"&gt;for &lt;/span&gt;ctx &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;kubectl config get-contexts &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="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"== &lt;/span&gt;&lt;span class="nv"&gt;$ctx&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;kubectl &lt;span class="nt"&gt;--context&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ctx&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; version &lt;span class="nt"&gt;-o&lt;/span&gt; json 2&amp;gt;/dev/null &lt;span class="se"&gt;\&lt;/span&gt;
    | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.serverVersion.gitVersion // "unreachable"'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  kubectl &lt;span class="nt"&gt;--context&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ctx&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; get nodes &lt;span class="nt"&gt;--no-headers&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-o&lt;/span&gt; custom-columns&lt;span class="o"&gt;=&lt;/span&gt;KUBELET:.status.nodeInfo.kubeletVersion,RUNTIME:.status.nodeInfo.containerRuntimeVersion &lt;span class="se"&gt;\&lt;/span&gt;
    2&amp;gt;/dev/null | &lt;span class="nb"&gt;sort&lt;/span&gt; | &lt;span class="nb"&gt;uniq&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Checking each upgrade with Radar
&lt;/h2&gt;

&lt;p&gt;We build Radar, an open-source Kubernetes UI, so take this section with that in mind. Its Upgrade impact view (Checks, then the Upgrade impact tab) runs against one cluster at a time and targets the next minor by default. Each check belongs to the release that makes it relevant, so it only runs when your upgrade crosses that release. A 1.34 cluster targeting 1.35 gets node cgroup compatibility and, on GKE, exec probe timeout exposure. A 1.35 cluster targeting 1.36 gets container runtime support, gitRepo volumes, Service externalIPs, strict IP/CIDR validation, and renamed metrics. Every upgrade also gets the standing checks: removed API usage, kubelet and kube-proxy version skew, admission and CRD conversion webhooks, and node drain feasibility.&lt;/p&gt;

&lt;p&gt;Here it is on our own GKE nonprod cluster, which runs 1.35 on the Regular channel and is next in line for 1.36. The 1.36 checks all pass there, but the scan still finds three blockers in webhooks and node drains:&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%2F53rqgnpte82r96xkkfac.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%2F53rqgnpte82r96xkkfac.png" alt="Radar's Upgrade impact view on a GKE cluster upgrading from Kubernetes 1.35 to 1.36: three upgrade blockers in admission webhook readiness, CRD conversion webhook readiness, and node drain feasibility, removed API usage marked Incomplete, and the 1.36 checks for strict IP and CIDR validation, Service externalIPs, container runtime support, renamed metrics, and gitRepo volumes all passed" width="800" height="847"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you pick a target more than one minor ahead, the verdict is Blocked and the report lists the upgrade sequence, the same one-minor-at-a-time path EKS, GKE, and non-LTS AKS require. When Radar can't read something, such as kubelet configuration without &lt;code&gt;nodes/proxy&lt;/code&gt; access, the check is marked Incomplete rather than passed. Radar doesn't know your provider's dates or release channels; that's what the table at the top is for.&lt;/p&gt;

&lt;p&gt;The same scan runs headless over HTTP and over MCP (&lt;code&gt;get_cluster_upgrade_readiness&lt;/code&gt;), so you can script it per cluster or hand it to an agent. The &lt;a href="https://radarhq.io/blog/kubernetes-1-37-breaking-changes#scripting-it-the-headless-scan-and-mcp" rel="noopener noreferrer"&gt;1.37 post&lt;/a&gt; has the commands.&lt;/p&gt;

&lt;h2&gt;
  
  
  Also on the calendar
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Helm 3&lt;/strong&gt;: bug fixes ended with the final feature release, v3.22.0, on September 9, 2026. Security fixes continue until &lt;a href="https://helm.sh/blog/helm-v3-end-of-life/" rel="noopener noreferrer"&gt;February 10, 2027&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kubernetes 1.38&lt;/strong&gt;: proposed release on &lt;a href="https://github.com/kubernetes/sig-release/blob/master/releases/release-1.38/README.md" rel="noopener noreferrer"&gt;December 16, 2026&lt;/a&gt;, which starts the same cycle for 1.35.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Check each cluster before your provider's date arrives. After an automatic upgrade you'll be debugging on whatever version you were moved to.&lt;/p&gt;

&lt;p&gt;Radar is open source (Apache-2.0). &lt;code&gt;brew install skyhook-io/tap/radar&lt;/code&gt;, point it at a cluster, and open Checks. Or star it on &lt;a href="https://github.com/skyhook-io/radar" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://radarhq.io/blog/kubernetes-1-34-end-of-life?utm_source=devto&amp;amp;utm_campaign=kubernetes-1-34-end-of-life-launch-2026-10&amp;amp;utm_content=devto" rel="noopener noreferrer"&gt;radarhq.io&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>devops</category>
      <category>aws</category>
      <category>azure</category>
    </item>
    <item>
      <title>Karpenter Troubleshooting: Pending Pods, Failed Launches, and Nodes That Never Join</title>
      <dc:creator>Roy</dc:creator>
      <pubDate>Mon, 28 Sep 2026 17:53:11 +0000</pubDate>
      <link>https://dev.to/skyhook-radar/karpenter-troubleshooting-pending-pods-failed-launches-and-nodes-that-never-join-467h</link>
      <guid>https://dev.to/skyhook-radar/karpenter-troubleshooting-pending-pods-failed-launches-and-nodes-that-never-join-467h</guid>
      <description>&lt;p&gt;&lt;a href="https://karpenter.sh/" rel="noopener noreferrer"&gt;Karpenter&lt;/a&gt; changes how you debug a Pending pod.&lt;/p&gt;

&lt;p&gt;A Karpenter NodePool can report &lt;code&gt;Ready=True&lt;/code&gt; while the nodes it launches repeatedly fail to register. And that is only one reason these incidents can be confusing: the scheduler, NodePool, NodeClaim, NodeClass, and controller logs each hold a different part of the answer.&lt;/p&gt;

&lt;p&gt;On a fixed set of nodes, Pending usually means the nodes you already have cannot take the pod. With Karpenter, there is another question: did Karpenter rule out every NodePool, or did it try to create capacity and fail?&lt;/p&gt;

&lt;p&gt;This guide maps the failures we run into most often to the place I would look next. The screenshots come from Radar's Capacity view, the open-source tool we maintain, which assembles the same evidence from the cluster itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Karpenter pending pods: symptom, cause, where to look
&lt;/h2&gt;

&lt;p&gt;Scheduler messages usually arrive as &lt;code&gt;0/N nodes are available:&lt;/code&gt; followed by a reason. That message is useful, but with Karpenter it is often only the first step.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Symptom or message&lt;/th&gt;
&lt;th&gt;Usual cause&lt;/th&gt;
&lt;th&gt;Where to look&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;didn't match Pod's node affinity/selector&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Pod requires a label no NodePool declares&lt;/td&gt;
&lt;td&gt;NodePool &lt;code&gt;requirements&lt;/code&gt; and &lt;code&gt;spec.template.metadata.labels&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;had untolerated taint {key: value}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;NodePool template taints the node, pod has no toleration&lt;/td&gt;
&lt;td&gt;NodePool &lt;code&gt;spec.template.spec.taints&lt;/code&gt; vs pod tolerations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;Insufficient cpu&lt;/code&gt; / &lt;code&gt;Insufficient memory&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Existing nodes are full. Says nothing about what Karpenter could provision&lt;/td&gt;
&lt;td&gt;Node allocatable vs scheduled requests, then Karpenter's scheduling event&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pod pending, no NodeClaim created&lt;/td&gt;
&lt;td&gt;Every pool ruled the pod out, or NodePool limits are reached&lt;/td&gt;
&lt;td&gt;Karpenter &lt;code&gt;FailedScheduling&lt;/code&gt; event; &lt;code&gt;status.resources&lt;/code&gt; vs &lt;code&gt;spec.limits&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NodeClaim created, no node appears&lt;/td&gt;
&lt;td&gt;Launch failed, or instance launched and never registered&lt;/td&gt;
&lt;td&gt;NodeClaim conditions for launch failures; &lt;code&gt;NodeRegistrationHealthy&lt;/code&gt; for repeated registration failures&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nodes launch then disappear&lt;/td&gt;
&lt;td&gt;Registration timeout&lt;/td&gt;
&lt;td&gt;NodePool &lt;code&gt;NodeRegistrationHealthy&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pod stuck with no scheduler attempt&lt;/td&gt;
&lt;td&gt;Scheduling gate still present&lt;/td&gt;
&lt;td&gt;&lt;code&gt;spec.schedulingGates&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nodes not consolidating&lt;/td&gt;
&lt;td&gt;Disruption blocked or budgets exhausted&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;DisruptionBlocked&lt;/code&gt; / &lt;code&gt;Unconsolidatable&lt;/code&gt; events&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&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%2F9se5mczwhh7b8k3f9lop.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%2F9se5mczwhh7b8k3f9lop.png" alt="The Capacity overview: capacity managers, the cluster scheduling bar, prioritized operational signals including " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The important part is that these failures do not all live on the same object. A scheduling event can tell you why a pool was rejected, while a NodeClaim or NodePool condition tells you why capacity that should have appeared never did.&lt;/p&gt;

&lt;h2&gt;
  
  
  "didn't match Pod's node affinity/selector"
&lt;/h2&gt;

&lt;p&gt;This is a common Karpenter misconfiguration and one of the easier ones to identify from configuration alone.&lt;/p&gt;

&lt;p&gt;Karpenter only applies labels that come from a NodePool's &lt;code&gt;requirements&lt;/code&gt; or its template labels. If a pod requires a custom label that no NodePool declares, no node Karpenter builds will ever carry it.&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%2F11wjmno3i9q0w3ofv9n3.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%2F11wjmno3i9q0w3ofv9n3.png" alt="Radar's Demand view showing a blocked group headlined " width="799" height="333"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In production this is often a stale label: a &lt;code&gt;workload-type&lt;/code&gt; renamed during a migration, or a &lt;code&gt;nodeSelector&lt;/code&gt; copied from another cluster.&lt;/p&gt;

&lt;p&gt;Start with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl describe pod &amp;lt;pod&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then compare the pod's selectors and required affinity against each NodePool's:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;spec.requirements
spec.template.metadata.labels
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the label the pod requires is never declared by any NodePool, Karpenter has nowhere to place it.&lt;/p&gt;

&lt;p&gt;Radar's Demand view does this comparison across all NodePools and groups pods with the same scheduling constraints together, but the underlying check is the same one you can do manually.&lt;/p&gt;

&lt;h2&gt;
  
  
  "had untolerated taint" on a Karpenter cluster
&lt;/h2&gt;

&lt;p&gt;Karpenter taints nodes from &lt;code&gt;spec.template.spec.taints&lt;/code&gt; on the NodePool.&lt;/p&gt;

&lt;p&gt;A pod without a matching toleration cannot land on anything that pool creates, so Karpenter can rule the pool out before provisioning even starts.&lt;/p&gt;

&lt;p&gt;Compare:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NodePool.spec.template.spec.taints
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;against the pod's:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;spec.tolerations
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One thing to watch: startup taints are different from permanent taints.&lt;/p&gt;

&lt;p&gt;Startup taints, along with the well-known unreachable/not-ready taints, are expected during parts of a node's lifecycle. They should not automatically be treated as evidence that the NodePool can never run the workload.&lt;/p&gt;

&lt;h2&gt;
  
  
  LaunchFailed, VCPULimitExceeded, and InsufficientInstanceCapacity
&lt;/h2&gt;

&lt;p&gt;If Karpenter creates a NodeClaim and the cloud provider refuses it, the useful evidence moves away from the pod.&lt;/p&gt;

&lt;p&gt;Look at the NodeClaim lifecycle conditions.&lt;/p&gt;

&lt;p&gt;Common AWS examples include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;VCPULimitExceeded&lt;/code&gt; - the account's vCPU quota for that instance family is exhausted.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;InsufficientInstanceCapacity&lt;/code&gt; - AWS does not currently have capacity for that instance type in the requested region or zone.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Unauthorized&lt;/code&gt; - the instance profile or IAM configuration is wrong.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;LaunchFailed&lt;/code&gt; - Karpenter reached the launch stage but could not create the instance successfully.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A detail that makes these annoying to debug: failed NodeClaims do not necessarily stay around forever.&lt;/p&gt;

&lt;p&gt;Timed-out NodeClaims are deleted, and the conditions attached to them disappear too. Karpenter can surface repeated launch or registration trouble at the NodePool level through &lt;code&gt;NodeRegistrationHealthy&lt;/code&gt;, but the individual history - which claims failed, how many, and in what order - can be harder to reconstruct afterward.&lt;/p&gt;

&lt;p&gt;That is why events and controller logs matter for intermittent failures.&lt;/p&gt;

&lt;p&gt;Radar's Activity view keeps a longer provisioning history, but even without Radar, the practical rule is simple: if you see a NodeClaim, inspect it before assuming the pod event contains the answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why aren't my Karpenter nodes joining the cluster?
&lt;/h2&gt;

&lt;p&gt;This is one of the more confusing failure modes because the NodePool itself can still report &lt;code&gt;Ready=True&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Karpenter tracks &lt;code&gt;NodeRegistrationHealthy&lt;/code&gt; separately from the NodePool's top-level &lt;code&gt;Ready&lt;/code&gt; condition.&lt;/p&gt;

&lt;p&gt;That means the NodePool can be valid for provisioning, successfully launch instances, and still fail to produce usable nodes because those instances never register with the cluster.&lt;/p&gt;

&lt;p&gt;The pattern looks roughly like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Karpenter decides the NodePool can satisfy the workload.&lt;/li&gt;
&lt;li&gt;A NodeClaim is created.&lt;/li&gt;
&lt;li&gt;The cloud instance launches.&lt;/li&gt;
&lt;li&gt;The node never registers successfully.&lt;/li&gt;
&lt;li&gt;The registration timeout is reached.&lt;/li&gt;
&lt;li&gt;The instance is deleted.&lt;/li&gt;
&lt;li&gt;Karpenter tries again.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;From the workload side, the result is simple: the pod stays Pending and capacity never arrives.&lt;/p&gt;

&lt;p&gt;From the NodePool side, &lt;code&gt;Ready=True&lt;/code&gt; alone does not tell the whole story.&lt;/p&gt;

&lt;p&gt;This is different from a broken NodeClass.&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%2Fget49qynfhwuu2ekfnvc.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%2Fget49qynfhwuu2ekfnvc.png" alt="The gpu-workloads NodePool detail page leading with two warnings - NodeClass Not Ready and NodePool Node Registration Unhealthy - each with a cause and a next step, above the capacity ledger" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For example, if an EC2NodeClass looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ValidationSucceeded=False
SecurityGroupsReady=False
SubnetsReady=False
InstanceProfileReady=Unknown
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;then the NodeClass itself is not ready. That propagates through &lt;code&gt;NodeClassReady&lt;/code&gt;, so this is not a case where the NodePool remains happily &lt;code&gt;Ready&lt;/code&gt; while nodes fail to register.&lt;/p&gt;

&lt;p&gt;Typical NodeClass problems include selectors that resolve to no subnets or security groups, a missing instance profile, or another prerequisite that cannot be resolved before launch.&lt;/p&gt;

&lt;p&gt;A &lt;code&gt;Ready=True&lt;/code&gt; plus &lt;code&gt;NodeRegistrationHealthy=False&lt;/code&gt; problem happens later: the NodeClass was valid enough to provision, but the resulting node never successfully joined the cluster.&lt;/p&gt;

&lt;p&gt;Those two failures can look nearly identical from the pod's point of view, but they require looking in different places.&lt;/p&gt;

&lt;h2&gt;
  
  
  Am I about to hit a NodePool limit?
&lt;/h2&gt;

&lt;p&gt;Karpenter stops provisioning when a NodePool reaches &lt;code&gt;spec.limits&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The pods that would have triggered more capacity stay Pending, and no NodeClaim is created for them.&lt;/p&gt;

&lt;p&gt;Karpenter does tell you when this happens. Look for a &lt;code&gt;FailedScheduling&lt;/code&gt; event containing messages such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;node limits have been exhausted for nodepool
all available instance types exceed limits for nodepool
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The number Karpenter checks is &lt;code&gt;status.resources&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That matters because &lt;code&gt;status.resources&lt;/code&gt; includes resources represented by in-flight NodeClaims. So:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;headroom = limit - provisioned
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;not:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;headroom = limit - allocatable
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A NodePool can therefore be close to or at its limit even when relatively little capacity has successfully registered with Kubernetes.&lt;/p&gt;

&lt;p&gt;The event tells you which pool hit its limit. To understand how much room is left elsewhere, compare &lt;code&gt;spec.limits&lt;/code&gt; and &lt;code&gt;status.resources&lt;/code&gt; across the other pools.&lt;/p&gt;

&lt;h2&gt;
  
  
  Provisioned, allocatable, requests, usage: which number means what
&lt;/h2&gt;

&lt;p&gt;These numbers answer different questions:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Number&lt;/th&gt;
&lt;th&gt;Source&lt;/th&gt;
&lt;th&gt;What it tells you&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Provisioned&lt;/td&gt;
&lt;td&gt;&lt;code&gt;status.resources&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;What counts against NodePool &lt;code&gt;spec.limits&lt;/code&gt;, including in-flight claims&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Node allocatable&lt;/td&gt;
&lt;td&gt;Registered nodes&lt;/td&gt;
&lt;td&gt;What Kubernetes can actually schedule onto now&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scheduled requests&lt;/td&gt;
&lt;td&gt;Pod specs&lt;/td&gt;
&lt;td&gt;What the scheduler has already committed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Actual usage&lt;/td&gt;
&lt;td&gt;Metrics API&lt;/td&gt;
&lt;td&gt;What workloads are consuming, not what can still be scheduled&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Karpenter and Kubernetes schedule based on requests, not current CPU or memory usage.&lt;/p&gt;

&lt;p&gt;A node can be using 20% CPU and still be completely full from the scheduler's point of view if all of its CPU has already been requested.&lt;/p&gt;

&lt;p&gt;That is why Radar keeps eight capacity values separate instead of reducing them to a single utilization percentage.&lt;/p&gt;

&lt;p&gt;There is another subtlety: &lt;code&gt;unallocated&lt;/code&gt; is only an aggregate subtraction.&lt;/p&gt;

&lt;p&gt;If a cluster has 4 CPUs and 8 GiB of memory unallocated in total, that does not prove a pod requesting 4 CPUs and 8 GiB can fit. Those resources still need to exist together on a single node that also satisfies the pod's other constraints.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does DisruptionBlocked mean in Karpenter?
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;DisruptionBlocked&lt;/code&gt; and &lt;code&gt;Unconsolidatable&lt;/code&gt; do not mean Karpenter disrupted a node.&lt;/p&gt;

&lt;p&gt;They mean disruption was &lt;strong&gt;prevented&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Read either as disruption happening and the timeline reads backwards.&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%2Ftrds6zliq8thrc03lyu5.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%2Ftrds6zliq8thrc03lyu5.png" alt="An Activity episode reading " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Common causes include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a disruption budget that is exhausted&lt;/li&gt;
&lt;li&gt;a PodDisruptionBudget that cannot be satisfied&lt;/li&gt;
&lt;li&gt;a &lt;code&gt;karpenter.sh/do-not-disrupt&lt;/code&gt; annotation&lt;/li&gt;
&lt;li&gt;a NodeClaim with no associated node to disrupt&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The raw event is usually the best place to start:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DisruptionBlocked: Nodeclaim does not have an associated node
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do not treat these events as evidence that Karpenter removed capacity. They are evidence that Karpenter considered a disruption and could not proceed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a pod evaluates as "unknown" instead of incompatible
&lt;/h2&gt;

&lt;p&gt;When we built this evaluation into Radar, we found it useful to separate three cases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;declared compatible&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;incompatible&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;unknown&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The difference matters because configuration can prove some scheduling failures, but not all of them.&lt;/p&gt;

&lt;p&gt;A required custom label that no NodePool declares is &lt;strong&gt;incompatible&lt;/strong&gt;. Nothing the cloud provider does can fix that.&lt;/p&gt;

&lt;p&gt;Provider-controlled labels such as zone, instance type, capacity type, and architecture are different. If the NodePool leaves them open, the provider's actual offerings may still satisfy the pod.&lt;/p&gt;

&lt;p&gt;Requests are similar. Observed nodes can tell you whether a known machine shape is large enough, but they do not prove which instance types the provider could create next.&lt;/p&gt;

&lt;p&gt;Radar therefore does not simulate provider offerings or claim that a compatible pod will schedule. It only says that the declared constraints do not rule it out.&lt;/p&gt;

&lt;p&gt;If the available information cannot prove either outcome, the result is &lt;code&gt;unknown&lt;/code&gt; instead of a guess.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why some numbers show ≥ or ?
&lt;/h2&gt;

&lt;p&gt;Capacity information is not always complete.&lt;/p&gt;

&lt;p&gt;For example, RBAC may prevent access to one source, or metrics may only be available for part of the fleet.&lt;/p&gt;

&lt;p&gt;Radar marks that distinction explicitly:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Glyph&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;(none)&lt;/td&gt;
&lt;td&gt;Exact - the source was fully observed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;≥&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Lower bound - only part of the source was observed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;≤&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Upper bound - derived from a lower-bound input&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;?&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Unknown - the source was not observed&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Missing data is never shown as zero, and partial data is never shown as exact.&lt;/p&gt;

&lt;p&gt;This matters during an incident because "0" and "I could not read it" are very different answers.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this doesn't cover
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No full scheduling simulation.&lt;/strong&gt; Provider inventory, spot availability, and exact bin-packing remain Karpenter's job.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DRA demand is outside the normal requests ledger.&lt;/strong&gt; Dynamic Resource Allocation &lt;code&gt;ResourceClaims&lt;/code&gt; can carry accelerator demand outside normal container requests.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No long-term trend analysis.&lt;/strong&gt; This is about current state and recent failure evidence.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Single cluster.&lt;/strong&gt; Same scope as the rest of Radar OSS.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Clusters without Karpenter can still be inspected through the normal node and node-group views. The Karpenter-specific Demand and Activity analysis depends on NodePools and NodeClaims being present.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running it
&lt;/h2&gt;

&lt;p&gt;Radar is Apache-2.0, runs as a single binary, and does not install anything in the cluster:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;skyhook-io/tap/radar
radar
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Capacity requires Radar v1.9.0 or newer.&lt;/p&gt;

&lt;p&gt;You can open it from the sidebar or jump there directly from a stuck pod using "Evaluate against Karpenter NodePools".&lt;/p&gt;

&lt;p&gt;If Radar reaches a verdict you can prove wrong, that's worth an &lt;a href="https://github.com/skyhook-io/radar" rel="noopener noreferrer"&gt;issue on GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Related reading: &lt;a href="https://radarhq.io/blog/five-questions-you-cant-answer-with-kubectl" rel="noopener noreferrer"&gt;Five Questions You Can't Answer With kubectl&lt;/a&gt;, &lt;a href="https://radarhq.io/blog/kubernetes-network-reachability" rel="noopener noreferrer"&gt;Everything Is Green and Nothing Works: Network Reachability in Radar&lt;/a&gt;, and &lt;a href="https://radarhq.io/blog/kubernetes-1-37-breaking-changes" rel="noopener noreferrer"&gt;Kubernetes 1.37 Breaking Changes&lt;/a&gt;.&lt;/p&gt;

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