<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Argocd on bismigel</title><link>https://bismigel.dev/tags/argocd/</link><description>Recent content in Argocd on bismigel</description><generator>Hugo</generator><language>en-us</language><copyright>bismigel</copyright><lastBuildDate>Tue, 20 Oct 2026 10:00:00 -0400</lastBuildDate><atom:link href="https://bismigel.dev/tags/argocd/index.xml" rel="self" type="application/rss+xml"/><item><title>Getting ArgoCD Redis HA through the chart, not around it</title><link>https://bismigel.dev/posts/2026-10-20-argocd-redis-ha-through-the-chart/</link><pubDate>Tue, 20 Oct 2026 10:00:00 -0400</pubDate><guid>https://bismigel.dev/posts/2026-10-20-argocd-redis-ha-through-the-chart/</guid><description/><content:encoded><![CDATA[<p>The previous post used the official ArgoCD HA manifests and solved a scheduling problem. This one is about the Helm chart. The chart has a different failure, and it took a disposable cluster to see it clearly.</p>
<p>ArgoCD was already Helm-owned. Single Redis was healthy, but it was the last obvious single point of failure in the control plane. Enabling <code>redis-ha.enabled</code> was supposed to be a values change.</p>
<p>It was not.</p>
<h1 id="the-symptom">The symptom</h1>
<p>The first rollout reached two Redis pods and two HAProxy pods. The third Redis pod stayed in init:</p>
<div class="code-block">
  <div class="code-block-header">
    <span class="code-block-lang">text</span>
    <button type="button" class="code-copy" aria-label="Copy code">
      <span class="code-copy-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg>
</span>
      <span class="code-copy-icon code-copy-icon-check"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
</span>
      <span class="code-copy-label">Copy</span>
    </button>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">argocd-redis-ha-server-0   0/3   Init:0/1
</span></span><span class="line"><span class="cl">argocd-redis-ha-haproxy    2/3   Running</span></span></code></pre></div>
</div>
<p>The init log was not a Redis crash:</p>
<div class="code-block">
  <div class="code-block-header">
    <span class="code-block-lang">text</span>
    <button type="button" class="code-copy" aria-label="Copy code">
      <span class="code-copy-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg>
</span>
      <span class="code-copy-icon code-copy-icon-check"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
</span>
      <span class="code-copy-label">Copy</span>
    </button>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Identifying redis master (get-master-addr-by-name)..
</span></span><span class="line"><span class="cl">  using sentinel (argocd-redis-ha), sentinel group name (argocd)
</span></span><span class="line"><span class="cl">Could not connect to Redis at argocd-redis-ha:26379: Try again</span></span></code></pre></div>
</div>
<p>HAProxy was waiting for the same sentinel. The announce endpoint existed, but connecting to it timed out. The pod could not become ready, so the sentinel Service had no usable endpoint, so the lookup kept failing.</p>
<h1 id="first-rule-out-the-test-cluster">First, rule out the test cluster</h1>
<p>I built three disposable NixOS VMs on <code>calamansi-intel</code> to test this without touching production. The first test was invalid.</p>
<p>k3s picked the wrong network interface for Flannel:</p>
<div class="code-block">
  <div class="code-block-header">
    <span class="code-block-lang">text</span>
    <button type="button" class="code-copy" aria-label="Copy code">
      <span class="code-copy-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg>
</span>
      <span class="code-copy-icon code-copy-icon-check"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
</span>
      <span class="code-copy-label">Copy</span>
    </button>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">flannel.1 local 10.0.2.15 dev eth0</span></span></code></pre></div>
</div>
<p>That is the QEMU NAT address, not the cluster network. CoreDNS was unreachable across nodes, and Redis init hung on <code>getent hosts</code>. The fix was:</p>
<div class="code-block">
  <div class="code-block-header">
    <span class="code-block-lang">text</span>
    <button type="button" class="code-copy" aria-label="Copy code">
      <span class="code-copy-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg>
</span>
      <span class="code-copy-icon code-copy-icon-check"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
