<?xml version="1.0" encoding="utf-8"?><?xml-stylesheet type="text/xsl" href="atom.xsl"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <id>https://tanmay.dev/hi/blog/</id>
    <title>Tanmay Mohan — Senior Platform Engineer, GPU inference on EKS Blog</title>
    <updated>2026-10-11T00:00:00.000Z</updated>
    <generator>https://github.com/jpmonette/feed</generator>
    <link rel="alternate" href="https://tanmay.dev/hi/blog/"/>
    <subtitle>Tanmay Mohan — Senior Platform Engineer, GPU inference on EKS Blog</subtitle>
    <icon>https://tanmay.dev/hi/img/favicon.svg</icon>
    <entry>
        <title type="html"><![CDATA[Finding a Regression in NVIDIA Container Toolkit 1.20.0]]></title>
        <id>https://tanmay.dev/hi/blog/nvidia-container-toolkit-regression-fix/</id>
        <link href="https://tanmay.dev/hi/blog/nvidia-container-toolkit-regression-fix/"/>
        <updated>2026-10-11T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[How I found and reproduced a regression in NVIDIA Container Toolkit 1.20.0 that broke GPU workloads on EKS: the new CDI hook tried to mkdir under a read-only ConfigMap mount, the device plugin never started, and every GPU node advertised 0 GPUs. Filed upstream (NVIDIA/nvidia-container-toolkit#2026), NVIDIA merged the fix in PR #2028, released in v1.20.1.]]></summary>
        <content type="html"><![CDATA[<p>GPU workloads on our EKS clusters started failing after a routine node group upgrade. The error was cryptic: <code>nvidia.com/gpu</code> resource not found on nodes that clearly had GPUs. Every GPU pod stuck in <code>Pending</code>. This is the story of finding and reproducing a regression in NVIDIA Container Toolkit 1.20.0 that shipped in the EKS NVIDIA AMI v20260818.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-symptom">The Symptom<a href="https://tanmay.dev/hi/blog/nvidia-container-toolkit-regression-fix/#the-symptom" class="hash-link" aria-label="The Symptom का सीधा लिंक" title="The Symptom का सीधा लिंक" translate="no">​</a></h2>
<p>After Karpenter launched new nodes using the latest EKS NVIDIA AMI (v20260818, which included NVIDIA Container Toolkit 1.20.0), pods requesting <code>nvidia.com/gpu</code> stuck in <code>Pending</code>:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">$ kubectl describe pod llm-inference-xyz</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Warning  FailedScheduling  default-scheduler  0/3 nodes are available: 3 Insufficient nvidia.com/gpu.</span><br></div></code></pre></div></div>
<p>But the nodes <em>had</em> GPUs — <code>nvidia-smi</code> showed them, drivers loaded, everything looked fine. The NVIDIA device plugin just wasn't registering the <code>nvidia.com/gpu</code> resource. Something in the toolkit upgrade broke the device plugin's ability to start.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-root-cause">The Root Cause<a href="https://tanmay.dev/hi/blog/nvidia-container-toolkit-regression-fix/#the-root-cause" class="hash-link" aria-label="The Root Cause का सीधा लिंक" title="The Root Cause का सीधा लिंक" translate="no">​</a></h2>
<p>Toolkit 1.20.0 added a new <strong><code>update-application-profile</code> CDI hook</strong>. On every container create, this hook runs:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">mkdir -p /etc/nvidia/nvidia-application-profiles-rc.d</span><br></div></code></pre></div></div>
<p>The NVIDIA device plugin mounts its config at <code>/etc/nvidia</code> as a <strong>read-only ConfigMap</strong> (<code>--config-file=/etc/nvidia/config</code>). When the hook runs <code>mkdir</code> inside that read-only mount, it fails with <code>EROFS</code> (read-only file system). The hook exits non-zero, <code>createContainer</code> fails, and the device plugin never starts — so the node advertises <strong>0 <code>nvidia.com/gpu</code></strong>.</p>
<p><strong>Error in kubelet logs:</strong></p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">createContainer hook #6: exit status 1 ... mkdirat /etc/nvidia/nvidia-application-profiles-rc.d: read-only file system</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="minimal-repro">Minimal Repro<a href="https://tanmay.dev/hi/blog/nvidia-container-toolkit-regression-fix/#minimal-repro" class="hash-link" aria-label="Minimal Repro का सीधा लिंक" title="Minimal Repro का सीधा लिंक" translate="no">​</a></h2>
<p>I stripped it down to the smallest possible test:</p>
<ol>
<li class="">Launch a <code>g5.xlarge</code> (A10G), <code>g6.xlarge</code> (L4), or <code>g6e.xlarge</code> (L40S) with the affected EKS AMI (v20260818)</li>
<li class="">Deploy a pod that triggers the CDI hook — even a pod with <strong>no GPU request</strong> but with environment-based injection triggers it</li>
<li class="">Mount any ConfigMap at <code>/etc/nvidia</code> (read-only)</li>
<li class="">Container creation fails with <code>EROFS</code></li>
</ol>
<p>The same manifests worked on toolkit 1.19.x.</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic"># Minimal repro - deploy this and watch it fail on 1.20.0</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">apiVersion</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> v1</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">kind</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> Pod</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">metadata</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> toolkit</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">repro</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">spec</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">containers</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> test</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">image</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> alpine</span><span class="token punctuation" style="color:#393A34">:</span><span class="token number" style="color:#36acaa">3.20</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">command</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"sleep"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"3600"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">env</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> NVIDIA_VISIBLE_DEVICES</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">value</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"all"</span><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic"># triggers CDI hook</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">volumeMounts</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> config</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">mountPath</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> /etc/nvidia</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">readOnly</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean important" style="color:#36acaa">true</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">volumes</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> config</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">configMap</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> dummy</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">config  </span><span class="token comment" style="color:#999988;font-style:italic"># any ConfigMap</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="reporting--fix">Reporting &amp; Fix<a href="https://tanmay.dev/hi/blog/nvidia-container-toolkit-regression-fix/#reporting--fix" class="hash-link" aria-label="Reporting &amp; Fix का सीधा लिंक" title="Reporting &amp; Fix का सीधा लिंक" translate="no">​</a></h2>
<p>I filed the issue at <strong><a href="https://github.com/NVIDIA/nvidia-container-toolkit/issues/2026" target="_blank" rel="noopener noreferrer" class="">NVIDIA/nvidia-container-toolkit#2026</a></strong> with:</p>
<ul>
<li class="">Minimal repro steps</li>
<li class="">Exact error from kubelet logs</li>
<li class="">EKS AMI version (v20260818)</li>
<li class="">Root cause analysis (read-only ConfigMap + mkdir in CDI hook)</li>
</ul>
<p>NVIDIA engineers confirmed the regression. The fix landed in <strong><a href="https://github.com/NVIDIA/nvidia-container-toolkit/pull/2028" target="_blank" rel="noopener noreferrer" class="">PR #2028</a></strong> (by @henry118, merged 27 Aug 2026) — the hook now ignores read-only filesystem errors when creating the application profiles directory.</p>
<p><strong>Released in NVIDIA Container Toolkit 1.20.1.</strong></p>
<p>I also filed a tracking issue at <strong><a href="https://github.com/awslabs/amazon-eks-ami/issues/2790" target="_blank" rel="noopener noreferrer" class="">awslabs/amazon-eks-ami#2790</a></strong> for the EKS AMI update.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="eks-workaround-until-ami-update">EKS Workaround (Until AMI Update)<a href="https://tanmay.dev/hi/blog/nvidia-container-toolkit-regression-fix/#eks-workaround-until-ami-update" class="hash-link" aria-label="EKS Workaround (Until AMI Update) का सीधा लिंक" title="EKS Workaround (Until AMI Update) का सीधा लिंक" translate="no">​</a></h2>
<p>If you're on EKS and can't wait for the updated AMI, you have two options:</p>
<p><strong>Option 1: Pin to the previous EKS NVIDIA AMI</strong></p>
<div class="language-hcl codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-hcl codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain"># Terraform - pin to known-good AMI ID</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">resource "aws_eks_node_group" "gpu" {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  ami_type = "AL2_x86_64_GPU"  # or BOTTLEROCKET_x86_64_NVIDIA</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  # Use the AMI ID from before v20260818, not "latest"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  # Find it in EC2 &gt; AMIs &gt; Owned by Amazon &gt; amazon-eks-nvidia-*</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">}</span><br></div></code></pre></div></div>
<p><strong>Option 2: Mount the device-plugin config elsewhere (keep <code>/etc/nvidia</code> writable)</strong></p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic"># Device plugin daemonset patch</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">apiVersion</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> apps/v1</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">kind</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> DaemonSet</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">metadata</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> nvidia</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">device</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">plugin</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">daemonset</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">namespace</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> kube</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">system</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">spec</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">template</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">spec</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">containers</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> nvidia</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">device</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">plugin</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">ctr</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token comment" style="color:#999988;font-style:italic"># Change config mount path away from /etc/nvidia</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token key atrule" style="color:#00a4db">volumeMounts</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> config</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#00a4db">mountPath</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> /etc/nvidia</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">device</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">plugin  </span><span class="token comment" style="color:#999988;font-style:italic"># not /etc/nvidia</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">volumes</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> config</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token key atrule" style="color:#00a4db">configMap</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> nvidia</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">device</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">plugin</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">config</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-this-taught-me">What This Taught Me<a href="https://tanmay.dev/hi/blog/nvidia-container-toolkit-regression-fix/#what-this-taught-me" class="hash-link" aria-label="What This Taught Me का सीधा लिंक" title="What This Taught Me का सीधा लिंक" translate="no">​</a></h2>
<ol>
<li class="">
<p><strong>Pin critical AMIs</strong>. The EKS NVIDIA AMI "latest" moved under us. For GPU workloads, pin the AMI ID in Terraform/Karpenter.</p>
</li>
<li class="">
<p><strong>Test GPU registration in CI</strong>. Add a step that launches a test node, installs the device plugin, and asserts <code>nvidia.com/gpu</code> &gt; 0.</p>
</li>
<li class="">
<p><strong>Upstream first</strong>. The fix was a one-line change once reproduced. Reporting it saved everyone else running GPU workloads on EKS.</p>
</li>
<li class="">
<p><strong>CDI hooks run on <em>every</em> container create</strong>. Not just GPU pods — any pod with <code>NVIDIA_VISIBLE_DEVICES</code> triggers the hook. That's why even non-GPU workloads were affected.</p>
</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="links">Links<a href="https://tanmay.dev/hi/blog/nvidia-container-toolkit-regression-fix/#links" class="hash-link" aria-label="Links का सीधा लिंक" title="Links का सीधा लिंक" translate="no">​</a></h2>
<ul>
<li class=""><strong>Primary Issue (my report)</strong>: <a href="https://github.com/NVIDIA/nvidia-container-toolkit/issues/2026" target="_blank" rel="noopener noreferrer" class="">NVIDIA/nvidia-container-toolkit#2026</a></li>
<li class=""><strong>Fix PR</strong>: <a href="https://github.com/NVIDIA/nvidia-container-toolkit/pull/2028" target="_blank" rel="noopener noreferrer" class="">NVIDIA/nvidia-container-toolkit#2028</a> (by @henry118, merged 27 Aug 2026)</li>
<li class=""><strong>Release</strong>: <a href="https://github.com/NVIDIA/nvidia-container-toolkit/releases/tag/v1.20.1" target="_blank" rel="noopener noreferrer" class="">NVIDIA Container Toolkit 1.20.1</a></li>
<li class=""><strong>EKS AMI Tracking</strong>: <a href="https://github.com/awslabs/amazon-eks-ami/issues/2790" target="_blank" rel="noopener noreferrer" class="">awslabs/amazon-eks-ami#2790</a></li>
<li class=""><strong>EKS AMI Reference</strong>: <a href="https://github.com/awslabs/amazon-eks-ami" target="_blank" rel="noopener noreferrer" class="">AWS EKS NVIDIA AMIs</a></li>
</ul>
<hr>
<p><em>I found, reproduced, and reported this upstream; NVIDIA merged the fix in PR #2028, released in v1.20.1. If you run GPU workloads on EKS, upgrade to Toolkit 1.20.1+ or pin to a known-good AMI.</em></p>]]></content>
        <author>
            <name>Tanmay Mohan</name>
            <uri>https://github.com/tanmaymohan</uri>
        </author>
        <category label="GPU" term="GPU"/>
        <category label="Kubernetes" term="Kubernetes"/>
        <category label="Open Source" term="Open Source"/>
        <category label="Debugging" term="Debugging"/>
        <category label="AWS" term="AWS"/>
        <category label="EKS" term="EKS"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[What to Do After Your Cloud Credits Run Out]]></title>
        <id>https://tanmay.dev/hi/blog/credits-ran-out-first-savings-plan/</id>
        <link href="https://tanmay.dev/hi/blog/credits-ran-out-first-savings-plan/"/>
        <updated>2026-10-05T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Post-credits cloud cost strategy: split bill into baseline (commit) vs variable (on-demand). 5-step order: list steady workloads, move to Spot first, buy 1-year no-upfront Compute Savings Plan, add RIs for databases only, skip <65% utilization. Baseline invoice mostly committed, API spend variable with own budget.]]></summary>
        <content type="html"><![CDATA[<p>Credits hide the bill. The day they end, the same usage shows up as cash. The useful move is to split that bill into a baseline you can commit to, and a variable line you refuse to commit to, then buy the commitment only after the baseline is real.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="split-the-bill-before-you-buy-anything">Split the bill before you buy anything<a href="https://tanmay.dev/hi/blog/credits-ran-out-first-savings-plan/#split-the-bill-before-you-buy-anything" class="hash-link" aria-label="Split the bill before you buy anything का सीधा लिंक" title="Split the bill before you buy anything का सीधा लिंक" translate="no">​</a></h2>
<p>Draw two lines.</p>
<ul>
<li class=""><strong>Baseline.</strong> The compute and databases that are still there after you have rightsized and moved what you can onto Spot. This is the number a Savings Plan or a reserved instance is allowed to cover.</li>
<li class=""><strong>Variable.</strong> GenAI and other usage-based APIs. They swing with product traffic. A commitment here either strands money in a quiet month or runs out in a busy one. Leave them on demand and budget them separately.</li>
</ul>
<p>Rightsizing comes before the commitment. A provider coverage suggestion, often something like 65% of current on-demand spend, is calculated on the bill you have today, including waste you are about to delete. Commit to the floor you expect to still be running, not to today's average.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="order-of-operations">Order of operations<a href="https://tanmay.dev/hi/blog/credits-ran-out-first-savings-plan/#order-of-operations" class="hash-link" aria-label="Order of operations का सीधा लिंक" title="Order of operations का सीधा लिंक" translate="no">​</a></h2>
<ol>
<li class=""><strong>List what is steady.</strong> Karpenter node pools that change instance family every week are a Compute Savings Plan problem. Databases that sit on one engine and one size are a reserved-instance problem. Spot GPUs are neither. Do not cover them.</li>
<li class=""><strong>Move eligible workloads to Spot first.</strong> A Savings Plan is a promise to pay for on-demand compute. Shifting that compute to Spot later leaves the promise in place and the discount with nothing to apply to. Downsizing does not do that. Spot moves do.</li>
<li class=""><strong>Buy a 1-year, no-upfront Compute Savings Plan under that floor.</strong> No-upfront keeps the exit cheap if the floor was wrong. One year is long enough for the discount and short enough that a platform change does not outlive the contract.</li>
<li class=""><strong>Add reserved instances only for the databases.</strong> Do not stack a reserved instance and a Savings Plan on the same usage. One coverage instrument per resource.</li>
<li class=""><strong>Skip anything that is busy less than about 65% of the time.</strong> A box that is actually busy about a third of the hours costs more reserved, per hour you use it, than the same box on demand. For a GPU that only has to exist during a workday, hold capacity for that window instead of reserving the instance around the clock. That pattern is in <a class="" href="https://tanmay.dev/hi/blog/business-hours-gpu-capacity-reservations/">Booking g6 capacity before the workday</a>.</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-done-looks-like">What "done" looks like<a href="https://tanmay.dev/hi/blog/credits-ran-out-first-savings-plan/#what-done-looks-like" class="hash-link" aria-label="What &quot;done&quot; looks like का सीधा लिंक" title="What &quot;done&quot; looks like का सीधा लिंक" translate="no">​</a></h2>
<p>The baseline invoice is mostly commitment, at a utilization you can actually hit. The API line is still variable, and it has its own budget. If a month is quiet, the Savings Plan is still earning its discount on the floor. If a month is loud, the overage is on demand, which is cheaper than having bought a plan sized for the peak.</p>]]></content>
        <author>
            <name>Tanmay Mohan</name>
            <uri>https://github.com/tanmaymohan</uri>
        </author>
        <category label="Cloud" term="Cloud"/>
        <category label="Cost Optimization" term="Cost Optimization"/>
        <category label="AWS" term="AWS"/>
        <category label="Optimization" term="Optimization"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Booking g6 Capacity Before the Workday]]></title>
        <id>https://tanmay.dev/hi/blog/business-hours-gpu-capacity-reservations/</id>
        <link href="https://tanmay.dev/hi/blog/business-hours-gpu-capacity-reservations/"/>
        <updated>2026-10-04T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Using On-Demand Capacity Reservations (ODCR) to guarantee g6 GPU availability for business hours. EventBridge + Lambda creates reservation before workday, cancels after. Covers billing rules, open vs targeted matching, Savings Plan stacking, and why ODCR beats Reserved Instances for time-bounded workloads.]]></summary>
        <content type="html"><![CDATA[<p><code>g6</code> was not there when the session started. Not on Spot, and not as a normal On-Demand launch either. Karpenter would sit on <code>InsufficientInstanceCapacity</code> while a user was already waiting. I used an <strong>On-Demand Capacity Reservation</strong> to hold the instance before business hours, then release it when the day was over.</p>
<p>The point was the GPU existing at the right moment. The smaller bill was a side effect.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-was-actually-failing">What was actually failing<a href="https://tanmay.dev/hi/blog/business-hours-gpu-capacity-reservations/#what-was-actually-failing" class="hash-link" aria-label="What was actually failing का सीधा लिंक" title="What was actually failing का सीधा लिंक" translate="no">​</a></h2>
<p>The session-driven service needs one GPU while someone is in a session, and nothing overnight. Scale-to-zero handles the overnight part. It does not help if AWS has no <code>g6</code> to give you at 9 a.m.</p>
<p>A normal On-Demand run is still "launch it now and hope the AZ has one." On the days that mattered, it did not. An always-on Reserved Instance would have kept a box overnight that nobody was using. An ODCR is a hold on a specific instance type, in a specific AZ, for a window I choose.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-window">The window<a href="https://tanmay.dev/hi/blog/business-hours-gpu-capacity-reservations/#the-window" class="hash-link" aria-label="The window का सीधा लिंक" title="The window का सीधा लिंक" translate="no">​</a></h2>
<p>EventBridge fires a Lambda before the users' workday and creates the reservation. A second schedule cancels it afterwards. The window covers US Eastern through Pacific hours. Creating it early is the whole trick: the capacity is already matched before the first session, instead of racing a launch at the moment someone logs in.</p>
<!-- -->
<p>Compared shapes were g6.2xlarge, g5.2xlarge, and g6e.2xlarge. The reservation I needed was the <code>g6</code>, because that was the family that kept coming back empty.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="billing-once-the-capacity-is-the-reason">Billing, once the capacity is the reason<a href="https://tanmay.dev/hi/blog/business-hours-gpu-capacity-reservations/#billing-once-the-capacity-is-the-reason" class="hash-link" aria-label="Billing, once the capacity is the reason का सीधा लिंक" title="Billing, once the capacity is the reason का सीधा लिंक" translate="no">​</a></h2>
<p>A few rules I checked against AWS docs so the hold did not surprise the bill:</p>
<ul>
<li class="">A running instance inside an ODCR is <strong>not</strong> double-charged. The reservation line is the cost.</li>
<li class="">Billing starts when the reservation is created, whether or not a pod has landed on it. If the morning is quiet, you still pay for the hold. That is the price of knowing the card will be there.</li>
<li class="">The end time is approximate. Cancel on a schedule, and check that the reservation is actually gone.</li>
<li class=""><code>open</code> and <code>targeted</code> matching are not the same thing. The NodePool has to be able to land on the reservation you just created, or you pay for a hold that Karpenter never uses.</li>
<li class="">A regional Reserved Instance and a Savings Plan <strong>do</strong> apply on top of an ODCR. A <strong>zonal</strong> Reserved Instance does not, and stacking one double-charges idle capacity.</li>
</ul>
<p>The session path this protects is in <a class="" href="https://tanmay.dev/hi/blog/scale-to-zero-mlops-on-eks/">Scale-to-zero GPU inference on EKS</a>.</p>]]></content>
        <author>
            <name>Tanmay Mohan</name>
            <uri>https://github.com/tanmaymohan</uri>
        </author>
        <category label="GPU" term="GPU"/>
        <category label="AWS" term="AWS"/>
        <category label="Cost Optimization" term="Cost Optimization"/>
        <category label="Kubernetes" term="Kubernetes"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Why I Chose vmagent over Grafana Alloy for Edge Devices]]></title>
        <id>https://tanmay.dev/hi/blog/vmagent-over-alloy-for-edge-devices/</id>
        <link href="https://tanmay.dev/hi/blog/vmagent-over-alloy-for-edge-devices/"/>
        <updated>2026-10-04T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Why vmagent beat Grafana Alloy for edge device monitoring: 20 GB on-disk queue vs 8-hour WAL, per-device tokens via vmauth, path-locked ALB + WAF, macOS launchd, 3-step onboarding. VictoriaMetrics + Grafana read-only architecture.]]></summary>
        <content type="html"><![CDATA[<p>On-prem workstations were reporting health by cron scripts that posted into chat. The replacement ships metrics from macOS and Linux machines into VictoriaMetrics. Planning docs looked at Grafana Alloy over Tailscale. That is not what shipped.</p>
<!-- -->
<!-- -->
<p><code>node_exporter</code> and <strong>vmagent</strong> run on the device. An allow-list means only <code>node_*</code> and <code>up</code> leave the machine. Logs stay local. vmagent keeps a <strong>20 GB on-disk queue</strong>, so a site that goes dark for days still drains when the link returns. Alloy's write-ahead log covers roughly <strong>8 hours</strong>. That was the deciding gap.</p>
<p>Devices push HTTPS to a <strong>path-locked public ALB with WAF</strong>. The default response is 403. Only POST on the write path is allowed. <strong>vmauth</strong> checks a per-device token and forwards to VictoriaMetrics. Grafana reads with a different, read-only token. Uptime Kuma, in push mode, alerts when heartbeats stop, which is how you notice a powered-off machine that cannot scrape itself.</p>
<p>Naming is a <code>device_id</code> plus a token. macOS keeps the agent up with <code>launchd</code>. Onboarding is three steps: a short doc, a script that registers the token, and an onboard script for Linux or Mac. Status codes in the troubleshooting guide map to device, network, receiver, or Grafana, so a 403 is not treated as "the agent is down."</p>
<p>This is phase 3 of the rollout, not a finished fleet.</p>]]></content>
        <author>
            <name>Tanmay Mohan</name>
            <uri>https://github.com/tanmaymohan</uri>
        </author>
        <category label="Observability" term="Observability"/>
        <category label="AWS" term="AWS"/>
        <category label="Networking" term="Networking"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Serving a 31B 4-bit Model on Blackwell Spot GPUs]]></title>
        <id>https://tanmay.dev/hi/blog/serving-a-31b-4bit-model-on-blackwell-spot/</id>
        <link href="https://tanmay.dev/hi/blog/serving-a-31b-4bit-model-on-blackwell-spot/"/>
        <updated>2026-10-01T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Running a 31B NVFP4 model exclusively on Blackwell spot GPUs with no fallback. 29.5 GB image, 1800s ALB timeout, 3600s progress deadline, 15.5min startup probe for kernel JIT. Why NVFP4 requires Blackwell and why Pending is better than wrong-family scheduling.]]></summary>
        <content type="html"><![CDATA[<p>A roughly 31B-parameter multimodal model, quantized to <strong>NVFP4</strong>, runs only on Blackwell. The 4-bit kernels are not a format you can fall back onto an L40S or an L4. The service is spot-only, with <strong>no fallback on purpose</strong>. If there is no Blackwell capacity, the pod stays Pending and nothing is billed.</p>
<!-- -->
<p>Weights are baked into a <strong>29.5 GB</strong> image. The ALB idle timeout moved from 900 to <strong>1,800</strong> seconds. <code>progressDeadlineSeconds</code> on the Deployment is <strong>3,600</strong>. The startup probe allows <strong>15.5 minutes</strong> for kernel JIT on first start.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="probes">Probes<a href="https://tanmay.dev/hi/blog/serving-a-31b-4bit-model-on-blackwell-spot/#probes" class="hash-link" aria-label="Probes का सीधा लिंक" title="Probes का सीधा लिंक" translate="no">​</a></h2>
<p>The liveness probe (180 seconds) is deliberately slower than the readiness probe (60 seconds). Heavy compute must not look like a dead process. Without the long startup budget, the kubelet kills the pod mid-compile and it loops forever.</p>
<p>Forcing the FlashInfer attention backend failed. Let vLLM pick the backend.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-no-fallback">Why no fallback<a href="https://tanmay.dev/hi/blog/serving-a-31b-4bit-model-on-blackwell-spot/#why-no-fallback" class="hash-link" aria-label="Why no fallback का सीधा लिंक" title="Why no fallback का सीधा लिंक" translate="no">​</a></h2>
<p>A spot waterfall is the right default for the rest of the platform, written up in <a class="" href="https://tanmay.dev/hi/blog/scale-to-zero-mlops-on-eks/">Scale-to-zero GPU inference on EKS</a>. This model is the exception. An L40S cannot run NVFP4. Scheduling it there would Pending in a more confusing way, or serve an error after you paid for the node. Pending on the right family is cheaper.</p>
<p>Canary the AMI on one node and confirm it advertises a GPU before you raise the pool limit. A node that never advertises the device still bills.</p>]]></content>
        <author>
            <name>Tanmay Mohan</name>
            <uri>https://github.com/tanmaymohan</uri>
        </author>
        <category label="GPU" term="GPU"/>
        <category label="Kubernetes" term="Kubernetes"/>
        <category label="AI" term="AI"/>
        <category label="AWS" term="AWS"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Isolating GPU Pools with Value-Based Taints]]></title>
        <id>https://tanmay.dev/hi/blog/isolating-gpu-pools-with-value-based-taints/</id>
        <link href="https://tanmay.dev/hi/blog/isolating-gpu-pools-with-value-based-taints/"/>
        <updated>2026-09-29T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[How to isolate GPU workloads using value-based taints: keep taint key as nvidia.com/gpu, change only the value. NVIDIA device plugin tolerates by key (Exists), workloads tolerate by value (Equal). Prevents cross-contamination and capacity confusion.]]></summary>
        <content type="html"><![CDATA[<p>Each GPU workload in my current cluster has its own Karpenter NodePool. The isolation trick is small: keep the taint <strong>key</strong> as <code>nvidia.com/gpu</code>, and change only the <strong>value</strong>.</p>
<!-- -->
<p>The NVIDIA device plugin tolerates by key (<code>operator: Exists</code>), so it still runs on every pool and the nodes still advertise GPUs. Each workload tolerates only its own value (<code>operator: Equal</code>), so other GPU apps are repelled.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-failure-mode-if-you-invent-a-key">The failure mode if you invent a key<a href="https://tanmay.dev/hi/blog/isolating-gpu-pools-with-value-based-taints/#the-failure-mode-if-you-invent-a-key" class="hash-link" aria-label="The failure mode if you invent a key का सीधा लिंक" title="The failure mode if you invent a key का सीधा लिंक" translate="no">​</a></h2>
<p>A new taint key looks cleaner in a diagram. The device plugin does not tolerate it. It will not run on those nodes. The nodes advertise no GPUs. Pods sit Pending. It looks exactly like a capacity shortage, which is how you waste a day raising quotas.</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">taints</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">key</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> nvidia.com/gpu</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">value</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> workload</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">a</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">effect</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> NoSchedule</span><br></div></code></pre></div></div>
<p>The matching toleration on the workload uses <code>operator: Equal</code> and that same value. The device plugin's toleration stays <code>operator: Exists</code> on the key, with no value.</p>
<p>Karpenter still tries the <strong>higher</strong> NodePool weight first. Put the preferred family at the higher weight, not the lower one.</p>
<p>A sanitized reference pool — spot with on-demand fallback, a GPU limit, <code>WhenEmpty</code> after 15 minutes, a dated AMI alias, and a 150 GiB gp3 root volume — is the shape behind the Blackwell inference pool. Pin the AMI alias to a dated release. <code>latest</code> is how a toolkit bump reaches every GPU node at once.</p>]]></content>
        <author>
            <name>Tanmay Mohan</name>
            <uri>https://github.com/tanmaymohan</uri>
        </author>
        <category label="GPU" term="GPU"/>
        <category label="Kubernetes" term="Kubernetes"/>
        <category label="AWS" term="AWS"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Cutting GPU Cold Starts on EKS, with Numbers]]></title>
        <id>https://tanmay.dev/hi/blog/cutting-gpu-cold-starts-on-eks/</id>
        <link href="https://tanmay.dev/hi/blog/cutting-gpu-cold-starts-on-eks/"/>
        <updated>2026-09-26T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Benchmark results for GPU image pull optimization on EKS: NVMe, Bottlerocket, SOCI lazy loading, parallel pulls, FastImagePull, EBS throughput. Best config: 43s (54% faster) with AL2023 FastImagePull + 600 MiB/s EBS. SOCI parallel-pull hit 49s.]]></summary>
        <content type="html"><![CDATA[<p>Scale-to-zero is cheap until the first request waits on an image pull. I benchmarked NVMe instance storage, Bottlerocket, SOCI, AL2023 FastImagePull, and EBS throughput on a fresh node every time.</p>
<p>Setup: <strong>g4dn.xlarge</strong>, a <strong>3.6 GB</strong> image, <strong>one run per configuration</strong>. Treat the numbers as indicative, not a paper.</p>
<!-- -->
<table><thead><tr><th>Test</th><th>Configuration</th><th>Pull time</th><th>vs baseline</th></tr></thead><tbody><tr><td>t0</td><td>AL2023, gp3, 3 parallel downloads</td><td>94.3 s</td><td>—</td></tr><tr><td>t1</td><td>plus NVMe instance store</td><td>91.0 s</td><td>−4%</td></tr><tr><td>t2</td><td>plus 10 parallel downloads</td><td>82.6 s</td><td>−12%</td></tr><tr><td>t3a</td><td>Bottlerocket, EBS, 10 parallel downloads</td><td>76.7 s</td><td>−19%</td></tr><tr><td>t3b</td><td>Bottlerocket plus NVMe</td><td>77.4 s</td><td>−18%</td></tr><tr><td>t4</td><td>Bottlerocket plus SOCI lazy loading</td><td>92.8 s</td><td>−2%</td></tr><tr><td>t4b</td><td>Bottlerocket plus SOCI parallel-pull</td><td>60.8 s</td><td>−36%</td></tr><tr><td>t5</td><td>AL2023 FastImagePull, EBS at 600 MiB/s</td><td>43.0 s</td><td>−54%</td></tr><tr><td>t5b</td><td>Bottlerocket, SOCI parallel-pull, NVMe</td><td>48.5 s</td><td>−49%</td></tr></tbody></table>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-actually-moved">What actually moved<a href="https://tanmay.dev/hi/blog/cutting-gpu-cold-starts-on-eks/#what-actually-moved" class="hash-link" aria-label="What actually moved का सीधा लिंक" title="What actually moved का सीधा लिंक" translate="no">​</a></h2>
<p><strong>Lazy loading barely helps ML images.</strong> The process needs the model layers immediately, so "start while the rest pulls" does not hide the bytes. Parallel pulling and disk throughput do.</p>
<p>NVMe on its own was a 4% change. Bottlerocket without a pull-mode change was about 19%. The two configs under 50 seconds both pushed more bytes at once: SOCI in parallel-pull mode, or FastImagePull on a faster gp3 volume.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="two-caveats-before-you-copy-t5">Two caveats before you copy t5<a href="https://tanmay.dev/hi/blog/cutting-gpu-cold-starts-on-eks/#two-caveats-before-you-copy-t5" class="hash-link" aria-label="Two caveats before you copy t5 का सीधा लिंक" title="Two caveats before you copy t5 का सीधा लिंक" translate="no">​</a></h2>
<p>Test t5 ran on a <strong>4-vCPU</strong> instance. FastImagePull is reportedly ignored below 8 vCPUs. The EBS jump from 125 to <strong>600 MiB/s</strong> may deserve the credit. Re-test on a larger instance before you publish a claim about FastImagePull itself.</p>
<p>SOCI indexes have to be <strong>re-pushed for every image</strong> and version-matched to the node. Otherwise they are silently ignored, and you are back on a normal pull with extra CI work.</p>
<p>For a roughly 19-node general apps pool, <strong>300 MiB/s</strong> of EBS throughput was the cost-efficient point. GPU root volumes in the inference NodePool are a different story: 150 GiB gp3 at 4,000 IOPS and 600 MiB/s, because the image is tens of gigabytes, not 3.6.</p>
<p>A full inference cold start is still minutes once you add the driver and the model load. This table is only the pull. The platform around it is in <a class="" href="https://tanmay.dev/hi/blog/scale-to-zero-mlops-on-eks/">Scale-to-zero GPU inference on EKS</a>.</p>]]></content>
        <author>
            <name>Tanmay Mohan</name>
            <uri>https://github.com/tanmaymohan</uri>
        </author>
        <category label="GPU" term="GPU"/>
        <category label="Kubernetes" term="Kubernetes"/>
        <category label="AWS" term="AWS"/>
        <category label="Cost Optimization" term="Cost Optimization"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Auditing My Own Estimate: Langfuse from $3.4K a Month to $1.0K]]></title>
        <id>https://tanmay.dev/hi/blog/auditing-my-langfuse-estimate/</id>
        <link href="https://tanmay.dev/hi/blog/auditing-my-langfuse-estimate/"/>
        <updated>2026-09-24T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[How a $3.4K/month Langfuse EKS estimate was 3.5× too high. The lean tier at $1K/month cuts monitoring nodes, provisioned IOPS, and 90-day retention. Includes 4-tier cost table and risk analysis for single ClickHouse node.]]></summary>
        <content type="html"><![CDATA[<p>The first Langfuse design on EKS was full HA and about <strong>$3,400 a month</strong> at the load I expected. Scenarios ran from about $1.8K to $7.7K. I re-checked it before buying the hardware. The estimate was about <strong>3.5× too high</strong>. The tier that shipped is about <strong>$1,000 a month</strong>, roughly <strong>71% below</strong> the original number.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-the-34k-was-buying">What the $3.4K was buying<a href="https://tanmay.dev/hi/blog/auditing-my-langfuse-estimate/#what-the-34k-was-buying" class="hash-link" aria-label="What the $3.4K was buying का सीधा लिंक" title="What the $3.4K was buying का सीधा लिंक" translate="no">​</a></h2>
<ul>
<li class="">ClickHouse, 1 shard and 3 replicas, one per AZ, on r8g.2xlarge, each with a 1,500 GiB gp3 volume at 8,000 IOPS.</li>
<li class="">ClickHouse Keeper on 3 separate small nodes.</li>
<li class="">RDS PostgreSQL, ElastiCache Valkey, and S3 for events, media, exports, and backups.</li>
<li class="">Separate web and ingest deployments, because heavy ingest hurts the UI.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="where-the-money-actually-was">Where the money actually was<a href="https://tanmay.dev/hi/blog/auditing-my-langfuse-estimate/#where-the-money-actually-was" class="hash-link" aria-label="Where the money actually was का सीधा लिंक" title="Where the money actually was का सीधा लिंक" translate="no">​</a></h2>
<p>About <strong>$1,000 a month</strong> needed no redesign:</p>
<ul>
<li class="">Monitoring nodes that duplicated a stack that already existed.</li>
<li class="">Provisioned IOPS that the workload did not need.</li>
<li class="">90-day retention when 30 days was enough.</li>
</ul>
<p>About <strong>$1,400 a month</strong> was availability I was not ready to pay for yet: a single ClickHouse replica, no Keeper, and a deferred split between ingest and UI.</p>
<table><thead><tr><th>Tier</th><th>Monthly cost</th><th>What you get</th></tr></thead><tbody><tr><td>Pilot</td><td>~$0.55K</td><td>20% trace sampling</td></tr><tr><td>Lean (shipped)</td><td>~$1.0K</td><td>Single ClickHouse node, single-AZ Postgres, one queue node</td></tr><tr><td>AZ-resilient</td><td>~$1.6K</td><td>Survives the loss of an availability zone</td></tr><tr><td>Full HA</td><td>~$2.5K</td><td>Replicated ClickHouse with Keeper, split ingest and UI</td></tr></tbody></table>
<p>The single queue node looked like silent trace loss in the first design. Langfuse writes the event to S3 before it enqueues it, and the queue holds about 12 hours. A dead queue delays traces. It does not delete them. That is the risk the lean tier accepted.</p>
<p>The as-built runbook overrides the design doc wherever they differ. If you only read the ADR, you will build the $3.4K version.</p>
<p>How the cluster is wired, and the seven outages worth preventing, is in <a class="" href="https://tanmay.dev/hi/blog/self-hosting-langfuse-on-kubernetes-gotchas/">Self-hosting Langfuse on Kubernetes</a>.</p>]]></content>
        <author>
            <name>Tanmay Mohan</name>
            <uri>https://github.com/tanmaymohan</uri>
        </author>
        <category label="AI" term="AI"/>
        <category label="Observability" term="Observability"/>
        <category label="AWS" term="AWS"/>
        <category label="Cost Optimization" term="Cost Optimization"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Self-Hosting Langfuse on Kubernetes: 7 Gotchas]]></title>
        <id>https://tanmay.dev/hi/blog/self-hosting-langfuse-on-kubernetes-gotchas/</id>
        <link href="https://tanmay.dev/hi/blog/self-hosting-langfuse-on-kubernetes-gotchas/"/>
        <updated>2026-09-22T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[7 production gotchas self-hosting Langfuse v3 on EKS: ClickHouse single-node risk, ingestion queue delays, key migration from Docker Compose, resource limits, network policies, and upgrade pitfalls.]]></summary>
        <content type="html"><![CDATA[<p>The first Langfuse write-up on this site was an ECS sketch from earlier in the year. What I actually built, in my current role, is Langfuse v3 on EKS. It is validated on a lean tier. Apps are still cutting over from the old Docker Compose box.</p>
<p>These are the seven things that cause real outages, plus how the keys survive the move.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-shipped">What shipped<a href="https://tanmay.dev/hi/blog/self-hosting-langfuse-on-kubernetes-gotchas/#what-shipped" class="hash-link" aria-label="What shipped का सीधा लिंक" title="What shipped का सीधा लिंक" translate="no">​</a></h2>
<p>Lean tier, about <strong>$1,000 a month</strong>:</p>
<ul>
<li class="">Langfuse v3 from plain manifests. The Helm chart targets v4.</li>
<li class="">One ClickHouse node, Altinity operator. The official ClickHouse operator was still very early.</li>
<li class="">Single-AZ RDS PostgreSQL and one Valkey node.</li>
<li class="">AWS-managed encryption keys.</li>
<li class="">Ingestion over OpenTelemetry at <code>/api/public/otel</code>, from OpenAI, Bedrock, Vertex, and in-VPC models.</li>
</ul>
<!-- -->
<p>Langfuse writes each event to <strong>S3 before</strong> it queues it. Losing the single queue node mostly delays processing. The raw event stays recoverable, and the queue buffers about 12 hours. That is why a single queue node was acceptable. The original design called it silent trace loss. It is not.</p>
<p>The cost reassessment that got the estimate from about $3.4K to about $1.0K is <a class="" href="https://tanmay.dev/hi/blog/auditing-my-langfuse-estimate/">Auditing my own Langfuse estimate</a>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="seven-gotchas">Seven gotchas<a href="https://tanmay.dev/hi/blog/self-hosting-langfuse-on-kubernetes-gotchas/#seven-gotchas" class="hash-link" aria-label="Seven gotchas का सीधा लिंक" title="Seven gotchas का सीधा लिंक" translate="no">​</a></h2>
<ol>
<li class="">
<p><strong><code>NODE_OPTIONS=--max-old-space-size</code>.</strong> Node defaults to about a 1.7 GiB heap no matter what the container limit is. This is the most common self-hosted Langfuse crash. Set the heap from the limit you actually gave the pod.</p>
</li>
<li class="">
<p><strong>Postgres connections.</strong> Prisma's default pool is 9 per pod. At an HPA max around 33 pods that approaches <code>max_connections</code> (about 450). Set <code>connection_limit=5</code>.</p>
</li>
<li class="">
<p><strong>ClickHouse system log tables have no TTL</strong> and will fill the disk. Disable them in <code>config.d</code> on day one.</p>
</li>
<li class="">
<p><strong>The queue is not a cache.</strong> Set <code>maxmemory-policy=noeviction</code> and alert on any eviction.</p>
</li>
<li class="">
<p><strong>ElastiCache Serverless will not work</strong> for this. It has no parameter group, so you cannot set <code>noeviction</code>.</p>
</li>
<li class="">
<p><strong>Karpenter will consolidate stateful pods</strong> unless you stop it. ClickHouse and the queue do not belong on a pool with aggressive consolidation.</p>
</li>
<li class="">
<p><strong>Do not co-locate ClickHouse Keeper with ClickHouse</strong> if you later add replication. Keeper is sensitive to fsync latency. The lean tier skips Keeper entirely. The full design does not.</p>
</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="moving-off-docker-compose-without-new-api-keys">Moving off Docker Compose without new API keys<a href="https://tanmay.dev/hi/blog/self-hosting-langfuse-on-kubernetes-gotchas/#moving-off-docker-compose-without-new-api-keys" class="hash-link" aria-label="Moving off Docker Compose without new API keys का सीधा लिंक" title="Moving off Docker Compose without new API keys का सीधा लिंक" translate="no">​</a></h2>
<p>Different salt, encryption key, or auth secret invalidates every existing key. Copy those three values verbatim, and pin the <strong>same Langfuse version</strong>.</p>
<ul>
<li class="">Postgres: <code>pg_dump -Fc</code> and <code>pg_restore</code>.</li>
<li class="">ClickHouse history: native backup files through S3.</li>
<li class="">No dual-running. Stop the web tier, drain the worker, copy the delta, flip DNS.</li>
</ul>
<p>Four things that corrupt the move:</p>
<ul>
<li class=""><code>docker exec -t</code> rewrites bytes. Binary dumps come out wrong. Drop the <code>-t</code>.</li>
<li class="">The RDS admin user is not a superuser. Plan the restore grants around that.</li>
<li class="">URL-encode the database password in the connection string.</li>
<li class=""><code>NEXTAUTH_URL</code> has to match the new URL.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="gates-not-a-big-bang-cutover">Gates, not a big-bang cutover<a href="https://tanmay.dev/hi/blog/self-hosting-langfuse-on-kubernetes-gotchas/#gates-not-a-big-bang-cutover" class="hash-link" aria-label="Gates, not a big-bang cutover का सीधा लिंक" title="Gates, not a big-bang cutover का सीधा लिंक" translate="no">​</a></h2>
<p>Lean took about five weeks. Full HA would have been eight to ten. Each phase had a gate: <code>noeviction</code> and TLS, PVCs binding in-zone, a ClickHouse restore drill, then the first trace end to end. That first-trace gate is what turned an interim budget into a full one. Alerts, SSO, an egress allow-list, and a 3× peak load test come after.</p>
<p>The worker's liveness check is a <strong>stalled-queue check</strong>, not an HTTP ping. An HTTP 200 from a worker that has stopped draining the queue is a green dashboard over a dead pipeline.</p>]]></content>
        <author>
            <name>Tanmay Mohan</name>
            <uri>https://github.com/tanmaymohan</uri>
        </author>
        <category label="AI" term="AI"/>
        <category label="Observability" term="Observability"/>
        <category label="AWS" term="AWS"/>
        <category label="Kubernetes" term="Kubernetes"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[What GPU Time-Slicing Does Not Give You]]></title>
        <id>https://tanmay.dev/hi/blog/what-gpu-time-slicing-does-not-give-you/</id>
        <link href="https://tanmay.dev/hi/blog/what-gpu-time-slicing-does-not-give-you/"/>
        <updated>2026-09-20T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Why NVIDIA GPU time-slicing failed for embedding inference: added sharing overhead with zero throughput gain. Moved to exclusive L40S per pod. Covers NVIDIA device plugin, MIG comparison, and when time-slicing actually helps.]]></summary>
        <content type="html"><![CDATA[<p>Two embedding models, one physical GPU. That was the May design. By August each pod had its own L40S. Time-slicing had added sharing and <strong>no throughput</strong>.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="v1-two-slots-on-one-gpu">v1: two slots on one GPU<a href="https://tanmay.dev/hi/blog/what-gpu-time-slicing-does-not-give-you/#v1-two-slots-on-one-gpu" class="hash-link" aria-label="v1: two slots on one GPU का सीधा लिंक" title="v1: two slots on one GPU का सीधा लिंक" translate="no">​</a></h2>
<p>The NVIDIA device plugin time-sliced each physical GPU into two slots. A separate device-plugin DaemonSet, with a node selector, covered the spot GPU nodes. Fallback order was g7e, then g6e, then g6.</p>
<p>Scale-to-zero used a KEDA <code>HTTPScaledObject</code> and went to zero after 30 minutes idle. While a GPU was up, cost was about <strong>$0.40–1.20 per hour</strong>. Idle was $0. A cold start was about 5 minutes.</p>
<p>Pod anti-affinity had to come off, or the two models could never share a node. After enabling time-slicing, check the node's allocatable GPU count. If it still says 1, the slice config did not land.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="v2-one-exclusive-gpu-per-pod">v2: one exclusive GPU per pod<a href="https://tanmay.dev/hi/blog/what-gpu-time-slicing-does-not-give-you/#v2-one-exclusive-gpu-per-pod" class="hash-link" aria-label="v2: one exclusive GPU per pod का सीधा लिंक" title="v2: one exclusive GPU per pod का सीधा लिंक" translate="no">​</a></h2>
<p>Time-slicing context-switches one GPU between processes. It does not give you a second GPU. For these models, throughput did not move.</p>
<p>Each pod now gets its own <strong>L40S spot</strong> GPU and scales <strong>0 to 4 pods per model</strong> from an app-published demand count. That is the KEDA <code>metrics-api</code> scaler, with no interceptor. Cost is about <strong>$0.80 per hour per GPU</strong> while it is running.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-i-would-use-it-for">What I would use it for<a href="https://tanmay.dev/hi/blog/what-gpu-time-slicing-does-not-give-you/#what-i-would-use-it-for" class="hash-link" aria-label="What I would use it for का सीधा लिंक" title="What I would use it for का सीधा लिंक" translate="no">​</a></h2>
<p>Time-slicing fits many small, bursty models that are idle most of the time and can tolerate context switches. It does not fit a throughput target.</p>
<p>Two operational notes from the move:</p>
<ul>
<li class="">The <code>metrics-api</code> scaler's default 3-second poll timeout kept tripping fallback. Raising it to <strong>10 seconds</strong> stopped that.</li>
<li class="">With no interceptor, a request that arrives at zero replicas gets a <strong>503</strong>. The client retries, or you keep a minimum of 1 during busy hours.</li>
</ul>
<p><code>HTTPScaledObject</code> is deprecated. Plan on <code>InterceptorRoute</code> if you stay on the HTTP add-on.</p>
<p>The wider platform is in <a class="" href="https://tanmay.dev/hi/blog/scale-to-zero-mlops-on-eks/">Scale-to-zero GPU inference on EKS</a>.</p>]]></content>
        <author>
            <name>Tanmay Mohan</name>
            <uri>https://github.com/tanmaymohan</uri>
        </author>
        <category label="GPU" term="GPU"/>
        <category label="Kubernetes" term="Kubernetes"/>
        <category label="MLOps" term="MLOps"/>
        <category label="Cost Optimization" term="Cost Optimization"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[EKS पर Scale-to-Zero MLOps: GPU Idle Spend खत्म, बिना 5 मिनट के Cold Start के]]></title>
        <id>https://tanmay.dev/hi/blog/scale-to-zero-mlops-on-eks/</id>
        <link href="https://tanmay.dev/hi/blog/scale-to-zero-mlops-on-eks/"/>
        <updated>2026-08-25T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[रात भर idle पड़ा GPU node "available capacity" नहीं है। वह एक line item है। Intermittent inference के लिए g6e या g7e 24/7 चलाओ तो पहला token जाने से पहले ही लगभग $10k/माह जल सकता है। मैंने तीन महीने में AWS EKS पर एक event-driven MLOps platform बनाया जो GPU replicas को zero तक scale करता है, mission-critical sessions को Spot से दूर रखता है, और 15 GB+ LLM images पर sub-60-second cold starts का लक्ष्य रखता है।]]></summary>
        <content type="html"><![CDATA[<p>रात भर idle पड़ा GPU node "available capacity" नहीं है। वह एक line item है। Intermittent inference के लिए <code>g6e</code> या <code>g7e</code> 24/7 चलाओ तो पहला token जाने से पहले ही लगभग <strong>$10k/माह</strong> जल सकता है। मैंने तीन महीने में AWS EKS पर एक event-driven MLOps platform बनाया जो GPU replicas को zero तक scale करता है, mission-critical sessions को Spot से दूर रखता है, और 15 GB+ LLM images पर <strong>sub-60-second cold starts</strong> का लक्ष्य रखता है।</p>
<p>यह GPU path है: architecture, hardware constraints, और scars।</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="10000माह-का-idle-gpu-problem">$10,000/माह का Idle GPU Problem<a href="https://tanmay.dev/hi/blog/scale-to-zero-mlops-on-eks/#10000%E0%A4%AE%E0%A4%BE%E0%A4%B9-%E0%A4%95%E0%A4%BE-idle-gpu-problem" class="hash-link" aria-label="$10,000/माह का Idle GPU Problem का सीधा लिंक" title="$10,000/माह का Idle GPU Problem का सीधा लिंक" translate="no">​</a></h2>
<p>Inference traffic bursty है। Fine-tuning और eval jobs और ज़्यादा। Clinical tool पर sessions तब होते हैं जब कोई उसे इस्तेमाल कर रहा हो, फिर रुक जाते हैं। इनमें से कोई 3 बजे रात को गर्म GPU fleet justify नहीं करता।</p>
<p>जो बार-बार दिखता था:</p>
<ul>
<li class=""><strong>Static GPU Deployments</strong> <code>replicas: 1</code> (या "HA" के लिए <code>replicas: 2</code>) ऐसे nodes पर जो एक से ज़्यादा inference pod pack ही नहीं करते</li>
<li class=""><strong>असली SLA image pull है</strong>। Fresh node पर 15 GB Triton / vLLM image NAT और default <code>gp3</code> से 5–8 मिनट का event है</li>
<li class=""><strong>Spot ICE</strong> (<code>InsufficientInstanceCapacity</code>) एक GPU family पर — "सस्ता" NodePool pods को <code>Pending</code> में छोड़ देता है</li>
<li class=""><strong>KV cache silent OOM</strong>। 24 GB weight file 24 GB L4 (<code>g6.2xlarge</code>) पर पहले concurrent request के KV cache से पहले ही भर चुकी है</li>
</ul>
<p>लक्ष्य mechanical था: <strong><code>active_sessions == 0</code> पर कोई GPU burn नहीं</strong>, और Spot सूखने पर defined fallback।</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="architecture-blueprint-event-driven-scale-to-zero">Architecture Blueprint: Event-Driven Scale-to-Zero<a href="https://tanmay.dev/hi/blog/scale-to-zero-mlops-on-eks/#architecture-blueprint-event-driven-scale-to-zero" class="hash-link" aria-label="Architecture Blueprint: Event-Driven Scale-to-Zero का सीधा लिंक" title="Architecture Blueprint: Event-Driven Scale-to-Zero का सीधा लिंक" translate="no">​</a></h2>
<p>Control loop on-demand GPU inference और quantized Qwen 3.5 (NVFP4) server दोनों के लिए एक है:</p>
<ol>
<li class="">Client API hit करता है (या clinical tool से webhook)।</li>
<li class="">API <strong>active session metric</strong> लिखता है (Prometheus gauge, या DynamoDB/Redis counter जिसे Prometheus scrape करे)।</li>
<li class=""><strong>KEDA</strong> GPU Deployment को 0 से N scale करता है।</li>
<li class=""><strong>Karpenter</strong> Spot waterfall से GPU node लाता है, या जब SLA interruption मना करे तो On-Demand / ODCR से।</li>
<li class=""><strong>15 मिनट inactivity</strong> के बाद KEDA 0 पर लौटता है। Karpenter node consolidate करता है।</li>
</ol>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">Client / App ──HTTP / webhook──► API</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                                    │</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                                    │  active_sessions metric</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                                    ▼</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                                  KEDA</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                                    │</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                                    │  replicas &gt; 0</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                                    ▼</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                               Karpenter</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                    ┌───────────────┼───────────────┐</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                    │               │               │</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">              g7e Spot         g6e / g6 Spot     g5 Spot</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                    │               │               │</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                    └───────┬───────┴───────┬───────┘</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                            │  Spot drought │</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                            ▼               ▼</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                         ODCR /        On-Demand</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                       reserved pool   (clinical)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                            │</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                            ▼</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                     EKS GPU node</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                            │</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">              SOCI lazy-load via ECR + S3 VPC endpoints</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                            │</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                            ▼</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                  vLLM / SGLang  ──►  GPU VRAM (gp3)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                            │</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                   15 min idle ──►  KEDA scale to 0</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="gpu-inference-with-scale-to-zero">GPU inference with scale-to-zero<a href="https://tanmay.dev/hi/blog/scale-to-zero-mlops-on-eks/#gpu-inference-with-scale-to-zero" class="hash-link" aria-label="GPU inference with scale-to-zero का सीधा लिंक" title="GPU inference with scale-to-zero का सीधा लिंक" translate="no">​</a></h3>
<p>Trigger एक <strong>VPC-only URL</strong> है। API GPU pod को internet पर expose नहीं करता। वह <code>active_sessions</code> set करता है और replica count KEDA के पास रहता है।</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">apiVersion</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> keda.sh/v1alpha1</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">kind</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> ScaledObject</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">metadata</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> gpu</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">inference</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">spec</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">scaleTargetRef</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> gpu</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">inference</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">minReplicaCount</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">maxReplicaCount</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">4</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">cooldownPeriod</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">900</span><span class="token plain">   </span><span class="token comment" style="color:#999988;font-style:italic"># 15 minutes</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">triggers</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">type</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> prometheus</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">metadata</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token key atrule" style="color:#00a4db">serverAddress</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> http</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain">//prometheus.monitoring.svc</span><span class="token punctuation" style="color:#393A34">:</span><span class="token number" style="color:#36acaa">9090</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token key atrule" style="color:#00a4db">metricName</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> active_sessions</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token key atrule" style="color:#00a4db">query</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> sum(active_sessions</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">workload="gpu</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">inference"</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain">)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token key atrule" style="color:#00a4db">threshold</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"1"</span><br></div></code></pre></div></div>
<p>Spot families weighted fallbacks हैं: पहले <code>g7e</code> (VRAM bandwidth, कम TTFT), फिर <code>g6e</code> / <code>g6</code>, फिर <code>g5</code>। Capacity type Spot है, जब तक नहीं।</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="quantized-llm-server-qwen-35-nvfp4">Quantized LLM server (Qwen 3.5, NVFP4)<a href="https://tanmay.dev/hi/blog/scale-to-zero-mlops-on-eks/#quantized-llm-server-qwen-35-nvfp4" class="hash-link" aria-label="Quantized LLM server (Qwen 3.5, NVFP4) का सीधा लिंक" title="Quantized LLM server (Qwen 3.5, NVFP4) का सीधा लिंक" translate="no">​</a></h3>
<p>वही scaler। दूसरा engine। <strong>vLLM / SGLang</strong> Qwen 3.5 को <strong>NVFP4</strong> (4-bit floating point) में serve करता है। NVFP4 generation quality को naive INT8 cut से ज़्यादा higher-precision baseline के पास रखता है, और FP8/FP16 से लगभग आधा weight memory लेता है। वह headroom KV cache के लिए है, छोटे GPU पर checkpoint गिराने का कारण नहीं।</p>
<p>NVFP4 सिर्फ़ <strong>RTX PRO 6000</strong> या <strong>B200 / B300</strong> पर चला। वही GPUs NVFP4 kernels execute करते थे। Spot waterfall (<code>g7e</code>, <code>g6e</code>, <code>g6</code>, <code>g5</code>) दूसरे inference path के लिए है, इस server के लिए नहीं।</p>
<p>जो hardware-software co-design सच में मायने रखता था:</p>
<ul>
<li class=""><strong>SM occupancy</strong>: vLLM / SGLang CUDA kernels इस पर जीते-मरते हैं कि SM कितने warps in-flight रख सकता है। Block size और batch caps occupancy knobs हैं, सिर्फ़ <code>max_num_seqs</code> लोककथा नहीं।</li>
<li class=""><strong>KV cache VRAM</strong>: static weights फर्श हैं, छत नहीं। Instance को <code>weights + KV(concurrent seqs × context) + allocator fragmentation</code> से size करो, safetensor file size से नहीं।</li>
<li class=""><strong>NVFP4 GPUs</strong>: RTX PRO 6000, B200, या B300। कोई और family नहीं।</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="hybrid-edge-to-cloud-clinical-tool">Hybrid edge-to-cloud clinical tool<a href="https://tanmay.dev/hi/blog/scale-to-zero-mlops-on-eks/#hybrid-edge-to-cloud-clinical-tool" class="hash-link" aria-label="Hybrid edge-to-cloud clinical tool का सीधा लिंक" title="Hybrid edge-to-cloud clinical tool का सीधा लिंक" translate="no">​</a></h3>
<p>On-prem <strong>Mac Studio</strong> clinical tool चलाता है। Session शुरू होते ही webhook API को hit करता है, <code>active_sessions = 1</code>, और KEDA उस session के लिए EKS GPU node cold-start करता है।</p>
<p>यह pool <strong>सिर्फ़ On-Demand</strong> है। Session के बीच Spot reclaim cost optimization नहीं है। Failed session है। Idle-spend नियम वही रहता है: session खत्म, cooldown fire, replicas 0।</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="multi-region-gpu-test-rig">Multi-region GPU test rig<a href="https://tanmay.dev/hi/blog/scale-to-zero-mlops-on-eks/#multi-region-gpu-test-rig" class="hash-link" aria-label="Multi-region GPU test rig का सीधा लिंक" title="Multi-region GPU test rig का सीधा लिंक" translate="no">​</a></h3>
<p>Production से अलग: कई regions में EC2 GPU boxes (L4 / L40S / A10G) <strong>cost-arbitrage</strong> और SKU availability tests के लिए। Tag enforcement automated है ताकि FinOps allocation किसी के याद रखने पर depend न करे।</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="capacity-matrix">Capacity matrix<a href="https://tanmay.dev/hi/blog/scale-to-zero-mlops-on-eks/#capacity-matrix" class="hash-link" aria-label="Capacity matrix का सीधा लिंक" title="Capacity matrix का सीधा लिंक" translate="no">​</a></h3>
<table><thead><tr><th>Traffic shape</th><th>Scaler</th><th>Capacity</th></tr></thead><tbody><tr><td>Baseline business hours</td><td>KEDA <strong>Cron</strong> ~15 मिनट पहले pre-warm</td><td>GPU NodePool पहले से join कर रहा हो</td></tr><tr><td>Spiky GPU jobs</td><td>Prometheus <code>active_sessions</code> + Karpenter Spot</td><td>GPU Spot waterfall</td></tr><tr><td>Clinical tool / session-critical</td><td>Prometheus <code>active_sessions</code></td><td><strong>On-Demand</strong> GPU NodePool</td></tr></tbody></table>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="cold-start-boss-15-gb-images-60-सेकंड-से-कम-में">Cold-Start Boss: 15 GB Images 60 सेकंड से कम में<a href="https://tanmay.dev/hi/blog/scale-to-zero-mlops-on-eks/#cold-start-boss-15-gb-images-60-%E0%A4%B8%E0%A5%87%E0%A4%95%E0%A4%82%E0%A4%A1-%E0%A4%B8%E0%A5%87-%E0%A4%95%E0%A4%AE-%E0%A4%AE%E0%A5%87%E0%A4%82" class="hash-link" aria-label="Cold-Start Boss: 15 GB Images 60 सेकंड से कम में का सीधा लिंक" title="Cold-Start Boss: 15 GB Images 60 सेकंड से कम में का सीधा लिंक" translate="no">​</a></h2>
<p>Scale-to-zero तब तक मुफ़्त है जब तक पहला user <code>docker pull</code> के लिए आठ मिनट न बैठे। असली लड़ाई यही थी।</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="soci-lazy-loading">SOCI lazy loading<a href="https://tanmay.dev/hi/blog/scale-to-zero-mlops-on-eks/#soci-lazy-loading" class="hash-link" aria-label="SOCI lazy loading का सीधा लिंक" title="SOCI lazy loading का सीधा लिंक" translate="no">​</a></h3>
<p><strong>Seekable OCI (SOCI)</strong> ECR images index करता है ताकि runtime सिर्फ़ ज़रूरी files pull करते हुए container शुरू कर सके। Index generation की कीमत CI में है (मैं GitHub Actions में async चलाता हूँ); boot पूरे 15 GB disk पर उतरने का इंतज़ार नहीं करता।</p>
<p>SOCI के बिना cold node यह करता है:</p>
<ol>
<li class="">हर layer NAT से pull (throughput + cost)</li>
<li class="">Default gp3 पर unpack</li>
<li class="">फिर multi-GB weights VRAM में copy</li>
</ol>
<p>SOCI + private endpoints + disk throughput पर critical path करीब यह है: <strong>process start → needed pages fault-in → weights load</strong>।</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="ecr-और-s3-vpc-endpoints">ECR और S3 VPC endpoints<a href="https://tanmay.dev/hi/blog/scale-to-zero-mlops-on-eks/#ecr-%E0%A4%94%E0%A4%B0-s3-vpc-endpoints" class="hash-link" aria-label="ECR और S3 VPC endpoints का सीधा लिंक" title="ECR और S3 VPC endpoints का सीधा लिंक" translate="no">​</a></h3>
<p>Container pulls और weight objects <strong>ECR और S3 gateway/interface endpoints</strong> से जाते हैं। NAT bandwidth caps और public internet का extra hop हटता है। Cost control भी: GPU nodes का NAT से भारी image pull silent tax है।</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="vram-feeder-के-रूप-में-gp3">VRAM feeder के रूप में gp3<a href="https://tanmay.dev/hi/blog/scale-to-zero-mlops-on-eks/#vram-feeder-%E0%A4%95%E0%A5%87-%E0%A4%B0%E0%A5%82%E0%A4%AA-%E0%A4%AE%E0%A5%87%E0%A4%82-gp3" class="hash-link" aria-label="VRAM feeder के रूप में gp3 का सीधा लिंक" title="VRAM feeder के रूप में gp3 का सीधा लिंक" translate="no">​</a></h3>
<p>Weights अभी भी disk से GPU memory में जाने हैं। जहाँ node class allow करे, <strong>gp3</strong> पर elevated throughput (लगभग <strong>1000 MiB/s</strong>) ताकि safetensor load default 125 MiB/s से throttle न हो। Tune <strong>disk → CPU → PCIe → VRAM</strong> है, Kubernetes <code>startupProbe</code> seconds नहीं।</p>
<p>Physics गायब नहीं होती। जिस model को tens of gigabytes weights छूने हैं, वह 200 ms में cold-start नहीं होगा। लक्ष्य हमारे inference images के लिए <strong>एक मिनट से कम</strong> था — हर checkpoint के लिए जादू की संख्या नहीं।</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="karpenter-spot-waterfalls-और-spot-drought-defense">Karpenter Spot Waterfalls और Spot Drought Defense<a href="https://tanmay.dev/hi/blog/scale-to-zero-mlops-on-eks/#karpenter-spot-waterfalls-%E0%A4%94%E0%A4%B0-spot-drought-defense" class="hash-link" aria-label="Karpenter Spot Waterfalls और Spot Drought Defense का सीधा लिंक" title="Karpenter Spot Waterfalls और Spot Drought Defense का सीधा लिंक" translate="no">​</a></h2>
<p>Single-family GPU NodePool उसी हफ़्ते fail होगा जब GPUs चाहिए। Waterfall <strong>InsufficientInstanceCapacity</strong> पर <code>Pending</code> से बचाता है।</p>
<p><strong>Priority 1 — performant Spot:</strong> <code>g7e</code> (और जब वही best हो तो <code>g6e</code>)। सबसे ज़्यादा VRAM bandwidth, बेहतर TTFT।</p>
<p><strong>Priority 2 — degraded Spot:</strong> ऊपर सूखे तो <code>g6</code> / <code>g5</code>। Latency और batch size बदलते हैं। Product को यह स्वीकार करना होगा, वरना fallback झूठ है।</p>
<p><strong>Priority 3 — guaranteed:</strong> <strong>On-Demand Capacity Reservations (ODCR)</strong> या dedicated On-Demand pool ताकि pods unschedulable न बैठें।</p>
<p>Karpenter में यह multiple NodePools हैं (या wide instance-type set वाला एक pool + अलग ODCR pool) — कोई काल्पनिक <code>priority: 1</code> field नहीं। Rough shape:</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">apiVersion</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> karpenter.sh/v1</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">kind</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> NodePool</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">metadata</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> gpu</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">spot</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">waterfall</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">spec</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">template</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">spec</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">requirements</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">key</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> karpenter.sh/capacity</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">type</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#00a4db">operator</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> In</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#00a4db">values</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"spot"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">key</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> node.kubernetes.io/instance</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">type</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#00a4db">operator</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> In</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#00a4db">values</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> g7e.xlarge</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> g6e.xlarge</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> g6.2xlarge</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> g5.2xlarge</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">taints</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">key</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> nvidia.com/gpu</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#00a4db">value</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"true"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#00a4db">effect</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> NoSchedule</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">limits</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">nvidia.com/gpu</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">8</span><br></div></code></pre></div></div>
<p>दूसरा NodePool <code>capacity-type: on-demand</code> (और जहाँ reserved हो वहाँ ODCR) clinical tool और drought के लिए है। Latency flex कर सकने वाले inference pods Spot pool सहते हैं; session-critical सिर्फ़ On-Demand — taints, labels, PriorityClasses; hope नहीं।</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="trade-offs-scars">Trade-offs (scars)<a href="https://tanmay.dev/hi/blog/scale-to-zero-mlops-on-eks/#trade-offs-scars" class="hash-link" aria-label="Trade-offs (scars) का सीधा लिंक" title="Trade-offs (scars) का सीधा लिंक" translate="no">​</a></h2>
<table><thead><tr><th>Choice</th><th>Flex</th><th>Scar</th><th>Mitigation</th></tr></thead><tbody><tr><td>KEDA scale-to-zero</td><td>Zero GPU idle spend</td><td>पहला user cold-start भरता है</td><td>Cron pre-warm; SOCI; gp3 throughput</td></tr><tr><td>Spot waterfall</td><td>Cost + कुछ uptime</td><td>TTFT SKU के हिसाब से बदलता है</td><td>Workload के हिसाब से SLA; clinical tool On-Demand</td></tr><tr><td>SOCI lazy load</td><td>Sub-minute boots</td><td>CI में index build</td><td>GitHub Actions में async SOCI</td></tr></tbody></table>
<p>Weights pull करने वाले GPU pods <strong>IRSA</strong> इस्तेमाल करते हैं। उन containers में <code>AKIA*</code> keys नहीं। बाक़ी platform पर यही नियम non-GPU post में है।</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="summary-gpu-scale-to-zero-checklist">Summary: GPU Scale-to-Zero Checklist<a href="https://tanmay.dev/hi/blog/scale-to-zero-mlops-on-eks/#summary-gpu-scale-to-zero-checklist" class="hash-link" aria-label="Summary: GPU Scale-to-Zero Checklist का सीधा लिंक" title="Summary: GPU Scale-to-Zero Checklist का सीधा लिंक" translate="no">​</a></h2>
<p>GPU side ख़ुद जोड़ रहे हो तो:</p>
<ol>
<li class=""><strong>GPU Deployments 0 से scale करो।</strong> Idle process के CPU पर नहीं — <code>active_sessions</code> पर KEDA।</li>
<li class=""><strong>SLAs बाँटो।</strong> Interruptible inference के लिए Spot waterfall। Reclaim न सह सकने वाले sessions के लिए On-Demand / ODCR।</li>
<li class=""><strong>Scale-to-zero का दावा pull path ठीक करने के बाद।</strong> SOCI indexes, ECR/S3 VPC endpoints, gp3 throughput। वरना "zero idle" पहला request आठ मिनट का है।</li>
<li class=""><strong>Checkpoint file नहीं, KV cache के हिसाब से size करो।</strong> Concurrency आने के बाद L4 24 GB model GPU नहीं है।</li>
<li class=""><strong>NVFP4 capacity strategy सिर्फ़ RTX PRO 6000 या B200 / B300 पर है।</strong> KV headroom इसी से मिलता है। बाक़ी Spot waterfall पर यह format नहीं गिरता।</li>
<li class=""><strong>GPU pods पर IRSA।</strong> Weight pull करने वाले containers में static keys — credentials तुम्हारे सबसे महँगे nodes में पहुँचते हैं।</li>
<li class=""><strong>Calendar जब load balancer हो तो pre-warm करो।</strong> Business hours से 15 मिनट पहले Cron scaler humans को cold start से बेहतर है।</li>
</ol>
<p>GPU तरफ NVIDIA <strong>Video Search and Summarization</strong> blueprints Brev Launchables और NemoClaw agent skills से <code>g6e</code> / <code>g7e</code> पर: synthetic FPS footage पर high frame-rate extraction (HUD noise काटने के लिए edge-crop), और घने dashcam video पर lower-rate, motion-filtered indexing। Production वाली ही सीख — idle GPUs optional हैं; cold-start engineering नहीं।</p>
<p>Provider-level GPU notes: <a class="" href="https://tanmay.dev/hi/docs/running-gpus-in-2026/">Running GPUs in 2026</a>। Unmanaged GPU VMs को EKS में लाने वाला पिछला लेख: <a class="" href="https://tanmay.dev/hi/blog/migrating-gpu-workloads-across-clouds/">Migrating GPU workloads across clouds</a>।</p>]]></content>
        <author>
            <name>Tanmay Mohan</name>
            <uri>https://github.com/tanmaymohan</uri>
        </author>
        <category label="MLOps" term="MLOps"/>
        <category label="GPU" term="GPU"/>
        <category label="Kubernetes" term="Kubernetes"/>
        <category label="AWS" term="AWS"/>
        <category label="Cost Optimization" term="Cost Optimization"/>
        <category label="AI" term="AI"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[एक महीने में EKS Stack कैसे Harden किया]]></title>
        <id>https://tanmay.dev/hi/blog/hardening-eks-stack-6-months/</id>
        <link href="https://tanmay.dev/hi/blog/hardening-eks-stack-6-months/"/>
        <updated>2026-03-20T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[जब मैंने शुरू किया, EKS setup में basics थे — workloads चल रहे थे, traffic flow हो रहा था, deployments काम कर रहे थे। लेकिन security की नज़र से, काफ़ी surface area था: apps में baked static AWS access keys, default Amazon Linux nodes, कोई container image scanning नहीं, और publicly accessible Kubernetes API। पूरे stack को मैंने एक महीने में harden किया, एक हफ़्ता एक बार। यह रही पूरी journey।]]></summary>
        <content type="html"><![CDATA[<p>जब मैंने शुरू किया, EKS setup में basics थे — workloads चल रहे थे, traffic flow हो रहा था, deployments काम कर रहे थे। लेकिन security की नज़र से, काफ़ी surface area था: apps में baked static AWS access keys, default Amazon Linux nodes, कोई container image scanning नहीं, और publicly accessible Kubernetes API। पूरे stack को मैंने <strong>एक महीने</strong> में harden किया, एक हफ़्ता एक बार। यह रही पूरी journey।</p>
<!-- -->
<table><thead><tr><th>हफ़्ता</th><th>काम</th></tr></thead><tbody><tr><td>1</td><td>Host छोटा करो और API छुपाओ। Bottlerocket, फिर private EKS endpoint।</td></tr><tr><td>2</td><td>Bugs दिखें। हर PR पर SonarCloud, हर image पर Trivy।</td></tr><tr><td>3</td><td>Perimeter बंद करो। WAF, branch protection, hardened Dockerfiles।</td></tr><tr><td>4</td><td>Static keys खत्म, cluster upgrade, और external test।</td></tr></tbody></table>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="शुरुआती-हालत">शुरुआती हालत<a href="https://tanmay.dev/hi/blog/hardening-eks-stack-6-months/#%E0%A4%B6%E0%A5%81%E0%A4%B0%E0%A5%81%E0%A4%86%E0%A4%A4%E0%A5%80-%E0%A4%B9%E0%A4%BE%E0%A4%B2%E0%A4%A4" class="hash-link" aria-label="शुरुआती हालत का सीधा लिंक" title="शुरुआती हालत का सीधा लिंक" translate="no">​</a></h2>
<p>एक quick audit से स्थिति पता चली:</p>
<ul>
<li class=""><strong>Static access keys</strong>: Applications S3, DynamoDB, SQS, और अन्य AWS services से बात करने के लिए long-lived AWS access keys (<code>AKIA*</code> credentials) इस्तेमाल कर रही थीं। ये keys environment variables में stored थीं और manually rotate होती थीं (अगर होती थीं)।</li>
<li class=""><strong>Default node OS</strong>: EKS nodes default Amazon Linux 2 AMI पर चल रहे थे। Full Linux distribution — SSH access, package managers, और wide attack surface।</li>
<li class=""><strong>कोई container scanning नहीं</strong>: Docker images बिना vulnerability scanning के build और deploy होती थीं। किसी को नहीं पता था running images में कितने CVEs हैं।</li>
<li class=""><strong>Public EKS API</strong>: Kubernetes API server internet से accessible था, सिर्फ़ IAM authentication से protected।</li>
<li class=""><strong>कोई SAST नहीं</strong>: Code humans review करते थे, लेकिन CI pipeline में कोई static analysis security testing नहीं था।</li>
<li class=""><strong>कुछ जगह basic auth</strong>: कुछ internal APIs hardcoded basic authentication tokens इस्तेमाल कर रहे थे।</li>
</ul>
<p>Catastrophic नहीं, लेकिन जहाँ होना चाहिए वहाँ नहीं — ख़ासकर regulated environment में जहाँ formal security audits आने वाले थे।</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="हफ़्ता-1-host-और-api">हफ़्ता 1: Host और API<a href="https://tanmay.dev/hi/blog/hardening-eks-stack-6-months/#%E0%A4%B9%E0%A4%AB%E0%A4%BC%E0%A5%8D%E0%A4%A4%E0%A4%BE-1-host-%E0%A4%94%E0%A4%B0-api" class="hash-link" aria-label="हफ़्ता 1: Host और API का सीधा लिंक" title="हफ़्ता 1: Host और API का सीधा लिंक" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="bottlerocket-os">Bottlerocket OS<a href="https://tanmay.dev/hi/blog/hardening-eks-stack-6-months/#bottlerocket-os" class="hash-link" aria-label="Bottlerocket OS का सीधा लिंक" title="Bottlerocket OS का सीधा लिंक" translate="no">​</a></h3>
<p>पहला बदलाव सभी EKS nodes को Amazon Linux से <strong>Bottlerocket</strong> — Amazon का purpose-built container OS — पर switch करना था।</p>
<p>Bottlerocket क्यों matters:</p>
<ul>
<li class=""><strong>Immutable filesystem</strong>: Root filesystem read-only है। Packages install नहीं कर सकते, system files modify नहीं कर सकते, कुछ भी ऐसा नहीं कर सकते जो compromised container host पर करने की कोशिश करे।</li>
<li class=""><strong>कोई SSH नहीं</strong>: कोई SSH daemon नहीं। Traditional sense में Bottlerocket node में shell नहीं कर सकते। Admin access Bottlerocket API या SSM से है, दोनों auditable हैं।</li>
<li class=""><strong>कोई package manager नहीं</strong>: कोई <code>yum</code>, कोई <code>apt</code>, कोई <code>dnf</code> नहीं। इससे supply chain attacks की पूरी एक class eliminate हो जाती है।</li>
<li class=""><strong>CIS Level 1 compliant out of the box</strong>: कोई manual hardening ज़रूरी नहीं।</li>
</ul>
<p>मैंने पहले production में rollout किया (जहाँ सबसे ज़्यादा matter करता था), फिर staging और UAT में extend किया। Migration straightforward था — node group configuration में AMI type update किया, nodes roll किए, और verify किया कि workloads healthy वापस आ रहे हैं।</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="sealed-eks-api">Sealed EKS API<a href="https://tanmay.dev/hi/blog/hardening-eks-stack-6-months/#sealed-eks-api" class="hash-link" aria-label="Sealed EKS API का सीधा लिंक" title="Sealed EKS API का सीधा लिंक" translate="no">​</a></h3>
<p>EKS API endpoint पर public access disable किया और private-only access enable किया। इसका मतलब <code>kubectl</code> commands सिर्फ़ VPC के अंदर से काम करते हैं — practice में VPN से।</p>
<p>यह एक बदलाव पूरा attack vector eliminate करता है। अगर किसी के पास valid Kubernetes credentials भी हैं, API server internet से reach नहीं कर सकते।</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="हफ़्ता-2-bugs-दिखें">हफ़्ता 2: Bugs दिखें<a href="https://tanmay.dev/hi/blog/hardening-eks-stack-6-months/#%E0%A4%B9%E0%A4%AB%E0%A4%BC%E0%A5%8D%E0%A4%A4%E0%A4%BE-2-bugs-%E0%A4%A6%E0%A4%BF%E0%A4%96%E0%A5%87%E0%A4%82" class="hash-link" aria-label="हफ़्ता 2: Bugs दिखें का सीधा लिंक" title="हफ़्ता 2: Bugs दिखें का सीधा लिंक" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="ci-में-sonarcloud">CI में SonarCloud<a href="https://tanmay.dev/hi/blog/hardening-eks-stack-6-months/#ci-%E0%A4%AE%E0%A5%87%E0%A4%82-sonarcloud" class="hash-link" aria-label="CI में SonarCloud का सीधा लिंक" title="CI में SonarCloud का सीधा लिंक" translate="no">​</a></h3>
<p><strong>SonarCloud</strong> को GitHub में directly integrate किया:</p>
<ul>
<li class="">Main branches पर हर pull request SonarCloud scan trigger करता है</li>
<li class=""><strong>Quality Gates enforce हैं</strong>: अगर scan में security vulnerabilities, threshold से ऊपर code smells, या duplications मिलें, PR merge नहीं हो सकता</li>
<li class="">Python, Java, JavaScript, TypeScript, Terraform, और YAML पर coverage</li>
<li class="">SonarCloud bot हर PR पर findings comment करता है</li>
</ul>
<p>यह बहुत सारा low-hanging fruit पकड़ता है — hardcoded secrets, SQL injection patterns, XSS vulnerabilities, insecure random number usage — इससे पहले कि code production तक पहुँचे।</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="trivy-container-scanning">Trivy Container Scanning<a href="https://tanmay.dev/hi/blog/hardening-eks-stack-6-months/#trivy-container-scanning" class="hash-link" aria-label="Trivy Container Scanning का सीधा लिंक" title="Trivy Container Scanning का सीधा लिंक" translate="no">​</a></h3>
<p>SonarCloud pass होने के बाद, CI pipeline Docker image build करती है। Deploy होने से पहले, <strong>Trivy</strong> scan करता है:</p>
<ul>
<li class="">OS packages में known CVEs</li>
<li class="">Application dependencies में known CVEs</li>
<li class="">Misconfigured Dockerfile patterns</li>
</ul>
<p>Scan results CI console में log होते हैं। उस हफ़्ते log-only mode रखा, critical CVEs के लिए भी, ताकि baseline दिखे और किसी का deploy न रुके। Critical findings पर block तब किया जब backlog इतना छोटा था कि deploys freeze न हों।</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="हफ़्ता-3-perimeter-बंद">हफ़्ता 3: Perimeter बंद<a href="https://tanmay.dev/hi/blog/hardening-eks-stack-6-months/#%E0%A4%B9%E0%A4%AB%E0%A4%BC%E0%A5%8D%E0%A4%A4%E0%A4%BE-3-perimeter-%E0%A4%AC%E0%A4%82%E0%A4%A6" class="hash-link" aria-label="हफ़्ता 3: Perimeter बंद का सीधा लिंक" title="हफ़्ता 3: Perimeter बंद का सीधा लिंक" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="load-balancers-पर-waf">Load Balancers पर WAF<a href="https://tanmay.dev/hi/blog/hardening-eks-stack-6-months/#load-balancers-%E0%A4%AA%E0%A4%B0-waf" class="hash-link" aria-label="Load Balancers पर WAF का सीधा लिंक" title="Load Balancers पर WAF का सीधा लिंक" translate="no">​</a></h3>
<p>सभी applications proper AWS Application Load Balancers के पीछे होने के बाद (internal backend services के लिए, external public-facing APIs के लिए), मैंने external-facing ALBs पर <strong>AWS WAF</strong> enable किया:</p>
<ul>
<li class="">Common attack patterns (SQLi, XSS, known bad inputs) के लिए AWS managed rule sets</li>
<li class="">Rate-based rules पहले <strong>COUNT mode</strong> में — traffic patterns समझने के लिए बिना accidentally legitimate requests block किए</li>
<li class="">कुछ दिन counts, फिर हफ़्ता 4 के external test से पहले tuned thresholds के साथ BLOCK mode</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="branch-protection">Branch Protection<a href="https://tanmay.dev/hi/blog/hardening-eks-stack-6-months/#branch-protection" class="hash-link" aria-label="Branch Protection का सीधा लिंक" title="Branch Protection का सीधा लिंक" translate="no">​</a></h3>
<p>Code side पर, stricter branch protection rules enforce किए:</p>
<ul>
<li class="">Main branches में merge करने से पहले minimum 2 PR approvals ज़रूरी</li>
<li class="">सिर्फ़ designated lead developers production branches में merge कर सकते हैं</li>
<li class="">सभी status checks (SonarCloud, CI build, Trivy scan) pass होने चाहिए</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="dockerfile-hardening">Dockerfile Hardening<a href="https://tanmay.dev/hi/blog/hardening-eks-stack-6-months/#dockerfile-hardening" class="hash-link" aria-label="Dockerfile Hardening का सीधा लिंक" title="Dockerfile Hardening का सीधा लिंक" translate="no">​</a></h3>
<p>सभी services के Dockerfiles audit किए और hardening approach standardize किया:</p>
<ul>
<li class="">Specific base image tags (कोई <code>latest</code> नहीं)</li>
<li class="">Security patches pick up करने के लिए build में <code>apt-get update &amp;&amp; apt-get upgrade</code></li>
<li class="">जहाँ possible हो non-root user से run करो</li>
<li class="">Final image से unnecessary packages और tools remove करो</li>
</ul>
<p>Distroless images और Docker Hardened Images (dhi.io से) भी evaluate किए, लेकिन हमारे ज़्यादातर stack के लिए, <code>apt upgrade</code> approach ने security improvement और developer friction का सबसे अच्छा balance दिया।</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="हफ़्ता-4-keys-upgrade-और-external-test">हफ़्ता 4: Keys, Upgrade, और External Test<a href="https://tanmay.dev/hi/blog/hardening-eks-stack-6-months/#%E0%A4%B9%E0%A4%AB%E0%A4%BC%E0%A5%8D%E0%A4%A4%E0%A4%BE-4-keys-upgrade-%E0%A4%94%E0%A4%B0-external-test" class="hash-link" aria-label="हफ़्ता 4: Keys, Upgrade, और External Test का सीधा लिंक" title="हफ़्ता 4: Keys, Upgrade, और External Test का सीधा लिंक" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="access-keys-से-irsa">Access Keys से IRSA<a href="https://tanmay.dev/hi/blog/hardening-eks-stack-6-months/#access-keys-%E0%A4%B8%E0%A5%87-irsa" class="hash-link" aria-label="Access Keys से IRSA का सीधा लिंक" title="Access Keys से IRSA का सीधा लिंक" translate="no">​</a></h3>
<p>यह सबसे impactful बदलाव था, और मैंने इसे आख़िरी हफ़्ते के लिए रखा ताकि nodes, API, और scanners पहले से अपनी जगह पर हों।</p>
<p><strong>Problem</strong>: हर application जो AWS services (S3, DynamoDB, SQS, Kinesis, etc.) से बात करती थी, उसका अपना static access keys का set था। ये keys:</p>
<ul>
<li class="">Long-lived थीं (कुछ कभी rotate नहीं हुई थीं)</li>
<li class="">Overly broad permissions थीं (अक्सर <code>s3:*</code> या worse)</li>
<li class="">Environment variables में stored थीं, कभी-कभी code में commit</li>
</ul>
<p><strong>Solution</strong>: <strong>IAM Roles for Service Accounts (IRSA)</strong> — हर Kubernetes ServiceAccount एक specific IAM role से map होता है जिसमें scoped-down permissions।</p>
<p>हर application के लिए migration ऐसा दिखता था:</p>
<ol>
<li class="">Trust policy के साथ IAM role create करो जो EKS OIDC provider को assume करने दे</li>
<li class="">Role की permissions को exactly वही scope करो जो application को चाहिए (जैसे specific S3 buckets read/write, specific DynamoDB tables)</li>
<li class="">IAM role ARN से annotated Kubernetes ServiceAccount create करो</li>
<li class="">Application की deployment update करो नए ServiceAccount इस्तेमाल करने के लिए</li>
<li class="">Verify करो कि application new role के साथ काम कर रही है</li>
<li class="">पुरानी access keys revoke करो</li>
</ol>
<p>Python applications से शुरू किया (simpler AWS SDK configuration) और फिर Java apps पर गया। Cutover चलते हुए, पुरानी access keys पर VPC-restricted policies add किए — ताकि अगर leak हों भी, सिर्फ़ VPC के अंदर से इस्तेमाल हो सकें।</p>
<p>Applications migrate होने के बाद, पुरानी bot access keys revoke कीं और <strong>Pod Identity Agent</strong> को IRSA annotations के long-term replacement के रूप में deploy किया।</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="cluster-upgrade">Cluster Upgrade<a href="https://tanmay.dev/hi/blog/hardening-eks-stack-6-months/#cluster-upgrade" class="hash-link" aria-label="Cluster Upgrade का सीधा लिंक" title="Cluster Upgrade का सीधा लिंक" translate="no">​</a></h3>
<p>सभी environments में EKS v1.30 से v1.31 upgrade किया। Process:</p>
<ol>
<li class=""><strong>Staging पहले</strong>: Cluster version upgrade करो, फिर addons upgrade करो (CoreDNS, kube-proxy, VPC CNI — हर एक की cluster version के साथ compatibility matrix है), फिर नए AMIs के साथ nodes roll करो।</li>
<li class=""><strong>UAT अगला</strong>: Same process, integration tests से verify करो।</li>
<li class=""><strong>Production last</strong>: Same process, maintenance window और rollback plan के साथ।</li>
</ol>
<p>सबसे tricky part <strong>addon version matrix</strong> है। हर EKS version के लिए हर addon के specific compatible versions हैं। मैंने Terraform में addon versions explicitly pin किए surprises से बचने के लिए।</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="final-cleanup">Final Cleanup<a href="https://tanmay.dev/hi/blog/hardening-eks-stack-6-months/#final-cleanup" class="hash-link" aria-label="Final Cleanup का सीधा लिंक" title="Final Cleanup का सीधा लिंक" translate="no">​</a></h3>
<ul>
<li class="">Trivy findings के लिए कई services की Docker images patch कीं</li>
<li class="">SonarCloud ने जो hardcoded credentials flag किए, उन्हें remove किया</li>
<li class="">Frontend applications पर security headers update किए (HSTS, CSP, XSS protection)</li>
<li class="">External <strong>VAPT</strong> (Vulnerability Assessment and Penetration Testing) complete की — starting point से security posture काफ़ी improve हो गया था</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="नतीजा">नतीजा<a href="https://tanmay.dev/hi/blog/hardening-eks-stack-6-months/#%E0%A4%A8%E0%A4%A4%E0%A5%80%E0%A4%9C%E0%A4%BE" class="hash-link" aria-label="नतीजा का सीधा लिंक" title="नतीजा का सीधा लिंक" translate="no">​</a></h2>
<table><thead><tr><th>Area</th><th>पहले</th><th>बाद में</th></tr></thead><tbody><tr><td>Node OS</td><td>Amazon Linux 2</td><td>Bottlerocket (CIS L1)</td></tr><tr><td>Container scanning</td><td>कोई नहीं</td><td>हर CI build में Trivy</td></tr><tr><td>SAST</td><td>कोई नहीं</td><td>हर PR पर SonarCloud</td></tr><tr><td>Access keys</td><td>Static, long-lived</td><td>IRSA / Pod Identity</td></tr><tr><td>EKS API</td><td>Public</td><td>Private (VPN-only)</td></tr><tr><td>WAF</td><td>कोई नहीं</td><td>सभी external ALBs पर AWS WAF</td></tr><tr><td>Branch protection</td><td>Optional reviews</td><td>Enforced 2+ reviews + सभी checks</td></tr><tr><td>External assessment</td><td>कमज़ोर</td><td>काफ़ी improved</td></tr></tbody></table>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="अगर-कोई-यह-शुरू-कर-रहा-हो-तो-मेरी-सलाह">अगर कोई यह शुरू कर रहा हो तो मेरी सलाह<a href="https://tanmay.dev/hi/blog/hardening-eks-stack-6-months/#%E0%A4%85%E0%A4%97%E0%A4%B0-%E0%A4%95%E0%A5%8B%E0%A4%88-%E0%A4%AF%E0%A4%B9-%E0%A4%B6%E0%A5%81%E0%A4%B0%E0%A5%82-%E0%A4%95%E0%A4%B0-%E0%A4%B0%E0%A4%B9%E0%A4%BE-%E0%A4%B9%E0%A5%8B-%E0%A4%A4%E0%A5%8B-%E0%A4%AE%E0%A5%87%E0%A4%B0%E0%A5%80-%E0%A4%B8%E0%A4%B2%E0%A4%BE%E0%A4%B9" class="hash-link" aria-label="अगर कोई यह शुरू कर रहा हो तो मेरी सलाह का सीधा लिंक" title="अगर कोई यह शुरू कर रहा हो तो मेरी सलाह का सीधा लिंक" translate="no">​</a></h2>
<ol>
<li class=""><strong>हफ़्ता 1 node OS और API है।</strong> Bottlerocket और private endpoint application code छूने से पहले दो attack surfaces हटा देते हैं।</li>
<li class=""><strong>हफ़्ता 2 visibility है।</strong> SonarCloud और Trivy पहले log-only mode में चलाओ। Baseline समझो इससे पहले कि deployments block करना शुरू करो।</li>
<li class=""><strong>हफ़्ता 4 IRSA है।</strong> EKS पर सबसे बड़ा improvement। आख़िर में करो, एक service एक बार, और हर revoke तक पुरानी keys VPC तक सीमित रखो।</li>
<li class=""><strong>Security project नहीं, habit है।</strong> उस महीने के बाद भी, मैं weekly findings review करता हूँ और ज़रूरत अनुसार patch करता हूँ। Goal continuous improvement है, one-time checkbox नहीं।</li>
</ol>
<p>इन hardening techniques की step-by-step guides के लिए, docs में <a class="" href="https://tanmay.dev/hi/docs/eks-security-hardening/">EKS Security Hardening Guide</a> देखें।</p>]]></content>
        <author>
            <name>Tanmay Mohan</name>
            <uri>https://github.com/tanmaymohan</uri>
        </author>
        <category label="Kubernetes" term="Kubernetes"/>
        <category label="AWS" term="AWS"/>
        <category label="Security" term="Security"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[AWS पर LLM Observability के लिए Langfuse Self-Host किया]]></title>
        <id>https://tanmay.dev/hi/blog/self-hosting-langfuse-on-aws/</id>
        <link href="https://tanmay.dev/hi/blog/self-hosting-langfuse-on-aws/"/>
        <updated>2026-03-05T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[हमारी टीम LLM-powered features बना रही थी — agents, chat interfaces, document analysis — और हमें बिल्कुल visibility नहीं थी कि models actually क्या कर रहे हैं। कोई trace नहीं कि कौन से prompts भेजे गए, latency क्या थी, हर call कितना cost कर रही थी, या outputs अच्छे थे या नहीं।]]></summary>
        <content type="html"><![CDATA[<p>हमारी टीम LLM-powered features बना रही थी — agents, chat interfaces, document analysis — और हमें बिल्कुल visibility नहीं थी कि models actually क्या कर रहे हैं। कोई trace नहीं कि कौन से prompts भेजे गए, latency क्या थी, हर call कितना cost कर रही थी, या outputs अच्छे थे या नहीं।</p>
<p>मैंने hosted observability platforms evaluate किए, लेकिन data residency requirements की वजह से सब कुछ अपने cloud account में ही रखना था। तो मैंने <strong>Langfuse</strong> — एक open-source LLM observability platform — AWS पर self-host किया। यह रहा architecture और क्या सीखा।</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="langfuse-क्या-करता-है">Langfuse क्या करता है<a href="https://tanmay.dev/hi/blog/self-hosting-langfuse-on-aws/#langfuse-%E0%A4%95%E0%A5%8D%E0%A4%AF%E0%A4%BE-%E0%A4%95%E0%A4%B0%E0%A4%A4%E0%A4%BE-%E0%A4%B9%E0%A5%88" class="hash-link" aria-label="Langfuse क्या करता है का सीधा लिंक" title="Langfuse क्या करता है का सीधा लिंक" translate="no">​</a></h2>
<p><a href="https://langfuse.com/" target="_blank" rel="noopener noreferrer" class="">Langfuse</a> LLM observability के लिए एक open-source platform है। यह देता है:</p>
<ul>
<li class=""><strong>Traces</strong>: हर LLM call input, output, latency, token count, और cost के साथ logged</li>
<li class=""><strong>Sessions</strong>: Related LLM calls को user sessions में group करो conversation flows debug करने के लिए</li>
<li class=""><strong>Evaluations</strong>: Outputs को manually या automated eval pipelines से score करो</li>
<li class=""><strong>Metrics</strong>: Cost, latency, error rates, और usage trends के dashboards</li>
</ul>
<p>इसे LLM calls के लिए Datadog समझो। SDKs LangChain, OpenAI, और अन्य LLM frameworks के साथ कुछ lines of code में integrate हो जाते हैं।</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="self-host-क्यों">Self-Host क्यों<a href="https://tanmay.dev/hi/blog/self-hosting-langfuse-on-aws/#self-host-%E0%A4%95%E0%A5%8D%E0%A4%AF%E0%A5%8B%E0%A4%82" class="hash-link" aria-label="Self-Host क्यों का सीधा लिंक" title="Self-Host क्यों का सीधा लिंक" translate="no">​</a></h2>
<p>तीन कारण:</p>
<ol>
<li class=""><strong>Data residency</strong>: हम prompt/completion data (जिसमें sensitive information हो सकती है) किसी third-party SaaS को अपने cloud account के बाहर नहीं भेज सकते थे।</li>
<li class=""><strong>Scale पर cost</strong>: Langfuse की hosted pricing traces की संख्या पर scale होती है। हमारे volume पर, self-hosting काफ़ी सस्ता था।</li>
<li class=""><strong>Customization</strong>: Retention policies, access controls, और existing monitoring stack के साथ integration पर हमें control चाहिए था।</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="architecture">Architecture<a href="https://tanmay.dev/hi/blog/self-hosting-langfuse-on-aws/#architecture" class="hash-link" aria-label="Architecture का सीधा लिंक" title="Architecture का सीधा लिंक" translate="no">​</a></h2>
<p>Langfuse v3 के दो main components हैं: एक <strong>web server</strong> (UI और API) और एक <strong>worker</strong> (background processing)। इसके पीछे चार data stores चाहिए।</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="compute-ecs-fargate">Compute: ECS Fargate<a href="https://tanmay.dev/hi/blog/self-hosting-langfuse-on-aws/#compute-ecs-fargate" class="hash-link" aria-label="Compute: ECS Fargate का सीधा लिंक" title="Compute: ECS Fargate का सीधा लिंक" translate="no">​</a></h3>
<p>Web server और worker दोनों <strong>ECS Fargate</strong> services के रूप में चलते हैं:</p>
<ul>
<li class=""><strong>Web</strong>: 2 vCPU, 4 GB RAM, port 3000 पर expose</li>
<li class=""><strong>Worker</strong>: 2 vCPU, 4 GB RAM, port 3030 पर</li>
</ul>
<p>Fargate यहाँ सही choice था क्योंकि:</p>
<ul>
<li class="">कोई nodes manage, patch, या right-size नहीं करने</li>
<li class="">CPU/memory utilization पर auto-scaling</li>
<li class="">EKS cluster से isolated जहाँ application workloads चलते हैं (blast radius containment)</li>
</ul>
<p>हो सकता है वे database passwords और encryption keys जैसे secrets service configuration के साथ रखें, container image के बाहर।</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="oltp-rds-postgresql">OLTP: RDS PostgreSQL<a href="https://tanmay.dev/hi/blog/self-hosting-langfuse-on-aws/#oltp-rds-postgresql" class="hash-link" aria-label="OLTP: RDS PostgreSQL का सीधा लिंक" title="OLTP: RDS PostgreSQL का सीधा लिंक" translate="no">​</a></h3>
<p>Langfuse transactional data के लिए Postgres इस्तेमाल करता है — user accounts, project configs, session metadata, evaluation scores। एक छोटा <code>t4g.small</code> RDS instance इसे आसानी से handle करता है। Price/performance ratio के लिए Graviton।</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="olap-ec2-पर-clickhouse">OLAP: EC2 पर ClickHouse<a href="https://tanmay.dev/hi/blog/self-hosting-langfuse-on-aws/#olap-ec2-%E0%A4%AA%E0%A4%B0-clickhouse" class="hash-link" aria-label="OLAP: EC2 पर ClickHouse का सीधा लिंक" title="OLAP: EC2 पर ClickHouse का सीधा लिंक" translate="no">​</a></h3>
<p>यहाँ trace data रहता है — और यह बड़ा हो सकता है। Langfuse traces, spans, और observations पर analytical queries के लिए <strong>ClickHouse</strong> इस्तेमाल करता है।</p>
<p>मैं ClickHouse एक single EC2 instance पर चलाता हूँ:</p>
<ul>
<li class=""><strong>Instance type</strong>: <code>c7g.xlarge</code> (ARM/Graviton, 4 vCPU, 8 GB RAM)</li>
<li class=""><strong>OS</strong>: Ubuntu 24.04</li>
<li class=""><strong>Storage</strong>: Expected trace volume के हिसाब से sized gp3 EBS volume</li>
</ul>
<p>Managed ClickHouse service क्यों नहीं? Cost। Single EC2 instance पर ClickHouse managed alternatives से dramatically सस्ता है, और एक internal observability tool के लिए, एक ClickHouse node manage करने का operational overhead acceptable है।</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="cache-redis-valkey">Cache: Redis (Valkey)<a href="https://tanmay.dev/hi/blog/self-hosting-langfuse-on-aws/#cache-redis-valkey" class="hash-link" aria-label="Cache: Redis (Valkey) का सीधा लिंक" title="Cache: Redis (Valkey) का सीधा लिंक" translate="no">​</a></h3>
<p>ElastiCache Valkey node web और worker services के बीच caching और job queues handle करता है। एक छोटा single node काफ़ी है।</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="blob-storage-s3">Blob Storage: S3<a href="https://tanmay.dev/hi/blog/self-hosting-langfuse-on-aws/#blob-storage-s3" class="hash-link" aria-label="Blob Storage: S3 का सीधा लिंक" title="Blob Storage: S3 का सीधा लिंक" translate="no">​</a></h3>
<p>Langfuse event payloads और media uploads (traces से attached screenshots, audio files) S3 में store करता है। दो prefixes: <code>events/</code> और <code>media/</code>।</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="traffic-flow">Traffic Flow<a href="https://tanmay.dev/hi/blog/self-hosting-langfuse-on-aws/#traffic-flow" class="hash-link" aria-label="Traffic Flow का सीधा लिंक" title="Traffic Flow का सीधा लिंक" translate="no">​</a></h3>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">Applications (SDK) ──→ Internal ALB (TLS) ──→ Langfuse Web (:3000)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                                                    │</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Team (VPN से) ─────→ Internal ALB (TLS) ──→ Langfuse Web (:3000)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                                                    │</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                                              Langfuse Worker (:3030)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                                                    │</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                                    ┌───────────────┼───────────────┐</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                                    │               │               │</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                                Postgres       ClickHouse        S3</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                                (OLTP)          (OLAP)        (Blobs)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                                    │               │</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                                    └───────┬───────┘</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                                          Redis</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                                        (Cache/Queue)</span><br></div></code></pre></div></div>
<p>Internal ALB TLS terminate करता है और सिर्फ़ VPC के अंदर (और VPN से) reachable है। Langfuse UI के लिए Google SSO enable है ताकि टीम existing corporate accounts से sign in करे।</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="deployment">Deployment<a href="https://tanmay.dev/hi/blog/self-hosting-langfuse-on-aws/#deployment" class="hash-link" aria-label="Deployment का सीधा लिंक" title="Deployment का सीधा लिंक" translate="no">​</a></h2>
<p>ECS task definitions official Langfuse Docker images reference करती हैं (<code>langfuse/langfuse:3</code> web के लिए)। Worker अलग image इस्तेमाल करता है।</p>
<p>Configuration पूरी तरह environment variables से:</p>
<ul>
<li class=""><code>DATABASE_URL</code> — Postgres connection string</li>
<li class=""><code>CLICKHOUSE_URL</code> — ClickHouse HTTP endpoint (private IP)</li>
<li class=""><code>REDIS_HOST</code> — ElastiCache endpoint</li>
<li class=""><code>S3_BUCKET</code> — Blob storage bucket</li>
<li class=""><code>NEXTAUTH_URL</code> — Langfuse UI का public URL</li>
<li class=""><code>ENCRYPTION_KEY</code> — Postgres में sensitive data at rest encrypt करने के लिए</li>
</ul>
<p>Logs CloudWatch में जाते हैं। हर service के लिए अलग log group — easy filtering के लिए।</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="cost">Cost<a href="https://tanmay.dev/hi/blog/self-hosting-langfuse-on-aws/#cost" class="hash-link" aria-label="Cost का सीधा लिंक" title="Cost का सीधा लिंक" translate="no">​</a></h2>
<p>कुछ लाख traces per month generate करने वाली team के लिए:</p>
<table><thead><tr><th>Component</th><th style="text-align:right">मासिक Cost (लगभग)</th></tr></thead><tbody><tr><td>ECS Fargate (2 services)</td><td style="text-align:right">~$70</td></tr><tr><td>RDS t4g.small</td><td style="text-align:right">~$30</td></tr><tr><td>EC2 c7g.xlarge (ClickHouse)</td><td style="text-align:right">~$90</td></tr><tr><td>ElastiCache (small Valkey)</td><td style="text-align:right">~$15</td></tr><tr><td>S3 storage</td><td style="text-align:right">~$5</td></tr><tr><td>ALB</td><td style="text-align:right">~$20</td></tr><tr><td><strong>कुल</strong></td><td style="text-align:right"><strong>~$230/माह</strong></td></tr></tbody></table>
<p>Scale पर Langfuse Cloud की pricing से compare करो, और self-hosting जल्दी pay for itself कर लेता है।</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="मैंने-क्या-सीखा">मैंने क्या सीखा<a href="https://tanmay.dev/hi/blog/self-hosting-langfuse-on-aws/#%E0%A4%AE%E0%A5%88%E0%A4%82%E0%A4%A8%E0%A5%87-%E0%A4%95%E0%A5%8D%E0%A4%AF%E0%A4%BE-%E0%A4%B8%E0%A5%80%E0%A4%96%E0%A4%BE" class="hash-link" aria-label="मैंने क्या सीखा का सीधा लिंक" title="मैंने क्या सीखा का सीधा लिंक" translate="no">​</a></h2>
<ol>
<li class="">
<p><strong>ARM पर ClickHouse great fit है</strong>। Graviton instances per dollar ज़्यादा compute देते हैं, और ClickHouse ARM पर natively चलता है। c7g family इसके लिए ideal है।</p>
</li>
<li class="">
<p><strong>शुरू से OLTP और OLAP अलग रखो</strong>। Langfuse v3 का Postgres (transactions) और ClickHouse (analytics) वाला architecture सही design है। सब कुछ Postgres पर चलाने की कोशिश मत करो — trace query performance जल्दी degrade होगी।</p>
</li>
<li class="">
<p><strong>Internal tools के लिए ECS Fargate underrated है</strong>। Stateless web services के लिए Kubernetes से simpler है, ख़ासकर जब EKS की full power की ज़रूरत न हो। Task definitions, service auto-scaling, और ALB integration बस काम करता है।</p>
</li>
<li class="">
<p><strong>हो सकता है वे secrets image के बाहर रखें</strong>, internal tool के लिए। Production application workloads के लिए Secrets Manager या Parameter Store इस्तेमाल करूँगा।</p>
</li>
<li class="">
<p><strong>VPN-only access non-negotiable है</strong>। LLM traces में prompts और completions होते हैं जिनमें sensitive data हो सकता है। Langfuse UI कभी public internet पर expose नहीं होना चाहिए।</p>
</li>
</ol>
<p>अगर आप LLM workloads चला रहे हैं और उनमें कोई observability नहीं है, तो Langfuse visibility पाने का सबसे तेज़ तरीका है। Self-hosting में लगभग एक दिन लगता है और small-to-medium team के लिए $250/माह से कम cost है।</p>]]></content>
        <author>
            <name>Tanmay Mohan</name>
            <uri>https://github.com/tanmaymohan</uri>
        </author>
        <category label="AI" term="AI"/>
        <category label="Observability" term="Observability"/>
        <category label="AWS" term="AWS"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Strict Compliance वाली टीम के लिए VPN Access कैसे डिज़ाइन किया]]></title>
        <id>https://tanmay.dev/hi/blog/openvpn-cloudconnexa-architecture/</id>
        <link href="https://tanmay.dev/hi/blog/openvpn-cloudconnexa-architecture/"/>
        <updated>2026-02-25T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[जब मैंने infrastructure lead के रूप में join किया, तो internal tools जैसे dashboards, databases, और monitoring public internet पर accessible थे — सिर्फ़ application-level authentication से protected। यह काफ़ी नहीं है जब आप regulated environment में sensitive data handle कर रहे हों। मुझे ऐसा VPN solution चाहिए था जो manage करने में आसान हो, compliance requirements पूरा करे, और टीम को slow न करे।]]></summary>
        <content type="html"><![CDATA[<p>जब मैंने infrastructure lead के रूप में join किया, तो internal tools जैसे dashboards, databases, और monitoring public internet पर accessible थे — सिर्फ़ application-level authentication से protected। यह काफ़ी नहीं है जब आप regulated environment में sensitive data handle कर रहे हों। मुझे ऐसा VPN solution चाहिए था जो manage करने में आसान हो, compliance requirements पूरा करे, और टीम को slow न करे।</p>
<p>यह रहा कैसे मैंने <strong>OpenVPN CloudConnexa</strong> से इसे डिज़ाइन किया।</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="requirements">Requirements<a href="https://tanmay.dev/hi/blog/openvpn-cloudconnexa-architecture/#requirements" class="hash-link" aria-label="Requirements का सीधा लिंक" title="Requirements का सीधा लिंक" translate="no">​</a></h2>
<ol>
<li class=""><strong>सभी internal tools VPN के पीछे</strong>: Dashboards, databases, internal APIs, monitoring — इनमें से कुछ भी public internet से reachable नहीं होना चाहिए।</li>
<li class=""><strong>Compliance-ready</strong>: Setup को ISO 27001 और अन्य security standards के अनुरूप होना था — encryption in transit, access logging, periodic audits।</li>
<li class=""><strong>आसान onboarding</strong>: नए टीम members को 10 मिनट से कम में बिना IT की मदद के VPN access सेट कर लेना चाहिए।</li>
<li class=""><strong>Single identity</strong>: अलग VPN credentials नहीं। वही corporate identity (Google Workspace) जो टीम बाकी सब के लिए इस्तेमाल करती है।</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="cloudconnexa-क्यों-चुना">CloudConnexa क्यों चुना<a href="https://tanmay.dev/hi/blog/openvpn-cloudconnexa-architecture/#cloudconnexa-%E0%A4%95%E0%A5%8D%E0%A4%AF%E0%A5%8B%E0%A4%82-%E0%A4%9A%E0%A5%81%E0%A4%A8%E0%A4%BE" class="hash-link" aria-label="CloudConnexa क्यों चुना का सीधा लिंक" title="CloudConnexa क्यों चुना का सीधा लिंक" translate="no">​</a></h2>
<p>मैंने self-hosted OpenVPN, WireGuard, और Tailscale evaluate किए। Deciding factors:</p>
<ul>
<li class=""><strong>Cloud-hosted</strong>: कोई VPN server maintain, patch, या scale नहीं करना। CloudConnexa managed service के रूप में चलता है।</li>
<li class=""><strong>Multi-cloud connectivity</strong>: मुझे AWS और GCP के VPCs connect करने थे। CloudConnexa hub की तरह काम करता है जो दोनों को interconnect करता है।</li>
<li class=""><strong>SAML integration</strong>: Google Workspace as identity provider के लिए native support — VPN access और बाकी सब पर same MFA policies enforce कर सकता हूँ।</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="architecture">Architecture<a href="https://tanmay.dev/hi/blog/openvpn-cloudconnexa-architecture/#architecture" class="hash-link" aria-label="Architecture का सीधा लिंक" title="Architecture का सीधा लिंक" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="split-tunnel">Split Tunnel<a href="https://tanmay.dev/hi/blog/openvpn-cloudconnexa-architecture/#split-tunnel" class="hash-link" aria-label="Split Tunnel का सीधा लिंक" title="Split Tunnel का सीधा लिंक" translate="no">​</a></h3>
<p>VPN <strong>split tunnel</strong> के रूप में configured है: सिर्फ़ internal network ranges के लिए destined traffic VPN tunnel से जाता है। बाकी सारा internet traffic सीधे user की device से internet पर जाता है।</p>
<p>यह दो कारणों से ज़रूरी है:</p>
<ul>
<li class=""><strong>Performance</strong>: Users को slower browsing, video calls, या downloads का अनुभव नहीं होता क्योंकि वो traffic VPN को छूता ही नहीं।</li>
<li class=""><strong>Cost</strong>: VPN bandwidth metered है। सारा traffic tunnel से route करना wasteful और महँगा होगा।</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="authentication">Authentication<a href="https://tanmay.dev/hi/blog/openvpn-cloudconnexa-architecture/#authentication" class="hash-link" aria-label="Authentication का सीधा लिंक" title="Authentication का सीधा लिंक" translate="no">​</a></h3>
<ul>
<li class=""><strong>Google SAML SSO</strong>: Users अपने corporate Google account से sign in करते हैं। कोई अलग VPN username/password नहीं।</li>
<li class=""><strong>Enforced MFA</strong>: Google Workspace सभी users के लिए multi-factor authentication enforce करता है। चूँकि VPN auth Google से होता है, VPN access के लिए भी MFA automatically required है।</li>
<li class=""><strong>Automated profiles</strong>: जब user पहली बार login करता है, VPN profile अपने आप generate होकर OpenVPN Connect app में deploy हो जाती है। कोई manual config file distribution नहीं।</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="device-और-location-policies">Device और Location Policies<a href="https://tanmay.dev/hi/blog/openvpn-cloudconnexa-architecture/#device-%E0%A4%94%E0%A4%B0-location-policies" class="hash-link" aria-label="Device और Location Policies का सीधा लिंक" title="Device और Location Policies का सीधा लिंक" translate="no">​</a></h3>
<ul>
<li class=""><strong>Per user एक device</strong>: हर व्यक्ति एक device (work laptop) register कर सकता है VPN access के लिए। इससे credential sharing रुकती है और attack surface सीमित होता है।</li>
<li class=""><strong>Geographic restriction</strong>: VPN connections सिर्फ़ India से accept होते हैं। यह CloudConnexa level पर enforce है और compromised credentials के unexpected location से use होने पर protection का एक layer जोड़ता है।</li>
<li class=""><strong>Shield</strong>: CloudConnexa का Shield feature continuous monitoring से potential threats detect करता है।</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="vpn-के-पीछे-क्या-जाता-है">VPN के पीछे क्या जाता है<a href="https://tanmay.dev/hi/blog/openvpn-cloudconnexa-architecture/#vpn-%E0%A4%95%E0%A5%87-%E0%A4%AA%E0%A5%80%E0%A4%9B%E0%A5%87-%E0%A4%95%E0%A5%8D%E0%A4%AF%E0%A4%BE-%E0%A4%9C%E0%A4%BE%E0%A4%A4%E0%A4%BE-%E0%A4%B9%E0%A5%88" class="hash-link" aria-label="VPN के पीछे क्या जाता है का सीधा लिंक" title="VPN के पीछे क्या जाता है का सीधा लिंक" translate="no">​</a></h2>
<p>CloudConnexa deploy करने के बाद, मैंने सभी internal tools इसके पीछे रखे:</p>
<ul>
<li class="">Business intelligence dashboards</li>
<li class="">LLM observability platform</li>
<li class="">CI/CD platform UI</li>
<li class="">Database admin access</li>
<li class="">Internal APIs और microservice endpoints जिन्हें external exposure की ज़रूरत नहीं</li>
<li class="">Configuration management tools</li>
<li class="">Monitoring और alerting dashboards</li>
</ul>
<p>सभी के लिए traffic flow same है: <strong>VPN user → Route 53 DNS → Internal load balancer (TLS के साथ) → Service</strong>।</p>
<p>Internal load balancers public internet से reachable नहीं हैं। ये private subnets में हैं और सिर्फ़ VPN CIDR range और VPC से traffic accept करते हैं।</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="compliance-alignment">Compliance Alignment<a href="https://tanmay.dev/hi/blog/openvpn-cloudconnexa-architecture/#compliance-alignment" class="hash-link" aria-label="Compliance Alignment का सीधा लिंक" title="Compliance Alignment का सीधा लिंक" translate="no">​</a></h2>
<p>Regulated environments में काम करने वाली teams के लिए, यह architecture क्या provide करता है:</p>
<table><thead><tr><th>Requirement</th><th>कैसे पूरा होता है</th></tr></thead><tbody><tr><td>Encryption in transit</td><td>सारा VPN traffic OpenVPN protocol से encrypted; internal ALBs TLS terminate करते हैं</td></tr><tr><td>Access logging</td><td>CloudConnexa user connections, authentication events, और DNS queries के detailed logs maintain करता है</td></tr><tr><td>MFA enforcement</td><td>Google Workspace SAML handle करता है — bypass का कोई तरीका नहीं</td></tr><tr><td>Periodic audits</td><td>Connection logs और DNS logs audit review के लिए exportable हैं</td></tr><tr><td>Access control</td><td>सिर्फ़ specific user groups specific applications access कर सकते हैं</td></tr><tr><td>Device control</td><td>Per user single registered device</td></tr></tbody></table>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="onboarding-flow">Onboarding Flow<a href="https://tanmay.dev/hi/blog/openvpn-cloudconnexa-architecture/#onboarding-flow" class="hash-link" aria-label="Onboarding Flow का सीधा लिंक" title="Onboarding Flow का सीधा लिंक" translate="no">​</a></h2>
<p>नए टीम member के लिए day one पर:</p>
<ol>
<li class="">CloudConnexa portal URL पर navigate करो</li>
<li class="">Corporate Google account से sign in करो (Google SAML पर redirect)</li>
<li class="">अपने OS के लिए OpenVPN Connect app download करो</li>
<li class="">Profile अपने आप app में import हो जाती है</li>
<li class="">Nearest region select करो और connect करो</li>
</ol>
<p>Total time: 5 मिनट से कम। कोई IT tickets नहीं, कोई config files नहीं, कोई manual steps नहीं।</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="मैं-क्या-अलग-करता">मैं क्या अलग करता<a href="https://tanmay.dev/hi/blog/openvpn-cloudconnexa-architecture/#%E0%A4%AE%E0%A5%88%E0%A4%82-%E0%A4%95%E0%A5%8D%E0%A4%AF%E0%A4%BE-%E0%A4%85%E0%A4%B2%E0%A4%97-%E0%A4%95%E0%A4%B0%E0%A4%A4%E0%A4%BE" class="hash-link" aria-label="मैं क्या अलग करता का सीधा लिंक" title="मैं क्या अलग करता का सीधा लिंक" translate="no">​</a></h2>
<ul>
<li class=""><strong>Point-to-point rules</strong>: अभी, एक बार VPN पर आने के बाद, internal network पर कोई भी host reach कर सकते हो। एक mature setup user group के आधार पर access restrict करेगा — जैसे developers सिर्फ़ staging reach कर सकें, production databases नहीं। यह मेरे roadmap पर है।</li>
<li class=""><strong>MDM integration</strong>: VPN access को mobile device management solution के साथ combine करने से device-level policies (encryption, OS version, patch level) VPN access की condition के रूप में enforce कर सकता हूँ।</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="मुख्य-बात">मुख्य बात<a href="https://tanmay.dev/hi/blog/openvpn-cloudconnexa-architecture/#%E0%A4%AE%E0%A5%81%E0%A4%96%E0%A5%8D%E0%A4%AF-%E0%A4%AC%E0%A4%BE%E0%A4%A4" class="hash-link" aria-label="मुख्य बात का सीधा लिंक" title="मुख्य बात का सीधा लिंक" translate="no">​</a></h2>
<p>CloudConnexa जैसा managed VPN self-hosting का operational burden हटाता है और enterprise-grade access controls देता है। SAML SSO, enforced MFA, split tunneling, और geographic restrictions का combination ज़्यादातर compliance requirements cover करता है बिना solution को overengineer किए।</p>
<p>सबसे अच्छी बात: setup में लगभग एक हफ़्ता लगा, और नए user को onboard करने में 5 मिनट।</p>]]></content>
        <author>
            <name>Tanmay Mohan</name>
            <uri>https://github.com/tanmaymohan</uri>
        </author>
        <category label="Security" term="Security"/>
        <category label="Networking" term="Networking"/>
        <category label="Compliance" term="Compliance"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[एक महीने में 4 GPU Workloads GCP से AWS पर माइग्रेट किए]]></title>
        <id>https://tanmay.dev/hi/blog/migrating-gpu-workloads-across-clouds/</id>
        <link href="https://tanmay.dev/hi/blog/migrating-gpu-workloads-across-clouds/"/>
        <updated>2026-02-10T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[हमारे पास GCP virtual machines पर चार GPU workloads चल रहे थे — speech-to-text (Whisper), एक general Triton inference server, Stable Diffusion 3, और एक open-source LLM server (vLLM)। ये unmanaged थे, undocumented थे, और महँगे थे। मैंने चारों को लगभग एक महीने में AWS EKS पर शिफ़्ट किया। यह रहा पूरा अनुभव और क्या सीखा।]]></summary>
        <content type="html"><![CDATA[<p>हमारे पास GCP virtual machines पर चार GPU workloads चल रहे थे — speech-to-text (Whisper), एक general Triton inference server, Stable Diffusion 3, और एक open-source LLM server (vLLM)। ये unmanaged थे, undocumented थे, और महँगे थे। मैंने चारों को लगभग एक महीने में AWS EKS पर शिफ़्ट किया। यह रहा पूरा अनुभव और क्या सीखा।</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="हमने-क्यों-शिफ़्ट-किया">हमने क्यों शिफ़्ट किया<a href="https://tanmay.dev/hi/blog/migrating-gpu-workloads-across-clouds/#%E0%A4%B9%E0%A4%AE%E0%A4%A8%E0%A5%87-%E0%A4%95%E0%A5%8D%E0%A4%AF%E0%A5%8B%E0%A4%82-%E0%A4%B6%E0%A4%BF%E0%A4%AB%E0%A4%BC%E0%A5%8D%E0%A4%9F-%E0%A4%95%E0%A4%BF%E0%A4%AF%E0%A4%BE" class="hash-link" aria-label="हमने क्यों शिफ़्ट किया का सीधा लिंक" title="हमने क्यों शिफ़्ट किया का सीधा लिंक" translate="no">​</a></h2>
<p>चार चीज़ों ने यह फ़ैसला push किया:</p>
<ol>
<li class=""><strong>GCP पर GPU availability inconsistent थी</strong>। Quota increases में समय लगता था, और जो specific GPU SKUs चाहिए थे वो हमारे regions में हमेशा available नहीं होते थे।</li>
<li class=""><strong>Cost consolidation</strong>। एक अलग cloud provider पर GPU workloads चलाने का मतलब था अलग billing, अलग IAM, अलग networking। छोटे GPU fleet के लिए दो clouds मैनेज करने का overhead justify नहीं होता था।</li>
<li class=""><strong>Management overhead</strong>। GCP GPU instances manually provisioned VMs थे — कोई Kubernetes नहीं, कोई CI/CD नहीं, कोई proper monitoring नहीं। हर deployment SSH-and-pray था।</li>
<li class=""><strong>Latency</strong>। Production traffic पहले से AWS में था और GPUs GCP में। Audio, client-facing apps के लिए यह extra hop समस्या था।</li>
</ol>
<p>लक्ष्य सीधा था: सब कुछ AWS EKS में लाओ जहाँ बाकी infrastructure पहले से है, same CI/CD pipelines और monitoring stack से managed।</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="हमने-क्या-शिफ़्ट-किया">हमने क्या शिफ़्ट किया<a href="https://tanmay.dev/hi/blog/migrating-gpu-workloads-across-clouds/#%E0%A4%B9%E0%A4%AE%E0%A4%A8%E0%A5%87-%E0%A4%95%E0%A5%8D%E0%A4%AF%E0%A4%BE-%E0%A4%B6%E0%A4%BF%E0%A4%AB%E0%A4%BC%E0%A5%8D%E0%A4%9F-%E0%A4%95%E0%A4%BF%E0%A4%AF%E0%A4%BE" class="hash-link" aria-label="हमने क्या शिफ़्ट किया का सीधा लिंक" title="हमने क्या शिफ़्ट किया का सीधा लिंक" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-whisper-audio-speech-to-text">1. Whisper Audio (Speech-to-Text)<a href="https://tanmay.dev/hi/blog/migrating-gpu-workloads-across-clouds/#1-whisper-audio-speech-to-text" class="hash-link" aria-label="1. Whisper Audio (Speech-to-Text) का सीधा लिंक" title="1. Whisper Audio (Speech-to-Text) का सीधा लिंक" translate="no">​</a></h3>
<p>यह सबसे complex workload था। हम OpenAI का Whisper model audio transcription के लिए चला रहे थे।</p>
<p>GCP पर, यह NVIDIA GPU वाली VM पर standalone process के रूप में चलता था। AWS पर, मैंने इसे <strong>NVIDIA Triton Inference Server</strong> और <strong>TensorRT-LLM</strong> से rebuild किया:</p>
<ul>
<li class=""><strong>Multi-stage Docker build</strong>: पहला stage Hugging Face model weights से TRT-LLM engines compile करता है <code>convert_checkpoint.py</code> और <code>trtllm-build</code> (FP16, max batch size 2) से। दूसरा stage compiled engines को lightweight Triton runtime image में copy करता है।</li>
<li class=""><strong>Model weights</strong>: Build time पर S3 से Docker image में sync होते हैं। इससे image self-contained बनती है — कोई runtime downloads नहीं, कोई model registry dependency नहीं।</li>
<li class=""><strong>Serving</strong>: Triton gRPC (port 8001) और HTTP (port 8000) endpoints expose करता है। हम backend services से low-latency inference के लिए gRPC endpoint इस्तेमाल करते हैं।</li>
<li class=""><strong>Batching</strong>: Triton में dynamic batching enable है, max queue delay 100 microseconds। BLS (Business Logic Scripting) pipeline WAV input → Whisper inference → transcript output handle करती है।</li>
</ul>
<p><strong>Target instance</strong>: <code>g6.xlarge</code> (NVIDIA L4, 24 GB VRAM) — Whisper FP16 engines के लिए काफ़ी है।</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-triton-inference-server-general">2. Triton Inference Server (General)<a href="https://tanmay.dev/hi/blog/migrating-gpu-workloads-across-clouds/#2-triton-inference-server-general" class="hash-link" aria-label="2. Triton Inference Server (General) का सीधा लिंक" title="2. Triton Inference Server (General) का सीधा लिंक" translate="no">​</a></h3>
<p>कई छोटे models host करने वाला general-purpose Triton server। यह सीधा Docker image था (<code>nvcr.io/nvidia/tritonserver:24.06-py3</code>) जिसमें persistent storage से model repository mount होती थी।</p>
<p>AWS पर, यह EKS में Karpenter-managed dedicated GPU node pool पर चलता है। Node pool configured है कि सिर्फ़ GPU workloads schedule हों (taints और tolerations से), तो non-GPU pods कभी महँगे GPU nodes पर नहीं आएँगे।</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-image-generation-sd3">3. Image Generation (SD3)<a href="https://tanmay.dev/hi/blog/migrating-gpu-workloads-across-clouds/#3-image-generation-sd3" class="hash-link" aria-label="3. Image Generation (SD3) का सीधा लिंक" title="3. Image Generation (SD3) का सीधा लिंक" translate="no">​</a></h3>
<p>Internal image generation tasks के लिए Stable Diffusion 3। GCP पर <code>g6.2xlarge</code>-equivalent VM थी। AWS पर, same instance type (<code>g6.2xlarge</code>, NVIDIA L4) EKS में।</p>
<p>Migration के बाद पता चला कि यह workload बहुत कम इस्तेमाल होता है। मैंने node group बंद किया और deployment config Git में रख दिया जब ज़रूरत हो तब के लिए। इससे अकेले <code>g6.2xlarge</code> जितना on-demand cost बचा।</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="4-open-source-llm-serving">4. Open-Source LLM Serving<a href="https://tanmay.dev/hi/blog/migrating-gpu-workloads-across-clouds/#4-open-source-llm-serving" class="hash-link" aria-label="4. Open-Source LLM Serving का सीधा लिंक" title="4. Open-Source LLM Serving का सीधा लिंक" translate="no">​</a></h3>
<p>हम open-source large language models evaluate कर रहे थे। मैंने <strong>vLLM</strong> को OpenAI-compatible API server के रूप में सेट किया:</p>
<div class="language-dockerfile codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-dockerfile codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">FROM vllm/vllm-openai:v0.6.3</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">CMD ["--model", "mistralai/Mistral-7B-Instruct-v0.3", "--port", "8080", "--host", "0.0.0.0"]</span><br></div></code></pre></div></div>
<p>बेहद सीधा। Image tag pin करो — <code>latest</code> मत भेजो। vLLM KV-cache management, continuous batching, और tensor parallelism अपने आप handle करता है। API OpenAI के chat completions format के साथ compatible है, तो backend services को switch करने में zero code changes लगे — बस URL बदला।</p>
<p>इस workload के लिए, मैंने कई GPU types पर test किया:</p>
<ul>
<li class=""><strong>H200</strong> tensor parallelism 2 के साथ — latency-sensitive use cases के लिए अच्छा</li>
<li class=""><strong>B200</strong> tensor parallelism 1 के साथ — higher concurrency (~80-100 concurrent requests) पर बेहतर throughput</li>
</ul>
<p>Benchmarking से पता चला कि optimal concurrency GPU और parallelism strategy पर बहुत depend करता है। कोई universal "best" setting नहीं है।</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="migration-कैसे-काम-किया">Migration कैसे काम किया<a href="https://tanmay.dev/hi/blog/migrating-gpu-workloads-across-clouds/#migration-%E0%A4%95%E0%A5%88%E0%A4%B8%E0%A5%87-%E0%A4%95%E0%A4%BE%E0%A4%AE-%E0%A4%95%E0%A4%BF%E0%A4%AF%E0%A4%BE" class="hash-link" aria-label="Migration कैसे काम किया का सीधा लिंक" title="Migration कैसे काम किया का सीधा लिंक" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-1-data-और-models-extract-करो">Step 1: Data और Models Extract करो<a href="https://tanmay.dev/hi/blog/migrating-gpu-workloads-across-clouds/#step-1-data-%E0%A4%94%E0%A4%B0-models-extract-%E0%A4%95%E0%A4%B0%E0%A5%8B" class="hash-link" aria-label="Step 1: Data और Models Extract करो का सीधा लिंक" title="Step 1: Data और Models Extract करो का सीधा लिंक" translate="no">​</a></h3>
<p>GCP VMs से model weights, embeddings, और persistent data S3 में copy किया। Large models के लिए, <code>gsutil</code> से GCS से local staging area में copy किया, फिर <code>aws s3 sync</code> से target bucket में push किया।</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-2-सब-कुछ-containerize-करो">Step 2: सब कुछ Containerize करो<a href="https://tanmay.dev/hi/blog/migrating-gpu-workloads-across-clouds/#step-2-%E0%A4%B8%E0%A4%AC-%E0%A4%95%E0%A5%81%E0%A4%9B-containerize-%E0%A4%95%E0%A4%B0%E0%A5%8B" class="hash-link" aria-label="Step 2: सब कुछ Containerize करो का सीधा लिंक" title="Step 2: सब कुछ Containerize करो का सीधा लिंक" translate="no">​</a></h3>
<p>हर workload का अपना Dockerfile बना। मुख्य फ़ैसले:</p>
<ul>
<li class=""><strong>Base images pin करो</strong>: Specific NVIDIA container image tags (जैसे <code>nvcr.io/nvidia/tritonserver:24.06-py3</code>) इस्तेमाल करो, <code>latest</code> नहीं।</li>
<li class=""><strong>Inference workloads में models image में bake करो</strong>: कोई runtime downloads नहीं मतलब faster cold starts और startup पर external storage पर कोई dependency नहीं।</li>
<li class=""><strong>Training workloads अलग रखो</strong>: Training jobs runtime पर S3 से data pull करते हैं क्योंकि datasets बार-बार बदलते हैं।</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-3-eks-में-deploy-करो">Step 3: EKS में Deploy करो<a href="https://tanmay.dev/hi/blog/migrating-gpu-workloads-across-clouds/#step-3-eks-%E0%A4%AE%E0%A5%87%E0%A4%82-deploy-%E0%A4%95%E0%A4%B0%E0%A5%8B" class="hash-link" aria-label="Step 3: EKS में Deploy करो का सीधा लिंक" title="Step 3: EKS में Deploy करो का सीधा लिंक" translate="no">​</a></h3>
<p>सभी workloads EKS में Kubernetes Deployments के रूप में deploy:</p>
<ul>
<li class=""><strong>GPU scheduling</strong>: GPU workloads के लिए <code>PriorityClass</code> (value 100000) — ताकि GPU nodes पर किसी lower-priority pods से पहले इन्हें priority मिले।</li>
<li class=""><strong>Node management</strong>: Karpenter demand पर GPU nodes (<code>g6.xlarge</code>, <code>g6.2xlarge</code>) provision करता है और utilization कम होने पर consolidate करता है। Non-critical GPU workloads (training, evals) के लिए Spot instances, production inference के लिए on-demand।</li>
<li class=""><strong>Ingress</strong>: Triton और vLLM endpoints के लिए gRPC-compatible <strong>internal ALB</strong>। Public internet पर नहीं। In-VPC hop (ALB + CoreDNS) पुराने AWS-to-GCP round trip से छोटा है।</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-4-gcp-बंद-करो">Step 4: GCP बंद करो<a href="https://tanmay.dev/hi/blog/migrating-gpu-workloads-across-clouds/#step-4-gcp-%E0%A4%AC%E0%A4%82%E0%A4%A6-%E0%A4%95%E0%A4%B0%E0%A5%8B" class="hash-link" aria-label="Step 4: GCP बंद करो का सीधा लिंक" title="Step 4: GCP बंद करो का सीधा लिंक" translate="no">​</a></h3>
<p>जब सारा traffic AWS endpoints से flow होने लगा और parity confirm हो गई:</p>
<ol>
<li class="">सभी GCP GPU VMs बंद किए</li>
<li class="">Safety के लिए disks snapshot लिए</li>
<li class="">2-week bake period के बाद VMs permanently बंद किए</li>
<li class="">GCP billing account बंद किया</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="मैंने-क्या-सीखा">मैंने क्या सीखा<a href="https://tanmay.dev/hi/blog/migrating-gpu-workloads-across-clouds/#%E0%A4%AE%E0%A5%88%E0%A4%82%E0%A4%A8%E0%A5%87-%E0%A4%95%E0%A5%8D%E0%A4%AF%E0%A4%BE-%E0%A4%B8%E0%A5%80%E0%A4%96%E0%A4%BE" class="hash-link" aria-label="मैंने क्या सीखा का सीधा लिंक" title="मैंने क्या सीखा का सीधा लिंक" translate="no">​</a></h2>
<ol>
<li class="">
<p><strong>Migrate करने से पहले containerize करो</strong>। अगर workload अभी भी VM पर bare process के रूप में चल रहा है, तो पहले source cloud पर ही containerize करो। Cloud migration की complexity जोड़ने से पहले container issues debug करो।</p>
</li>
<li class="">
<p><strong>TensorRT-LLM build complexity के बावजूद worth है</strong>। Whisper TRT-LLM का multi-stage build trivial नहीं है, लेकिन vanilla PyTorch की तुलना में inference performance improvement significant है। L4 पर FP16 engines ने fraction of the cost पर ज़रूरी throughput दिया।</p>
</li>
<li class="">
<p><strong>Open-source LLMs serve करने का सबसे तेज़ तरीका vLLM है</strong>। OpenAI-compatible API का मतलब है कि आपके application code को पता नहीं चलता कि वो self-hosted model से बात कर रहा है। Continuous batching variable concurrency को gracefully handle करती है।</p>
</li>
<li class="">
<p><strong>जो GPU workloads कोई use नहीं करता, उनके लिए GPU nodes चालू मत रखो</strong>। SD3 instance occasional internal use के लिए real money खर्च कर रहा था। Bursty GPU workloads के लिए scale to zero और demand पर spin up — यही सही pattern है।</p>
</li>
<li class="">
<p><strong>Clouds consolidate करने से सब simplify होता है</strong>। एक IAM system, एक monitoring stack, एक CI/CD pipeline, एक networking layer। Operational overhead reduction instance cost savings से ज़्यादा valuable है।</p>
</li>
</ol>
<p>GPU workloads को अलग-अलग providers पर कैसे चलाता हूँ, इसके बारे में docs में <a class="" href="https://tanmay.dev/hi/docs/running-gpus-in-2026/">Running GPUs in 2026</a> guide देखें।</p>]]></content>
        <author>
            <name>Tanmay Mohan</name>
            <uri>https://github.com/tanmaymohan</uri>
        </author>
        <category label="GPU" term="GPU"/>
        <category label="Cloud" term="Cloud"/>
        <category label="AWS" term="AWS"/>
        <category label="Migration" term="Migration"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[क्लाउड कॉस्ट $20k से ज़्यादा कैसे कम किया — 8 सीख]]></title>
        <id>https://tanmay.dev/hi/blog/cost-optimization-journey/</id>
        <link href="https://tanmay.dev/hi/blog/cost-optimization-journey/"/>
        <updated>2026-01-22T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[मैंने एक पूरी तिमाही क्लाउड कॉस्ट को समझने और कम करने में लगाई — बिना reliability या developer velocity को नुकसान पहुँचाए। अंत तक नीचे दिए line items मिलाकर लगभग $1,800 USD प्रति माह — यानी सालाना $20k से ज़्यादा — AWS और GCP बिल से कट गए। Spot और GPU Reserved Instance discounts इसके ऊपर हैं, और उन्हें एक ही संख्या में बाँधना मुश्किल है।]]></summary>
        <content type="html"><![CDATA[<p>मैंने एक पूरी तिमाही क्लाउड कॉस्ट को समझने और कम करने में लगाई — बिना reliability या developer velocity को नुकसान पहुँचाए। अंत तक नीचे दिए line items मिलाकर लगभग <strong>$1,800 USD प्रति माह</strong> — यानी सालाना <strong>$20k से ज़्यादा</strong> — AWS और GCP बिल से कट गए। Spot और GPU Reserved Instance discounts इसके ऊपर हैं, और उन्हें एक ही संख्या में बाँधना मुश्किल है।</p>
<p>यहाँ वो 8 चीज़ें हैं जिनसे असल में फ़र्क पड़ा।</p>
<!-- -->
<p>अगर आप भी कॉस्ट ऑप्टिमाइज़ेशन पर काम कर रहे हैं, तो उम्मीद है ये नोट्स आपके काम आएंगे। अपनी सीख शेयर करने के लिए मेरे सोशल्स पर जुड़ें।</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="सीख-1--kubernetes-pod-packing">सीख #1 — Kubernetes Pod Packing<a href="https://tanmay.dev/hi/blog/cost-optimization-journey/#%E0%A4%B8%E0%A5%80%E0%A4%96-1--kubernetes-pod-packing" class="hash-link" aria-label="सीख #1 — Kubernetes Pod Packing का सीधा लिंक" title="सीख #1 — Kubernetes Pod Packing का सीधा लिंक" translate="no">​</a></h2>
<p>हमेशा अपने deployments में <code>requests</code> और <code>limits</code> सही से कॉन्फ़िगर करें। मेरे केस में, कई पुरानी ऐप्लिकेशन्स जो बमुश्किल इस्तेमाल होती थीं, उनमें अभी भी डिफ़ॉल्ट resource values थीं — या फिर runtime usage से कहीं ज़्यादा।</p>
<p>मैंने staging और UAT एनवायरनमेंट से शुरुआत की। मॉनिटरिंग से मिले real usage data के आधार पर हर ऐप्लिकेशन की CPU और memory requests को रीकॉन्फ़िगर किया। जब दो हफ़्ते तक सब स्टेबल रहा, तब production में भी यही बदलाव किए।</p>
<p>नतीजा चौंकाने वाला था: <strong>स्टैक मुख्य रूप से memory-heavy था और CPU usage बहुत कम</strong>। मैं <code>m6a.xlarge</code>, <code>c7i-flex</code>, और <code>r6a.xlarge</code> इंस्टेंसेज़ का मिश्रण चला रहा था — compute-optimized और general-purpose मशीनों का ऐसा कॉम्बिनेशन जो workload प्रोफ़ाइल से बिल्कुल मेल नहीं खाता था।</p>
<p>बदलाव के बाद, मैंने सभी एनवायरनमेंट में पूरी तरह <strong><code>r6a.xlarge</code> और <code>r6a.2xlarge</code></strong> (memory-optimized) पर शिफ़्ट किया। मैंने <a href="https://www.vantage.sh/" target="_blank" rel="noopener noreferrer" class="">Vantage के MCP server</a> को Cursor से कनेक्ट करके instance types ब्राउज़ किए और अपने region में सबसे कम spot price खोजा।</p>
<p><strong>नतीजा</strong>: कम instances, बेहतर bin packing, और fleet में कम DaemonSet pods।</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="सीख-2--gpu-use-case-में-बदलाव">सीख #2 — GPU Use Case में बदलाव<a href="https://tanmay.dev/hi/blog/cost-optimization-journey/#%E0%A4%B8%E0%A5%80%E0%A4%96-2--gpu-use-case-%E0%A4%AE%E0%A5%87%E0%A4%82-%E0%A4%AC%E0%A4%A6%E0%A4%B2%E0%A4%BE%E0%A4%B5" class="hash-link" aria-label="सीख #2 — GPU Use Case में बदलाव का सीधा लिंक" title="सीख #2 — GPU Use Case में बदलाव का सीधा लिंक" translate="no">​</a></h2>
<p>मैं <code>g6.xlarge</code> और <code>g6.2xlarge</code> इंस्टेंसेज़ (NVIDIA L4 GPUs) चला रहा था — हर node पूरी तरह एक ही pod को dedicated था — whisper audio transcription, image generation, और एक general inference server के लिए।</p>
<p>GPU utilization का ऑडिट करने के बाद पता चला:</p>
<ul>
<li class="">Whisper workload <strong>TensorRT-LLM</strong> के साथ Triton Server पर FP16 में चल सकता था, जिससे memory footprint काफ़ी कम हो गया</li>
<li class="">कई inference workloads <strong>Kubernetes PriorityClasses</strong> और proper resource scheduling से GPU nodes शेयर कर सकते थे</li>
<li class="">कुछ workloads जो dedicated GPU nodes पर चल रहे थे, उन्हें ज़्यादा efficiently batch किया जा सकता था (max batch size tuning से idle GPU time कम हुआ)</li>
</ul>
<p>मैंने GPU instances को <strong>Reserved Instances</strong> पर शिफ़्ट किया क्योंकि अब वे long-running और predictable थे। इससे on-demand pricing की तुलना में काफ़ी बचत हुई।</p>
<p><strong>नतीजा</strong>: कम GPU nodes, हर node पर बेहतर utilization, और on-demand की जगह RI pricing।</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="सीख-3--eks-पर-spot-instances">सीख #3 — EKS पर Spot Instances<a href="https://tanmay.dev/hi/blog/cost-optimization-journey/#%E0%A4%B8%E0%A5%80%E0%A4%96-3--eks-%E0%A4%AA%E0%A4%B0-spot-instances" class="hash-link" aria-label="सीख #3 — EKS पर Spot Instances का सीधा लिंक" title="सीख #3 — EKS पर Spot Instances का सीधा लिंक" translate="no">​</a></h2>
<p>यह सबसे आसान जीत थी और सबसे ज़्यादा रिटर्न वाली। मैंने non-production एनवायरनमेंट से शुरुआत की:</p>
<ol>
<li class="">UAT और staging node groups में spot capacity जोड़ी</li>
<li class=""><strong>Karpenter</strong> को ऐसे NodePools के साथ कॉन्फ़िगर किया जो <code>spot</code> और <code>on-demand</code> capacity types मिक्स करते हैं</li>
<li class="">Consolidation को कुछ मिनट के underutilization के बाद किक इन होने दिया — Karpenter अपने आप underused nodes को replace या remove करता है</li>
</ol>
<p>जब spot interruption handling पर भरोसा हो गया, तब production में भी stateless workloads और GPU training jobs के लिए spot एक्सटेंड किया।</p>
<p>मुख्य बात: <strong>memory-optimized spot instances इस्तेमाल करें</strong> (<code>r6a</code> और <code>m6a</code> families जैसी) क्योंकि <code>ap-south-1</code> region में इनकी spot availability बेहतर होती है और interruption rates compute-optimized types से कम।</p>
<p><strong>नतीजा</strong>: सभी non-production एनवायरनमेंट और bursty production workloads में compute costs में काफ़ी कमी।</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="सीख-4--reserved-instances-और-savings-plans">सीख #4 — Reserved Instances और Savings Plans<a href="https://tanmay.dev/hi/blog/cost-optimization-journey/#%E0%A4%B8%E0%A5%80%E0%A4%96-4--reserved-instances-%E0%A4%94%E0%A4%B0-savings-plans" class="hash-link" aria-label="सीख #4 — Reserved Instances और Savings Plans का सीधा लिंक" title="सीख #4 — Reserved Instances और Savings Plans का सीधा लिंक" translate="no">​</a></h2>
<p>Right-sizing के बाद, baseline compute needs की साफ़ तस्वीर थी। मैंने commit किया:</p>
<ul>
<li class=""><strong>GPU Reserved Instances</strong>: Production inference चलाने वाले <code>g6.xlarge</code> और <code>g6.2xlarge</code> instances अब predictable और long-running थे — 1-year RIs के लिए बिल्कुल सही</li>
<li class=""><strong>Compute Savings Plans</strong>: General EKS compute baseline के लिए, Compute Savings Plan ने बिल के predictable हिस्से पर लगभग <strong>20% discount</strong> दिया (~$550/माह बचत)</li>
</ul>
<p>ट्रिक यह है कि right-sizing <em>से पहले</em> RIs या Savings Plans न ख़रीदें। मैंने देखा है कि टीमें oversized instances पर commitments लॉक कर लेती हैं और फिर capacity use करने में दिक्कत होती है। पहले right-size करो, फिर commit करो।</p>
<p><strong>नतीजा</strong>: सिर्फ़ Savings Plans से ~$550/माह बचत, plus GPU RIs से अतिरिक्त बचत।</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="सीख-5--database-optimization">सीख #5 — Database Optimization<a href="https://tanmay.dev/hi/blog/cost-optimization-journey/#%E0%A4%B8%E0%A5%80%E0%A4%96-5--database-optimization" class="hash-link" aria-label="सीख #5 — Database Optimization का सीधा लिंक" title="सीख #5 — Database Optimization का सीधा लिंक" translate="no">​</a></h2>
<p>Databases एक चौंकाने वाला cost center थे। तीन बदलावों से फ़र्क पड़ा:</p>
<ol>
<li class="">
<p><strong>DocumentDB से MongoDB Atlas में माइग्रेशन</strong>: DocumentDB ~$170/माह खर्च कर रहा था। MongoDB Atlas ने नए customers को credits ऑफ़र किए, और माइग्रेशन सीधा था (export, import, connection strings अपडेट)। पहले साल credits से कॉस्ट कवर हुई, और credits ख़त्म होने के बाद भी Atlas pricing कम थी।</p>
</li>
<li class="">
<p><strong>RDS right-sizing</strong>: कई RDS instances ओवरसाइज़्ड specs पर चल रहे थे जो शुरू में provision किए गए थे। मैंने CPU, memory, connections, और IOPS के लिए CloudWatch metrics रिव्यू किए — फिर जहाँ safe था वहाँ downsize किया। इससे fleet में ~$150/माह बचत हुई।</p>
</li>
<li class="">
<p><strong>Unused databases को archive किया</strong>: एक पुराना data science database जिसे महीनों से किसी ने query नहीं किया था, उसका snapshot लिया और बंद कर दिया।</p>
</li>
</ol>
<p><strong>नतीजा</strong>: Database बदलावों से कुल ~$320/माह बचत।</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="सीख-6--storage-optimization">सीख #6 — Storage Optimization<a href="https://tanmay.dev/hi/blog/cost-optimization-journey/#%E0%A4%B8%E0%A5%80%E0%A4%96-6--storage-optimization" class="hash-link" aria-label="सीख #6 — Storage Optimization का सीधा लिंक" title="सीख #6 — Storage Optimization का सीधा लिंक" translate="no">​</a></h2>
<p>Storage costs चुपचाप बढ़ते हैं। मुझे ये मिला:</p>
<ul>
<li class=""><strong>GP2 से GP3 माइग्रेशन</strong>: GP3 volumes GP2 से कम कीमत पर बेहतर baseline performance देते हैं। EBS volumes को GP2 से GP3 में migrate करने से zero downtime के साथ ~$50/माह बचत हुई (AWS online volume type changes सपोर्ट करता है)।</li>
<li class=""><strong>S3 Intelligent Tiering</strong>: उन buckets पर enable किया जहाँ access patterns unpredictable थे। 30 दिन access न होने पर objects अपने आप cheaper tier में चले जाते हैं।</li>
<li class=""><strong>Archived buckets डिलीट किए</strong>: पुराने backup buckets और data exports जिनकी ज़रूरत नहीं थी, साफ़ किए।</li>
<li class=""><strong>Multipart upload lifecycle rules</strong>: Incomplete multipart uploads चुपचाप जमा होते रहते हैं। 7 दिन बाद abort करने की lifecycle rule से wasted storage साफ़ हुई।</li>
</ul>
<p><strong>नतीजा</strong>: EBS बदलावों से ~$50/माह, plus S3 lifecycle management से ongoing बचत।</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="सीख-7--unused-services-बंद-करना">सीख #7 — Unused Services बंद करना<a href="https://tanmay.dev/hi/blog/cost-optimization-journey/#%E0%A4%B8%E0%A5%80%E0%A4%96-7--unused-services-%E0%A4%AC%E0%A4%82%E0%A4%A6-%E0%A4%95%E0%A4%B0%E0%A4%A8%E0%A4%BE" class="hash-link" aria-label="सीख #7 — Unused Services बंद करना का सीधा लिंक" title="सीख #7 — Unused Services बंद करना का सीधा लिंक" translate="no">​</a></h2>
<p>यह सबसे बड़ी category थी। ऐसी services जो किसी ख़ास काम के लिए सेटअप की गईं, कुछ समय इस्तेमाल हुईं, और फिर भूल गए — लेकिन चलती रहीं और बिल आता रहा:</p>
<ul>
<li class=""><strong>पुराना Elasticsearch cluster</strong>: Workloads newer, right-sized setup में जाने के बाद पूरी तरह deprecated किया। यह <strong>$504/माह</strong> खर्च कर रहा था — सबसे बड़ा single line item जो मैंने हटाया।</li>
<li class=""><strong>Unused load balancers</strong>: Classic load balancers जो अब traffic route नहीं कर रहे थे। हर idle ALB/CLB अभी भी ~$16-22/माह खर्च करता है। इन्हें हटाने से ~$50/माह बचत हुई।</li>
<li class=""><strong>Unused Lambda functions</strong>: पुराने functions जो कभी trigger नहीं होते थे लेकिन provisioned concurrency या attached resources थे। Cleanup से ~$15/माह बचत।</li>
<li class=""><strong>CloudWatch Insights</strong>: EKS clusters पर Container Insights enable था लेकिन dashboards कभी इस्तेमाल नहीं हुए। सिर्फ़ data ingestion ~$200/माह खर्च कर रही थी। Confirm करने के बाद कि कोई इस पर depend नहीं करता, disable किया।</li>
</ul>
<p><strong>नतीजा</strong>: Service deprecation से ~$769/माह — Savings Plans से पहले सबसे बड़ी single category।</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="सीख-8--billing-और-account-structure">सीख #8 — Billing और Account Structure<a href="https://tanmay.dev/hi/blog/cost-optimization-journey/#%E0%A4%B8%E0%A5%80%E0%A4%96-8--billing-%E0%A4%94%E0%A4%B0-account-structure" class="hash-link" aria-label="सीख #8 — Billing और Account Structure का सीधा लिंक" title="सीख #8 — Billing और Account Structure का सीधा लिंक" translate="no">​</a></h2>
<p>दो बदलाव जिनमें कोई infrastructure काम नहीं:</p>
<ol>
<li class="">
<p><strong>AWS पर invoiced billing में शिफ़्ट किया</strong>: Credit card payments की जगह invoiced billing पर गए। इससे better payment terms मिले और account team से volume discounts का रास्ता खुला (committed spend पर typically 5-10% monthly bill पर)।</p>
</li>
<li class="">
<p><strong>पुराने cloud accounts बंद किए</strong>: एक GCP project था जो AWS में workloads migrate करने के बाद ज़्यादातर unused था। Project archive करने और बचे resources साफ़ करने से GCP bill ~10,000 INR/माह कम हुआ।</p>
</li>
</ol>
<p><strong>नतीजा</strong>: AWS पर better payment terms और एक पूरा cloud provider bill ख़त्म।</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="कुल-बचत">कुल बचत<a href="https://tanmay.dev/hi/blog/cost-optimization-journey/#%E0%A4%95%E0%A5%81%E0%A4%B2-%E0%A4%AC%E0%A4%9A%E0%A4%A4" class="hash-link" aria-label="कुल बचत का सीधा लिंक" title="कुल बचत का सीधा लिंक" translate="no">​</a></h2>
<table><thead><tr><th>Category</th><th style="text-align:right">मासिक बचत (लगभग)</th></tr></thead><tbody><tr><td>Savings Plans (compute)</td><td style="text-align:right">$550</td></tr><tr><td>Elasticsearch deprecation</td><td style="text-align:right">$504</td></tr><tr><td>CloudWatch Insights</td><td style="text-align:right">$200</td></tr><tr><td>DocumentDB → MongoDB Atlas</td><td style="text-align:right">$170</td></tr><tr><td>RDS right-sizing</td><td style="text-align:right">$150</td></tr><tr><td>Load balancer cleanup</td><td style="text-align:right">$50</td></tr><tr><td>GP2 → GP3</td><td style="text-align:right">$50</td></tr><tr><td>Lambda cleanup</td><td style="text-align:right">$15</td></tr><tr><td>GCP project closure</td><td style="text-align:right">~$120 (10k INR)</td></tr><tr><td><strong>कुल</strong></td><td style="text-align:right"><strong>~$1,800+/माह</strong></td></tr></tbody></table>
<p>Spot instance savings (जिसकी fixed number बताना मुश्किल है) और GPU RI discounts मिलाकर, सालाना आँकड़ा आराम से <strong>$20,000 USD</strong> से ज़्यादा है।</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="अगर-कोई-इसकी-शुरुआत-कर-रहा-हो-तो-मेरी-सलाह">अगर कोई इसकी शुरुआत कर रहा हो तो मेरी सलाह<a href="https://tanmay.dev/hi/blog/cost-optimization-journey/#%E0%A4%85%E0%A4%97%E0%A4%B0-%E0%A4%95%E0%A5%8B%E0%A4%88-%E0%A4%87%E0%A4%B8%E0%A4%95%E0%A5%80-%E0%A4%B6%E0%A5%81%E0%A4%B0%E0%A5%81%E0%A4%86%E0%A4%A4-%E0%A4%95%E0%A4%B0-%E0%A4%B0%E0%A4%B9%E0%A4%BE-%E0%A4%B9%E0%A5%8B-%E0%A4%A4%E0%A5%8B-%E0%A4%AE%E0%A5%87%E0%A4%B0%E0%A5%80-%E0%A4%B8%E0%A4%B2%E0%A4%BE%E0%A4%B9" class="hash-link" aria-label="अगर कोई इसकी शुरुआत कर रहा हो तो मेरी सलाह का सीधा लिंक" title="अगर कोई इसकी शुरुआत कर रहा हो तो मेरी सलाह का सीधा लिंक" translate="no">​</a></h2>
<ol>
<li class=""><strong>Visibility से शुरू करो</strong>: जो दिखता नहीं, उसे काट नहीं सकते। बदलाव करने से पहले cost allocation tags सेट करो और AWS Cost Explorer या Vantage जैसा tool इस्तेमाल करो।</li>
<li class=""><strong>Commit करने से पहले right-size करो</strong>: जब तक instances right-size नहीं हो जाते, Savings Plans या RIs मत ख़रीदो। वरना waste लॉक हो जाएगा।</li>
<li class=""><strong>पहले non-production</strong>: हर बदलाव (spot instances, instance type changes, database migrations) production छूने से पहले staging में टेस्ट करो।</li>
<li class=""><strong>Aggressively deprecate करो</strong>: सबसे बड़ी बचत हमेशा उन चीज़ों को बंद करने से आती है जो अब कोई इस्तेमाल नहीं करता — न कि चलती हुई चीज़ों को सस्ता करने से।</li>
<li class=""><strong>आदत बनाओ</strong>: Cost optimization एक बार का प्रोजेक्ट नहीं है। मैं अब हर महीने बिल रिव्यू करता हूँ और drift ढूँढता हूँ।</li>
</ol>
<p>इन techniques की step-by-step guides के लिए, docs section में <a class="" href="https://tanmay.dev/hi/docs/cost-optimization-playbook/">Cost Optimization Playbook</a> देखें।</p>]]></content>
        <author>
            <name>Tanmay Mohan</name>
            <uri>https://github.com/tanmaymohan</uri>
        </author>
        <category label="Cloud" term="Cloud"/>
        <category label="Cost Optimization" term="Cost Optimization"/>
        <category label="Optimization" term="Optimization"/>
        <category label="Kubernetes" term="Kubernetes"/>
        <category label="AWS" term="AWS"/>
    </entry>
</feed>