{"id":79714,"date":"2026-11-02T04:55:41","date_gmt":"2026-11-02T04:55:41","guid":{"rendered":"https:\/\/www.devopsschool.com\/blog\/?p=79714"},"modified":"2026-11-02T04:55:42","modified_gmt":"2026-11-02T04:55:42","slug":"debugging-terraform-helm-drift-on-eks-failure-behaviours-and-fixes","status":"publish","type":"post","link":"https:\/\/www.devopsschool.com\/blog\/debugging-terraform-helm-drift-on-eks-failure-behaviours-and-fixes\/","title":{"rendered":"Debugging Terraform + Helm Drift on EKS: Failure Behaviours and Fixes"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\"><em>A field tutorial based on a real incident removing a stale&nbsp;<code>CriticalAddonsOnly<\/code>&nbsp;toleration from&nbsp;<code>metrics-server<\/code>&nbsp;and&nbsp;<code>KEDA<\/code>&nbsp;on EKS Auto Mode clusters managed by Terraform Cloud (TFC) + the&nbsp;<code>eks-blueprints-addons<\/code>&nbsp;module.<\/em><\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">TL;DR<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">We tried to remove a Kubernetes&nbsp;<strong>toleration<\/strong>&nbsp;that was set through a Terraform&nbsp;<code>helm_release<\/code>. The code change looked trivial, but it took several apply cycles before the cluster actually changed. Along the way we hit&nbsp;<strong>five distinct failure behaviours<\/strong>, none of which threw an error \u2014 they all&nbsp;<em>silently did nothing<\/em>. This post documents each one, the symptom, the root cause, and the fix, plus the mental models and commands that make these problems obvious.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The single most useful signal throughout:&nbsp;<strong>the pod&#8217;s&nbsp;<code>AGE<\/code><\/strong>. If a Helm value truly changed the rendered manifest, the pod rolls and&nbsp;<code>AGE<\/code>&nbsp;resets. If&nbsp;<code>AGE<\/code>&nbsp;doesn&#8217;t reset,&nbsp;<em>nothing actually changed<\/em>&nbsp;\u2014 no matter what Terraform or Helm said.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Background: the change we wanted<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">EKS Auto Mode clusters here run a single built-in&nbsp;<strong><code>general-purpose<\/code><\/strong>&nbsp;NodePool. There is&nbsp;<strong>no&nbsp;<code>system<\/code>&nbsp;NodePool<\/strong>, so nothing in the cluster carries the&nbsp;<code>CriticalAddonsOnly:NoSchedule<\/code>&nbsp;taint.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><code>metrics-server<\/code>&nbsp;(and KEDA) had this toleration set via Terraform:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">metrics_server = {\n  values = &#91;yamlencode({\n    tolerations = &#91;{\n      key      = <span class=\"hljs-string\">\"CriticalAddonsOnly\"<\/span>\n      operator = <span class=\"hljs-string\">\"Exists\"<\/span>\n      effect   = <span class=\"hljs-string\">\"NoSchedule\"<\/span>\n    }]\n  })]\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h3 class=\"wp-block-heading\">Mental model #1 \u2014 a toleration is permissive, not attractive<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">A toleration&nbsp;<strong>does not<\/strong>&nbsp;force, attract, or pin a pod to a tainted node. It only&nbsp;<em>permits<\/em>&nbsp;a pod to land on a node that carries the matching taint. With no&nbsp;<code>system<\/code>&nbsp;NodePool, there is no matching taint, so the toleration matches nothing and has&nbsp;<strong>zero effect<\/strong>. It is dead config.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\">Removing it is pure cleanup \u2014 it never changed where the pod ran. The pod was always on&nbsp;<code>general-purpose<\/code>, with or without the toleration.<\/p>\n<\/blockquote>\n\n\n\n<p class=\"wp-block-paragraph\">That made it a perfect low-risk change&#8230; and a perfect way to surface every Terraform\/Helm drift trap, because the&nbsp;<em>expected<\/em>&nbsp;runtime effect was nil and we had to rely on tooling signals to know whether anything happened.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Failure Behaviour #1 \u2014 Local edits never reached the pipeline<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Symptom:<\/strong>&nbsp;Edited the&nbsp;<code>.tf<\/code>&nbsp;file, &#8220;applied,&#8221; but the cluster was unchanged.&nbsp;<code>helm get values<\/code>&nbsp;still showed the toleration; the pod&nbsp;<code>AGE<\/code>&nbsp;was unchanged.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Root cause:<\/strong>&nbsp;TFC builds from the&nbsp;<strong>VCS-connected branch<\/strong>, not from local working-tree files. Editing a file on the laptop (or even committing it to a&nbsp;<em>feature branch<\/em>) does nothing until that commit is on the branch the TFC workspace tracks (here,&nbsp;<code>main<\/code>).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>How we detected it:<\/strong><\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\">git status -sb                 <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">em<\/span>&gt;<\/span># showed work was on a feature branch, not main<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">em<\/span>&gt;<\/span>\ngit show origin\/main:<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">file<\/span>&gt;<\/span>    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">em<\/span>&gt;<\/span># main still contained the toleration<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">em<\/span>&gt;<\/span>\ngit log origin\/main..HEAD      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">em<\/span>&gt;<\/span># the fix commit was not in main<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">em<\/span>&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\"><strong>Fix:<\/strong>&nbsp;Merge the change into the branch TFC tracks, then apply.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\"><strong>Lesson:<\/strong>&nbsp;&#8220;I applied it&#8221; means&nbsp;<em>the pipeline applied the merged code<\/em>, not &#8220;I saved the file.&#8221; Always confirm which commit the run used.<\/p>\n<\/blockquote>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Failure Behaviour #2 \u2014 Stale&nbsp;<code>origin\/main<\/code>&nbsp;gave a wrong conclusion<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Symptom:<\/strong>&nbsp;We concluded &#8220;main doesn&#8217;t have the fix&#8221; \u2014 but it actually did. The PR had already been merged.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Root cause:<\/strong>&nbsp;Local remote-tracking refs are a&nbsp;<strong>snapshot from the last fetch<\/strong>.&nbsp;<code>git show origin\/main:...<\/code>&nbsp;reads that stale snapshot, not the live remote.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Fix:<\/strong><\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\">git fetch origin --quiet\ngit show origin\/main:<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">file<\/span>&gt;<\/span>                       <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">em<\/span>&gt;<\/span># now reflects reality<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">em<\/span>&gt;<\/span>\ngit merge-base --is-ancestor <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">sha<\/span>&gt;<\/span> origin\/main &amp;&amp; echo \"in main\"\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\"><strong>Lesson:<\/strong>&nbsp;Always&nbsp;<code>git fetch<\/code>&nbsp;before reasoning about remote branch state. Most &#8220;the merge didn&#8217;t happen&#8221; panics are stale refs.<\/p>\n<\/blockquote>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Failure Behaviour #3 \u2014 Helm reported &#8220;updated&#8221;, but nothing rolled<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Symptom:<\/strong>&nbsp;The TFC apply log said:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">Plan: 0 to add, 2 to change, 0 to destroy.\n... module....metrics_server.helm_release.this&#91;0]: Plan to update\nupdated  id=metrics-server\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">A new Helm&nbsp;<strong>revision<\/strong>&nbsp;was created. Yet:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\">helm -n kube-system get values metrics-server   <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">em<\/span>&gt;<\/span># toleration STILL present<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">em<\/span>&gt;<\/span>\nkubectl get pods -l ... -o wide                  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">em<\/span>&gt;<\/span># pod AGE unchanged (47h)<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">em<\/span>&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\"><strong>Root cause:<\/strong>&nbsp;Two things were true at once:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>The run executed against a commit that\u00a0<em>still had the toleration<\/em>\u00a0(timing \/ pinned-commit issue \u2014 see #1\/#2). So the values Helm received were unchanged in the part that matters.<\/li>\n\n\n\n<li>A Helm\u00a0<strong>upgrade always bumps the revision number<\/strong>, even when the rendered manifest is identical. A new revision is\u00a0<em>not<\/em>\u00a0proof of a real change.<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Mental model #2 \u2014 revision number is not a diff.<\/strong>&nbsp;The only reliable evidence that the manifest changed is the&nbsp;<strong>rolled pod<\/strong>&nbsp;(fresh&nbsp;<code>AGE<\/code>) and the actual rendered output (<code>helm get manifest<\/code>).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>How we detected it:<\/strong><\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\">helm -n kube-system history metrics-server          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">em<\/span>&gt;<\/span># revision incremented...<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">em<\/span>&gt;<\/span>\nkubectl get pods -l app.kubernetes.io\/name=metrics-server -o wide  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">em<\/span>&gt;<\/span># ...but AGE did NOT reset<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">em<\/span>&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\"><strong>Lesson:<\/strong>&nbsp;Verify outcomes by&nbsp;<strong>observed cluster state<\/strong>, not by Terraform&#8217;s &#8220;updated&#8221; or Helm&#8217;s revision counter. Pod&nbsp;<code>AGE<\/code>&nbsp;is the truth.<\/p>\n<\/blockquote>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Failure Behaviour #4 \u2014 The Terraform&nbsp;<code>helm_release<\/code>&nbsp;drift blind spot<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">This was the core, non-obvious one.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Symptom:<\/strong>&nbsp;After the fix was genuinely in&nbsp;<code>main<\/code>, a fresh TFC run reported&nbsp;<strong>&#8220;No changes&#8221;<\/strong>&nbsp;\u2014 while the live release&nbsp;<em>still<\/em>&nbsp;carried the toleration.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">helm -n kube-system get values metrics-server\n&lt;em&gt;<span class=\"hljs-comment\"># USER-SUPPLIED VALUES still shows the toleration&lt;\/em&gt;<\/span>\n\n&lt;em&gt;<span class=\"hljs-comment\"># ...but TFC plan: No changes. Your infrastructure matches the configuration.&lt;\/em&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\"><strong>Root cause:<\/strong>&nbsp;The Terraform Helm provider compares your&nbsp;<strong>config<\/strong>&nbsp;against&nbsp;<strong>Terraform state<\/strong>&nbsp;\u2014 it does&nbsp;<strong>not<\/strong>&nbsp;re-read and diff the&nbsp;<em>live release&#8217;s<\/em>&nbsp;actual values on every plan. Once a prior run wrote &#8220;no toleration&#8221; into&nbsp;<strong>state<\/strong>,&nbsp;<code>config == state<\/code>, so Terraform sees nothing to do \u2014 even though the&nbsp;<strong>live cluster release had drifted<\/strong>&nbsp;and still had the toleration.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"> <code>  config (no toleration)  ==  state (no toleration)   -&gt; \"No changes\"\n                                       \u2260\n                          live release (HAS toleration)   &lt;-- invisible to plan\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Mental model #3 \u2014&nbsp;<code>helm_release<\/code>&nbsp;trusts state, not the cluster.<\/strong>&nbsp;Out-of-band drift in a release&#8217;s rendered values is frequently&nbsp;<em>not<\/em>&nbsp;detected by a normal plan. &#8220;No changes&#8221; means &#8220;config matches state,&#8221; not &#8220;cluster matches config.&#8221;<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>How we confirmed it:<\/strong><\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">em<\/span>&gt;<\/span># What the live release actually has:<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">em<\/span>&gt;<\/span>\nhelm -n kube-system get values metrics-server | grep -i -A3 toleration   <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">em<\/span>&gt;<\/span># present<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">em<\/span>&gt;<\/span>\n\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">em<\/span>&gt;<\/span># What Terraform state thinks it has:<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">em<\/span>&gt;<\/span>\nterraform state show '...metrics_server.helm_release.this&#91;0]' | grep -i -A3 toleration  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">em<\/span>&gt;<\/span># absent<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">em<\/span>&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Present in one, absent in the other = confirmed drift the plan won&#8217;t catch.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Fixes (any one works)<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>A. Force a real value change (VCS-native, no special access).<\/strong>&nbsp;Make the&nbsp;<code>values<\/code>&nbsp;genuinely differ from state so Terraform&nbsp;<em>must<\/em>&nbsp;run&nbsp;<code>helm upgrade<\/code>, which re-renders from current config and drops the toleration:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">metrics_server = {\n  values = &#91;yamlencode({\n    podLabels = { <span class=\"hljs-string\">\"k8s-app\"<\/span> = <span class=\"hljs-string\">\"metrics-server\"<\/span> }   <span class=\"hljs-comment\"># benign, also matches other envs<\/span>\n  })]\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\"><strong>B. Force-replace the release (the literal &#8220;force apply&#8221;).<\/strong>&nbsp;TFC has no UI button for targeted replace; use the API&nbsp;<code>replace-addrs<\/code>&nbsp;or a CLI-driven run:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">&lt;em&gt;<span class=\"hljs-comment\"># CLI-driven (only if the workspace permits CLI applies):&lt;\/em&gt;<\/span>\nterraform apply -replace=<span class=\"hljs-string\">'module....metrics_server.helm_release.this&#91;0]'<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">&lt;em&gt;<span class=\"hljs-comment\"># TFC API run with replace-addrs (works for VCS workspaces):&lt;\/em&gt;<\/span>\ncurl -s \\\n  --header <span class=\"hljs-string\">\"Authorization: Bearer $TFC_TOKEN\"<\/span> \\\n  --header <span class=\"hljs-string\">\"Content-Type: application\/vnd.api+json\"<\/span> \\\n  --request POST \\\n  --data <span class=\"hljs-string\">'{\n    \"data\": {\n      \"type\": \"runs\",\n      \"attributes\": {\n        \"message\": \"force replace metrics-server (toleration drift)\",\n        \"replace-addrs\": &#91;\"module....metrics_server.helm_release.this&#91;0]\"]\n      },\n      \"relationships\": { \"workspace\": { \"data\": { \"type\": \"workspaces\", \"id\": \"ws-XXXX\" } } }\n    }\n  }'<\/span> \\\n  https:<span class=\"hljs-comment\">\/\/app.terraform.io\/api\/v2\/runs<\/span>\n&lt;em&gt;<span class=\"hljs-comment\"># then Confirm &amp; Apply the run in the UI&lt;\/em&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">We used&nbsp;<strong>Option A<\/strong>&nbsp;\u2014 it goes through the normal PR \u2192 apply gate and is auditable.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\"><strong>Lesson:<\/strong>&nbsp;When&nbsp;<code>plan<\/code>&nbsp;says &#8220;no changes&#8221; but the cluster disagrees, you have state\u2194cluster drift. Don&#8217;t keep re-running plan \u2014 force the resource to reconcile (value change or&nbsp;<code>-replace<\/code>).<\/p>\n<\/blockquote>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Failure Behaviour #5 \u2014 Branch hygiene \/ &#8220;fix is in a feature branch&#8221;<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Symptom:<\/strong>&nbsp;The fix existed and was committed&#8230; on an unmerged feature branch, while the workspace deployed from&nbsp;<code>main<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Fix:<\/strong>&nbsp;Standard hygiene \u2014 confirm the fix is on the deployed branch:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-11\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">git fetch origin\ngit merge-base --is-ancestor &lt;fix-sha&gt; origin\/main &amp;&amp; <span class=\"hljs-keyword\">echo<\/span> <span class=\"hljs-string\">\"deployable\"<\/span> || <span class=\"hljs-keyword\">echo<\/span> <span class=\"hljs-string\">\"still in a branch\"<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\"><strong>Lesson:<\/strong>&nbsp;&#8220;Committed&#8221; \u2260 &#8220;deployed.&#8221; Confirm the SHA the workspace will use.<\/p>\n<\/blockquote>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">What success finally looked like<\/h2>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-12\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">$ helm -n kube-system history metrics-server\nREVISION  UPDATED                   STATUS       ...\n<span class=\"hljs-number\">1<\/span>         Wed Mar  <span class=\"hljs-number\">4<\/span> <span class=\"hljs-number\">09<\/span>:<span class=\"hljs-number\">53<\/span>:<span class=\"hljs-number\">46<\/span> <span class=\"hljs-number\">2026<\/span>  superseded\n<span class=\"hljs-number\">2<\/span>         Fri Jun <span class=\"hljs-number\">19<\/span> <span class=\"hljs-number\">04<\/span>:<span class=\"hljs-number\">25<\/span>:<span class=\"hljs-number\">37<\/span> <span class=\"hljs-number\">2026<\/span>  superseded\n<span class=\"hljs-number\">3<\/span>         Fri Jun <span class=\"hljs-number\">19<\/span> <span class=\"hljs-number\">05<\/span>:<span class=\"hljs-number\">21<\/span>:<span class=\"hljs-number\">07<\/span> <span class=\"hljs-number\">2026<\/span>  deployed     &lt;em&gt;<span class=\"hljs-comment\"># &lt;-- fresh&lt;\/em&gt;<\/span>\n\n$ helm -n kube-system get values metrics-server\nUSER-SUPPLIED VALUES:\npodLabels:\n  k8s-app: metrics-server          &lt;em&gt;<span class=\"hljs-comment\"># &lt;-- no tolerations&lt;\/em&gt;<\/span>\n\n$ kubectl -n kube-system get pods -l app.kubernetes.io\/name=metrics-server -o wide\nNAME                              READY  STATUS   RESTARTS  AGE     ...\nmetrics-server<span class=\"hljs-number\">-64585697<\/span>f6-nbgsg   <span class=\"hljs-number\">1<\/span>\/<span class=\"hljs-number\">1<\/span>    Running  <span class=\"hljs-number\">0<\/span>         <span class=\"hljs-number\">2<\/span>m39s   &lt;em&gt;<span class=\"hljs-comment\"># &lt;-- AGE reset = rolled&lt;\/em&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-12\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">New revision&nbsp;<strong>+<\/strong>&nbsp;value gone&nbsp;<strong>+<\/strong>&nbsp;pod&nbsp;<code>AGE<\/code>&nbsp;reset = genuinely done.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Diagnostic cheat sheet<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th class=\"has-text-align-left\" data-align=\"left\">Question<\/th><th class=\"has-text-align-left\" data-align=\"left\">Command<\/th><\/tr><\/thead><tbody><tr><td>Which commit\/branch did the run use?<\/td><td>TFC run detail \u2192 &#8220;Configuration \/ Commit&#8221;; locally&nbsp;<code>git fetch &amp;&amp; git log origin\/main..HEAD<\/code><\/td><\/tr><tr><td>Is my fix actually in the deployed branch?<\/td><td><code>git merge-base --is-ancestor &lt;sha&gt; origin\/main<\/code><\/td><\/tr><tr><td>What did Helm actually deploy?<\/td><td><code>helm -n &lt;ns&gt; get manifest &lt;release&gt;<\/code><\/td><\/tr><tr><td>What user values are live?<\/td><td><code>helm -n &lt;ns&gt; get values &lt;release&gt;<\/code><\/td><\/tr><tr><td>Did a new revision deploy?<\/td><td><code>helm -n &lt;ns&gt; history &lt;release&gt;<\/code><\/td><\/tr><tr><td><strong>Did the pod actually roll?<\/strong><\/td><td><code>kubectl -n &lt;ns&gt; get pods -l &lt;selector&gt; -o wide<\/code>&nbsp;\u2192 check&nbsp;<code>AGE<\/code><\/td><\/tr><tr><td>What does TF state think?<\/td><td><code>terraform state show '&lt;addr&gt;'<\/code><\/td><\/tr><tr><td>Are there NodePools\/taints that matter?<\/td><td><code>kubectl get nodepools.karpenter.sh<\/code>;&nbsp;<code>kubectl get nodepool &lt;n&gt; -o jsonpath='{.spec.template.spec.taints}'<\/code><\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Key takeaways<\/h2>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Toleration \u2260 placement.<\/strong>\u00a0A toleration only permits scheduling onto a matching taint; with no such taint it is inert. Removing inert config is cleanup, not a behaviour change.<\/li>\n\n\n\n<li><strong>Pod\u00a0<code>AGE<\/code>\u00a0is the source of truth.<\/strong>\u00a0Terraform &#8220;updated&#8221; and Helm revision bumps can both happen with\u00a0<em>no real change<\/em>. A rolled pod proves it.<\/li>\n\n\n\n<li><strong><code>helm_release<\/code>\u00a0compares config to state, not to the cluster.<\/strong>\u00a0&#8220;No changes&#8221; can hide live drift. Reconcile with a forced value change or\u00a0<code>-replace<\/code>.<\/li>\n\n\n\n<li><strong>&#8220;Applied&#8221; means the pipeline applied the merged code.<\/strong>\u00a0Always verify the commit\/branch the run used;\u00a0<code>git fetch<\/code>\u00a0before trusting remote refs.<\/li>\n\n\n\n<li><strong>Verify on the cluster, not in the plan.<\/strong>\u00a0<code>helm get values<\/code>\u00a0\/\u00a0<code>get manifest<\/code>\u00a0\/ pod\u00a0<code>AGE<\/code>\u00a0beat any amount of staring at Terraform output.<\/li>\n<\/ol>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"><em>Generated from an incident review. Commands shown for EKS Auto Mode + Terraform Cloud +&nbsp;<code>aws-ia\/eks-blueprints-addons<\/code>; the principles apply to any Terraform + Helm provider setup.<\/em><\/p>\n","protected":false},"excerpt":{"rendered":"<p>A field tutorial based on a real incident removing a stale&nbsp;CriticalAddonsOnly&nbsp;toleration from&nbsp;metrics-server&nbsp;and&nbsp;KEDA&nbsp;on EKS Auto Mode clusters managed by Terraform Cloud (TFC) + the&nbsp;eks-blueprints-addons&nbsp;module. TL;DR We tried to&#8230; <\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_joinchat":[],"footnotes":""},"categories":[11138],"tags":[],"class_list":["post-79714","post","type-post","status-publish","format-standard","hentry","category-best-tools"],"_links":{"self":[{"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/posts\/79714","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/comments?post=79714"}],"version-history":[{"count":1,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/posts\/79714\/revisions"}],"predecessor-version":[{"id":79715,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/posts\/79714\/revisions\/79715"}],"wp:attachment":[{"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/media?parent=79714"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/categories?post=79714"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/tags?post=79714"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}