</span>
      <span class="code-copy-label">Copy</span>
    </button>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">--flannel-iface=eth1</span></span></code></pre></div>
</div>
<p>The k3s Flannel interface option is documented here: <a href="https://docs.k3s.io/networking/basic-network-options#flannel-options" target="_blank" rel="noopener noreferrer">https://docs.k3s.io/networking/basic-network-options#flannel-options</a>
</p>
<p>After reboot, DNS and cross-node pod traffic worked. The Redis failure remained. That ruled out CNI, scheduling and resources.</p>
<h1 id="the-real-problem">The real problem</h1>
<p>The chart creates one announce Service per Redis pod, but it hardcodes them as ClusterIP Services:</p>
<div class="code-block">
  <div class="code-block-header">
    <span class="code-block-lang">yaml</span>
    <button type="button" class="code-copy" aria-label="Copy code">
      <span class="code-copy-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg>
</span>
      <span class="code-copy-icon code-copy-icon-check"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
</span>
      <span class="code-copy-label">Copy</span>
    </button>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">publishNotReadyAddresses</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">ClusterIP</span></span></span></code></pre></div>
</div>
<p><code>config-init</code> resolves the announce name and gets the Service VIP, not the pod IP. Sentinel then monitors that VIP. The split-brain container never converges, so the pod stays NotReady.</p>
<p>This is upstream issue:</p>
<p><a href="https://github.com/DandyDeveloper/charts/issues/422" target="_blank" rel="noopener noreferrer">https://github.com/DandyDeveloper/charts/issues/422</a>
</p>
<p>The related masterless deadlock report is: <a href="https://github.com/DandyDeveloper/charts/issues/412" target="_blank" rel="noopener noreferrer">https://github.com/DandyDeveloper/charts/issues/412</a>
</p>
<p>The fix proposed there is simple: make the per-pod announce Services headless. Kubernetes documents headless Services here: <a href="https://kubernetes.io/docs/concepts/services-networking/service/#headless-services" target="_blank" rel="noopener noreferrer">https://kubernetes.io/docs/concepts/services-networking/service/#headless-services</a>
</p>
<div class="code-block">
  <div class="code-block-header">
    <span class="code-block-lang">yaml</span>
    <button type="button" class="code-copy" aria-label="Copy code">
      <span class="code-copy-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg>
</span>
      <span class="code-copy-icon code-copy-icon-check"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
</span>
      <span class="code-copy-label">Copy</span>
    </button>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">clusterIP</span><span class="p">:</span><span class="w"> </span><span class="l">None</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">publishNotReadyAddresses</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span></span></span></code></pre></div>
</div>
<p>I verified it on the test cluster. Announce resolved to a pod IP, Redis reached <code>3/3</code>, HAProxy reached <code>3/3</code>, and deleting the master made sentinel promote another pod. The old master rejoined as a replica.</p>
<h1 id="making-it-repeatable">Making it repeatable</h1>
<p>A manual <code>kubectl patch</code> would be undone by the next Helm upgrade. The chart has no value for this, so I added a Helm post-renderer. It rewrites only the <code>redis-ha</code> announce Services.</p>
<p>The script reads Helm output on stdin and inserts <code>clusterIP: None</code> after <code>spec</code> for any resource whose name matches <code>-announce-[0-9]+</code>:</p>
<div class="code-block">
  <div class="code-block-header">
    <span class="code-block-lang">bash</span>
    <button type="button" class="code-copy" aria-label="Copy code">
      <span class="code-copy-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg>
</span>
      <span class="code-copy-icon code-copy-icon-check"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
</span>
      <span class="code-copy-label">Copy</span>
    </button>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">awk <span class="s1">&#39;
</span></span></span><span class="line"><span class="cl"><span class="s1">  /^---[[:space:]]*$/ { announce=0; print; next }
</span></span></span><span class="line"><span class="cl"><span class="s1">  /^  name:[[:space:]]+.*-announce-[0-9]+[[:space:]]*$/ { announce=1 }
</span></span></span><span class="line"><span class="cl"><span class="s1">  /^spec:[[:space:]]*$/ &amp;&amp; announce { print; print &#34;  clusterIP: None&#34;; next }
</span></span></span><span class="line"><span class="cl"><span class="s1">  { print }
</span></span></span><span class="line"><span class="cl"><span class="s1">&#39;</span></span></span></code></pre></div>
</div>
<p>The values also need a toleration for the tainted control-plane node. The chart reads <code>redis-ha.tolerations</code> at the top level, not under <code>redis</code> or <code>haproxy</code>. That detail cost one more rollout.</p>
<div class="code-block">
  <div class="code-block-header">
    <span class="code-block-lang">yaml</span>
    <button type="button" class="code-copy" aria-label="Copy code">
      <span class="code-copy-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg>
</span>
      <span class="code-copy-icon code-copy-icon-check"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
</span>
      <span class="code-copy-label">Copy</span>
    </button>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">redis-ha</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">enabled</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">tolerations</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">key</span><span class="p">:</span><span class="w"> </span><span class="l">node-role.kubernetes.io/control-plane</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">operator</span><span class="p">:</span><span class="w"> </span><span class="l">Exists</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">effect</span><span class="p">:</span><span class="w"> </span><span class="l">NoSchedule</span></span></span></code></pre></div>
</div>
<h1 id="the-deploy">The deploy</h1>
<p>Helm 4 no longer accepts an executable post-renderer, so this used Helm 3.17.3:</p>
<div class="code-block">
  <div class="code-block-header">
    <span class="code-block-lang">bash</span>
    <button type="button" class="code-copy" aria-label="Copy code">
      <span class="code-copy-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg>
</span>
      <span class="code-copy-icon code-copy-icon-check"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
</span>
      <span class="code-copy-label">Copy</span>
    </button>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">helm -n argocd upgrade --install argocd argo/argo-cd <span class="se">\
</span></span></span><span class="line"><span class="cl">  --version 10.10.5 -f manifests/argocd/values.yaml <span class="se">\
</span></span></span><span class="line"><span class="cl">  --post-renderer ./scripts/argocd-redis-ha-post-renderer.sh <span class="se">\
</span></span></span><span class="line"><span class="cl">  --take-ownership --wait --timeout 12m</span></span></code></pre></div>
</div>
<p>The Redis server ConfigMap changed, but the existing ArgoCD pods kept their old cache endpoint. Restarting the consumers fixed the last connection errors:</p>
<div class="code-block">
  <div class="code-block-header">
    <span class="code-block-lang">bash</span>
    <button type="button" class="code-copy" aria-label="Copy code">
      <span class="code-copy-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg>
</span>
      <span class="code-copy-icon code-copy-icon-check"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
</span>
      <span class="code-copy-label">Copy</span>
    </button>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">kubectl -n argocd rollout restart <span class="se">\
</span></span></span><span class="line"><span class="cl">  deploy/argocd-repo-server deploy/argocd-server <span class="se">\
</span></span></span><span class="line"><span class="cl">  deploy/argocd-applicationset-controller <span class="se">\
</span></span></span><span class="line"><span class="cl">  statefulset/argocd-application-controller</span></span></code></pre></div>
</div>
<h1 id="verify">Verify</h1>
<div class="code-block">
  <div class="code-block-header">
    <span class="code-block-lang">bash</span>
    <button type="button" class="code-copy" aria-label="Copy code">
      <span class="code-copy-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg>
</span>
      <span class="code-copy-icon code-copy-icon-check"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
</span>
      <span class="code-copy-label">Copy</span>
    </button>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">kubectl -n argocd get pods -l <span class="s1">&#39;app.kubernetes.io/name in (argocd-redis-ha,argocd-redis-ha-haproxy)&#39;</span>
</span></span><span class="line"><span class="cl">kubectl -n argocd get endpoints argocd-redis-ha argocd-redis-ha-haproxy
</span></span><span class="line"><span class="cl">kubectl -n argocd get app root</span></span></code></pre></div>
</div>
<p>Production result:</p>
<ul>
<li>Redis <code>3/3</code> across all three nodes;</li>
<li>HAProxy <code>3/3</code> across all three nodes;</li>
<li>both Services have three endpoints;</li>
<li>ArgoCD root is Synced and Healthy;</li>
<li>29 applications Synced and Healthy, with the existing Cilium drift still showing as OutOfSync but Healthy.</li>
</ul>
<p>The chart still owns the workaround, not the fix. The post-renderer has to be included on every upgrade, and the consumers need a restart after the Redis endpoint changes. The rollback is still a Helm rollback to the single Redis revision.</p>
<h1 id="links">Links</h1>
<ul>
<li>Upstream headless announce issue: <a href="https://github.com/DandyDeveloper/charts/issues/422" target="_blank" rel="noopener noreferrer">https://github.com/DandyDeveloper/charts/issues/422</a>
</li>
<li>Previous post, official HA manifest route: <a href="/posts/2026-10-19-argocd-redis-ha-on-tainted-k3s/">ArgoCD Redis HA on a tainted k3s cluster</a>
</li>
<li>Helm post-renderers: <a href="https://helm.sh/docs/topics/advanced/#post-rendering" target="_blank" rel="noopener noreferrer">https://helm.sh/docs/topics/advanced/#post-rendering</a>
</li>
<li>ArgoCD high availability: <a href="https://argo-cd.readthedocs.io/en/stable/operator-manual/high_availability/" target="_blank" rel="noopener noreferrer">https://argo-cd.readthedocs.io/en/stable/operator-manual/high_availability/</a>
</li>
<li>Production GitOps changes: <code>bakuran-gitops</code> PR #124, #125 and #126 (the repository is private, so there is no public link).</li>
</ul>
]]></content:encoded></item><item><title>ArgoCD Redis HA on a tainted k3s cluster</title><link>https://bismigel.dev/posts/2026-10-19-argocd-redis-ha-on-tainted-k3s/</link><pubDate>Mon, 19 Oct 2026 14:00:00 -0400</pubDate><guid>https://bismigel.dev/posts/2026-10-19-argocd-redis-ha-on-tainted-k3s/</guid><description/><content:encoded><![CDATA[<blockquote>
<p><strong>Update 2026-10-20:</strong> This post documents the official-manifest attempt. Production later moved to the Helm chart with a headless announce post-renderer. The follow-up is <a href="/posts/2026-10-20-argocd-redis-ha-through-the-chart/">Getting ArgoCD Redis HA through the chart, not around it</a>
.</p>
</blockquote>
<p>ArgoCD stores desired state in etcd. Redis is a cache. Losing Redis pauses reconciliation, but it does not take down running workloads. That made the single Redis pod easy to ignore, until it became the last obvious single point of failure in the control plane.</p>
<p>This is the retry that worked.</p>
<h1 id="the-setup">The setup</h1>
<p>The cluster has three k3s nodes. Two are general schedulers. The third, <code>kamatis-mbp</code>, is control-plane and etcd only, with a <code>NoSchedule</code> taint.</p>
<p>I used the official ArgoCD HA Redis resources, not the previous third-party chart attempt:</p>
<ul>
<li>three Redis servers with sentinel;</li>
<li>three HAProxy pods;</li>
<li>headless announce services for sentinel discovery;</li>
<li>ArgoCD pointed at <code>argocd-redis-ha-haproxy:6379</code>.</li>
</ul>
<p>The official HA manifest is here: <a href="https://github.com/argoproj/argo-cd/blob/master/manifests/ha/install.yaml" target="_blank" rel="noopener noreferrer">https://github.com/argoproj/argo-cd/blob/master/manifests/ha/install.yaml</a>
</p>
<h1 id="what-failed">What failed</h1>
<p>Redis was healthy on two nodes. The third pod stayed Pending:</p>
<div class="code-block">
  <div class="code-block-header">
    <span class="code-block-lang">text</span>
    <button type="button" class="code-copy" aria-label="Copy code">
      <span class="code-copy-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg>
</span>
      <span class="code-copy-icon code-copy-icon-check"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
</span>
      <span class="code-copy-label">Copy</span>
    </button>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">0/3 nodes are available:
</span></span><span class="line"><span class="cl">  1 node(s) had untolerated taint(s),
</span></span><span class="line"><span class="cl">  2 node(s) didn&#39;t match pod anti-affinity rules.</span></span></code></pre></div>
</div>
<p>The HA manifests use required pod anti-affinity so Redis and sentinel are spread across three nodes. That is correct for quorum. But the third node was tainted, so the scheduler had no legal place to put the third replica.</p>
<p>The first HAProxy pod hit the same wall. During the rollout, old HAProxy pods also held their nodes, so a replacement stayed Pending even after the new ReplicaSet was ready.</p>
<h1 id="the-fix">The fix</h1>
<p>Add a toleration for the control-plane taint to both the Redis StatefulSet and the HAProxy Deployment:</p>
<div class="code-block">
  <div class="code-block-header">
    <span class="code-block-lang">bash</span>
    <button type="button" class="code-copy" aria-label="Copy code">
      <span class="code-copy-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg>
</span>
      <span class="code-copy-icon code-copy-icon-check"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
</span>
      <span class="code-copy-label">Copy</span>
    </button>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nv">TOL</span><span class="o">=</span><span class="s1">&#39;[{&#34;key&#34;:&#34;node-role.kubernetes.io/control-plane&#34;,&#34;operator&#34;:&#34;Exists&#34;,&#34;effect&#34;:&#34;NoSchedule&#34;}]&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">kubectl -n argocd patch statefulset argocd-redis-ha-server --type merge <span class="se">\
</span></span></span><span class="line"><span class="cl">  -p <span class="s2">&#34;{\&#34;spec\&#34;:{\&#34;template\&#34;:{\&#34;spec\&#34;:{\&#34;tolerations\&#34;:</span><span class="nv">$TOL</span><span class="s2">}}}}&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">kubectl -n argocd patch deploy argocd-redis-ha-haproxy --type merge <span class="se">\
</span></span></span><span class="line"><span class="cl">  -p <span class="s2">&#34;{\&#34;spec\&#34;:{\&#34;template\&#34;:{\&#34;spec\&#34;:{\&#34;tolerations\&#34;:</span><span class="nv">$TOL</span><span class="s2">}}}}&#34;</span></span></span></code></pre></div>
</div>
<p>Then remove the stale rolling pods so the scheduler can place the replacements. Finally point ArgoCD at HAProxy:</p>
<div class="code-block">
  <div class="code-block-header">
    <span class="code-block-lang">bash</span>
    <button type="button" class="code-copy" aria-label="Copy code">
      <span class="code-copy-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg>
</span>
      <span class="code-copy-icon code-copy-icon-check"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
</span>
      <span class="code-copy-label">Copy</span>
    </button>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">kubectl -n argocd patch configmap argocd-cmd-params-cm --type merge <span class="se">\
</span></span></span><span class="line"><span class="cl">  -p <span class="s1">&#39;{&#34;data&#34;:{&#34;redis.server&#34;:&#34;argocd-redis-ha-haproxy:6379&#34;}}&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">kubectl -n argocd rollout restart <span class="se">\
</span></span></span><span class="line"><span class="cl">  deploy/argocd-server deploy/argocd-repo-server <span class="se">\
</span></span></span><span class="line"><span class="cl">  deploy/argocd-applicationset-controller <span class="se">\
</span></span></span><span class="line"><span class="cl">  statefulset/argocd-application-controller</span></span></code></pre></div>
</div>
<h1 id="verify">Verify</h1>
<div class="code-block">
  <div class="code-block-header">
    <span class="code-block-lang">bash</span>
    <button type="button" class="code-copy" aria-label="Copy code">
      <span class="code-copy-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg>
</span>
      <span class="code-copy-icon code-copy-icon-check"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
</span>
      <span class="code-copy-label">Copy</span>
    </button>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">kubectl -n argocd get pods -l <span class="s1">&#39;app.kubernetes.io/name in (argocd-redis-ha,argocd-redis-ha-haproxy)&#39;</span>
</span></span><span class="line"><span class="cl">kubectl -n argocd get app root
</span></span><span class="line"><span class="cl">kubectl -n argocd logs deploy/argocd-repo-server --tail<span class="o">=</span><span class="m">50</span></span></span></code></pre></div>
</div>
<p>Expected:</p>
<ul>
<li>three Redis server pods at <code>3/3</code>;</li>
<li>three HAProxy pods at <code>1/1</code>;</li>
<li>ArgoCD root app Synced and Healthy;</li>
<li>no Redis connection errors in the repo server.</li>
</ul>
<p>At the time, the old single Redis deployment was kept as a fallback. The follow-up replaced this path with chart-managed HA; the fallback is now a Helm rollback revision, not a running second Redis.</p>
<h1 id="why-this-was-not-a-redis-bug">Why this was not a Redis bug</h1>
<p>The failure was scheduling policy, not Redis, sentinel or ArgoCD. The official HA manifest assumes three schedulable nodes. A tainted control-plane node needs an explicit toleration. That is a cluster decision, not a chart defect.</p>
<p>The bootstrap script now applies the HA resources, adds the toleration and sets the HAProxy endpoint, so a rebuild does not depend on manual patches.</p>
<h1 id="sentinel-cpu-spin">Sentinel CPU spin</h1>
<p>Redis HA has a known sentinel bug. After a restart, one or two sentinel containers can pin a full CPU core with no useful log output. ArgoCD stays healthy, but the core burn is real.</p>
<p>The workaround is a delayed sentinel reset after startup:</p>
<div class="code-block">
  <div class="code-block-header">
    <span class="code-block-lang">yaml</span>
    <button type="button" class="code-copy" aria-label="Copy code">
      <span class="code-copy-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg>
</span>
      <span class="code-copy-icon code-copy-icon-check"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
</span>
      <span class="code-copy-label">Copy</span>
    </button>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">lifecycle</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">postStart</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">exec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">command</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&#34;/bin/sh&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;-c&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;sleep 30; redis-cli -p 26379 sentinel reset argocd&#34;</span><span class="p">]</span></span></span></code></pre></div>
</div>
<p>The official-manifest bootstrap applied that to the sentinel container. The Helm chart path does not. There, container CPU alerts and the Redis HA alerts are the safety net; apply the same reset by hand if a sentinel pins a core. It is a workaround, not an upstream fix.</p>
<ul>
<li>Redis bug: <a href="https://github.com/redis/redis/issues/9956" target="_blank" rel="noopener noreferrer">https://github.com/redis/redis/issues/9956</a>
</li>
<li>ArgoCD bug: <a href="https://github.com/argoproj/argo-cd/issues/16360" target="_blank" rel="noopener noreferrer">https://github.com/argoproj/argo-cd/issues/16360</a>
</li>
<li>Patch reference: <a href="https://github.com/utilitywarehouse/system-manifests/pull/286" target="_blank" rel="noopener noreferrer">https://github.com/utilitywarehouse/system-manifests/pull/286</a>
</li>
</ul>
<h1 id="links">Links</h1>
<ul>
<li>ArgoCD high availability: <a href="https://argo-cd.readthedocs.io/en/stable/operator-manual/high_availability/" target="_blank" rel="noopener noreferrer">https://argo-cd.readthedocs.io/en/stable/operator-manual/high_availability/</a>
</li>
<li>Official HA install manifest: <a href="https://github.com/argoproj/argo-cd/blob/master/manifests/ha/install.yaml" target="_blank" rel="noopener noreferrer">https://github.com/argoproj/argo-cd/blob/master/manifests/ha/install.yaml</a>
</li>
<li>Kubernetes taints and tolerations: <a href="https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/" target="_blank" rel="noopener noreferrer">https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/</a>
</li>
<li>The edge split and Cilium migration: <a href="/posts/2026-10-18-homelab-network-migration/">Homelab network migration</a>
</li>
</ul>
]]></content:encoded></item></channel></rss>