<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: evan</title>
    <description>The latest articles on DEV Community by evan (@evannn-dev).</description>
    <link>https://dev.to/evannn-dev</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4175773%2F63b9c094-cc02-448a-aefd-8bebfa49b7a3.png</url>
      <title>DEV Community: evan</title>
      <link>https://dev.to/evannn-dev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/evannn-dev"/>
    <language>en</language>
    <item>
      <title>why your pod memory limits keep crashing and how to fix them</title>
      <dc:creator>evan</dc:creator>
      <pubDate>Fri, 23 Oct 2026 11:42:26 +0000</pubDate>
      <link>https://dev.to/evannn-dev/why-your-pod-memory-limits-keep-crashing-and-how-to-fix-them-32d5</link>
      <guid>https://dev.to/evannn-dev/why-your-pod-memory-limits-keep-crashing-and-how-to-fix-them-32d5</guid>
      <description>&lt;p&gt;you've probably seen a pod go into CrashLoopBackOff after a sudden traffic spike, even though the container's memory request matches the node's capacity. the root cause is an invisible cgroup pressure that the scheduler can't see, and a missing hard limit that lets the process eat the node's RAM until the kernel OOM killer steps in.&lt;/p&gt;

&lt;h2&gt;
  
  
  the hidden pressure
&lt;/h2&gt;

&lt;p&gt;kubernetes only schedules based on the &lt;em&gt;request&lt;/em&gt; field; if you omit a hard &lt;em&gt;limit&lt;/em&gt;, the container can exceed its request without any back‑pressure. the node's cgroup will then balloon, starving sibling pods and eventually triggering a node‑wide OOM. the failure surface is hard to diagnose because the pod logs show nothing unusual—only the termination reason "OOMKilled".&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Pod&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;memory‑stress&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;myorg/app:latest&lt;/span&gt;
    &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;256Mi"&lt;/span&gt;
      &lt;span class="na"&gt;limits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;512Mi"&lt;/span&gt;   &lt;span class="c1"&gt;# explicit hard limit&lt;/span&gt;
    &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ENABLE_METRICS&lt;/span&gt;
      &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true"&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cgroup‑exporter&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;prom/cgroup-exporter:latest&lt;/span&gt;
    &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;limits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;64Mi"&lt;/span&gt;
    &lt;span class="na"&gt;volumeMounts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cgroup&lt;/span&gt;
      &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/host/sys/fs/cgroup&lt;/span&gt;
  &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cgroup&lt;/span&gt;
    &lt;span class="na"&gt;hostPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/sys/fs/cgroup&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the sidecar above scrapes &lt;code&gt;/sys/fs/cgroup&lt;/code&gt; and pushes memory usage into Prometheus, giving you a real‑time view of the container's actual consumption versus its limits. coupling this with an alert on &lt;code&gt;container_memory_working_set_bytes&lt;/code&gt; approaching &lt;code&gt;container_spec_memory_limit_bytes&lt;/code&gt; catches overshoots before the kernel kills the pod.&lt;/p&gt;

&lt;h3&gt;
  
  
  trade‑offs and failure modes
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;limit too low&lt;/strong&gt; – the pod throttles under normal load, increasing latency and causing request retries that amplify load. you need to benchmark realistic burst patterns and set the limit a safe headroom (often 1.5‑2× the 95th‑percentile usage).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;limit too high&lt;/strong&gt; – you re‑introduce noisy‑neighbor risk; a memory leak in one tenant can starve the rest of the node. run chaos experiments that inject a synthetic leak (&lt;code&gt;stress --vm 2 --vm-bytes 300M&lt;/code&gt;) to verify bulkhead isolation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;metrics lag&lt;/strong&gt; – if the exporter polls too infrequently, you may react after the OOM event. configure a scrape interval of ≤15s for critical services.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;node‑level cgroup fragmentation&lt;/strong&gt; – on long‑running clusters, leftover cgroup hierarchies can corrupt accounting. schedule a periodic &lt;code&gt;systemd-run --quiet --property=Delegate=yes&lt;/code&gt; cleanup job to prune orphaned groups.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;by treating the memory limit as a contract rather than a suggestion, you turn an invisible failure domain into an observable one. the contract must be continuously validated in production; otherwise you inherit the same brittle behavior you tried to avoid.&lt;/p&gt;

&lt;p&gt;what's your approach to validating pod memory limits in production without triggering OOM kills&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>go</category>
      <category>observability</category>
      <category>terraform</category>
    </item>
    <item>
      <title>Why explicit cgroup limits are non‑negotiable in production Kubernetes clusters</title>
      <dc:creator>evan</dc:creator>
      <pubDate>Tue, 20 Oct 2026 02:51:58 +0000</pubDate>
      <link>https://dev.to/evannn-dev/why-explicit-cgroup-limits-are-non-negotiable-in-production-kubernetes-clusters-57n2</link>
      <guid>https://dev.to/evannn-dev/why-explicit-cgroup-limits-are-non-negotiable-in-production-kubernetes-clusters-57n2</guid>
      <description>&lt;p&gt;when a new tenant lands on a shared node, the first thing that goes wrong is usually a runaway container that steals memory or CPU, starving the rest of the workloads. in my last on‑call rotation the node went from 70 % to 100 % memory within minutes, the OOM killer started terminating unrelated pods, and the incident stretched beyond our SLA.&lt;/p&gt;

&lt;h2&gt;
  
  
  the missing guard: cgroup limits in pod specs
&lt;/h2&gt;

&lt;p&gt;Kubernetes lets you describe resource requests and limits, but the defaults are often left at "0" (unbounded) because the UI hides the fields or the helm chart skips them for simplicity. without explicit &lt;code&gt;memory&lt;/code&gt; and &lt;code&gt;cpu&lt;/code&gt; limits, the container runs directly against the node’s cgroup hierarchy, which is exactly the noisy‑neighbor scenario we try to avoid.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Pod&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;data‑processor&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;worker&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;myorg/processor:latest&lt;/span&gt;
    &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;500m"&lt;/span&gt;
        &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;256Mi"&lt;/span&gt;
      &lt;span class="na"&gt;limits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1"&lt;/span&gt;
        &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;512Mi"&lt;/span&gt;
    &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;OTEL_EXPORTER_OTLP_ENDPOINT&lt;/span&gt;
      &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://otel-collector:4317"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;limits&lt;/code&gt; line creates a hard cgroup cap; the kernel will throttle CPU and trigger OOM kill only for that container. note that the &lt;code&gt;requests&lt;/code&gt; field also informs the scheduler, keeping the pod on a node with sufficient headroom.&lt;/p&gt;

&lt;h2&gt;
  
  
  trade‑offs you have to accept
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;throughput vs safety&lt;/strong&gt; – a tightly capped pod may hit back‑pressure sooner, requiring you to tune your application’s internal queueing or enable graceful degradation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;cost impact&lt;/strong&gt; – over‑provisioning limits forces you to allocate more nodes to meet aggregate demand, but the alternative is unpredictable eviction and higher incident toil.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;observability burden&lt;/strong&gt; – you now need to surface cgroup metrics (e.g. &lt;code&gt;container_cpu_cfs_throttled_seconds_total&lt;/code&gt;, &lt;code&gt;container_memory_working_set_bytes&lt;/code&gt;) into Prometheus and set alerts for sustained throttling or OOM events.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  continuous validation with chaos engineering
&lt;/h2&gt;

&lt;p&gt;Static limits are only as good as the workload profiles you measured. embed a simple chaos test in your CI pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;TestMemoryThrottle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// launch pod with 128Mi limit, feed 200Mi of data&lt;/span&gt;
    &lt;span class="c"&gt;// assert that the process respects context cancellation after 30s&lt;/span&gt;
    &lt;span class="c"&gt;// verify that the node’s memory usage stays below 85% during the run&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running this test on every PR forces developers to keep the container footprint in check and surfaces regression before it hits production. combine it with a production‑grade chaos runner (e.g. Litmus or Gremlin) that randomly spikes CPU on a node to verify that other pods stay within their cgroup caps.&lt;/p&gt;

&lt;h2&gt;
  
  
  edge cases to watch
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;burstable QoS&lt;/strong&gt; – pods with &lt;code&gt;requests &amp;lt; limits&lt;/code&gt; can be throttled under node pressure; if your SLAs depend on consistent latency, consider &lt;code&gt;requests == limits&lt;/code&gt; to lock them in the Guaranteed QoS class.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;sidecar interference&lt;/strong&gt; – a logging sidecar that writes massive buffers can bypass the main container’s limits; enforce limits at the pod level or run sidecars in separate pods.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;node‑level cgroup overrides&lt;/strong&gt; – some managed K8s providers inject default limits that conflict with your spec; always inspect the effective cgroup hierarchy (&lt;code&gt;/sys/fs/cgroup/...&lt;/code&gt;) on a test node.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;by treating cgroup limits as a non‑negotiable contract rather than an optional knob, you shrink the failure surface dramatically and make chaos tests meaningful. how do you enforce and validate resource caps in your clusters, and what observability patterns have saved you from noisy‑neighbor incidents?&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>cgroup</category>
      <category>observability</category>
      <category>cicd</category>
    </item>
    <item>
      <title>hard‑capped CPU in k8s: stopping silent throttling before it crashes your node</title>
      <dc:creator>evan</dc:creator>
      <pubDate>Thu, 15 Oct 2026 10:22:02 +0000</pubDate>
      <link>https://dev.to/evannn-dev/hard-capped-cpu-in-k8s-stopping-silent-throttling-before-it-crashes-your-node-49jp</link>
      <guid>https://dev.to/evannn-dev/hard-capped-cpu-in-k8s-stopping-silent-throttling-before-it-crashes-your-node-49jp</guid>
      <description>&lt;p&gt;in a typical autoscaling deployment you’ll see pods spin up, hit their request rates, and then the node suddenly starts paging. the symptom is a spike in latency without any OOM kills – a classic silent CPU throttle that the kernel reports only in cgroup stats.&lt;/p&gt;

&lt;h2&gt;
  
  
  the silent throttle
&lt;/h2&gt;

&lt;p&gt;kubernetes lets you specify &lt;code&gt;requests&lt;/code&gt; and &lt;code&gt;limits&lt;/code&gt;, but the default &lt;code&gt;cpu&lt;/code&gt; limit is &lt;em&gt;soft&lt;/em&gt; when you use the &lt;code&gt;cpu&lt;/code&gt; &lt;em&gt;shares&lt;/em&gt; model on older cgroup v1 setups. the scheduler assumes the node can overcommit, and the kernel throttles containers that exceed their share without raising an alert. the result is a cascade: one noisy neighbor starves others, the node’s load average climbs, and latency budgets break.&lt;/p&gt;

&lt;h3&gt;
  
  
  a reproducible pod spec
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Pod&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;throttle-demo&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx:stable-alpine&lt;/span&gt;
    &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;250m"&lt;/span&gt;
      &lt;span class="na"&gt;limits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;500m"&lt;/span&gt;   &lt;span class="c1"&gt;# hard cap with cgroup v2&lt;/span&gt;
    &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;LOAD_DURATION&lt;/span&gt;
      &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;30"&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;load-generator&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;busybox&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sh"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-c"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;while&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;true;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;do&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;dd&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;if=/dev/zero&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;of=/dev/null&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;bs=1M&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;count=10;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;sleep&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;1;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;done"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;limits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;500m"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the &lt;code&gt;limits&lt;/code&gt; line forces a hard quota when the node runs a recent kernel with cgroup v2 enabled. you can verify the enforcement with &lt;code&gt;kubectl top pod throttle-demo&lt;/code&gt; and &lt;code&gt;cat /sys/fs/cgroup/cpu/kubepods/.../cpu.max&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  trade‑offs to watch
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;over‑provisioning vs latency&lt;/strong&gt; – setting limits too low guarantees throttling under normal load; too high defeats the purpose and re‑introduces noisy‑neighbor risk.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;cost impact&lt;/strong&gt; – hard caps improve bin‑packing efficiency, but you may need more nodes to meet peak demand, increasing egress and instance fees.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;observability gap&lt;/strong&gt; – most dashboards surface CPU &lt;em&gt;usage&lt;/em&gt; but not &lt;em&gt;throttling time&lt;/em&gt;. instrument your pods with &lt;code&gt;cpu.throttled.time&lt;/code&gt; from &lt;code&gt;/sys/fs/cgroup/cpu.stat&lt;/code&gt; and forward it to Prometheus; set alerts on a threshold of &amp;gt;5% throttled time over a 5‑minute window.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;failure mode testing&lt;/strong&gt; – simulate a burst with a temporary &lt;code&gt;load-generator&lt;/code&gt; sidecar or a chaos‑engineered pod that spikes CPU. verify that the throttled pod degrades gracefully (e.g., returns 503 with a Retry‑After) rather than timing out.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  cost‑aware scaling
&lt;/h2&gt;

&lt;p&gt;pair hard caps with a custom HPA that uses the throttling metric as a scaling signal instead of pure CPU utilization. this prevents the controller from scaling up based on a single hot pod that’s already hitting its quota.&lt;/p&gt;




&lt;p&gt;how do you validate that your resource limits actually protect the node under load without throttling critical paths&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>cpu</category>
      <category>observability</category>
      <category>terraform</category>
    </item>
    <item>
      <title>Why Unbounded Worker Pools Blow Up Go Services Under Back‑pressure</title>
      <dc:creator>evan</dc:creator>
      <pubDate>Mon, 12 Oct 2026 15:59:25 +0000</pubDate>
      <link>https://dev.to/evannn-dev/why-unbounded-worker-pools-blow-up-go-services-under-back-pressure-2jm4</link>
      <guid>https://dev.to/evannn-dev/why-unbounded-worker-pools-blow-up-go-services-under-back-pressure-2jm4</guid>
      <description>&lt;h3&gt;
  
  
  problem statement
&lt;/h3&gt;

&lt;p&gt;in a recent deployment we added a background worker pool to a high‑traffic HTTP service. the pool pulled jobs from a shared channel without any limit on the number of goroutines. under normal load the service seemed fine, but a spike in upstream traffic caused the request queue to fill, the pool spawned thousands of goroutines, and the pod hit OOM kills within seconds. the crash cascaded to the entire deployment because the readiness probe never recovered.&lt;/p&gt;

&lt;h2&gt;
  
  
  why the pool exploded
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;no back‑pressure&lt;/strong&gt; – a plain &lt;code&gt;chan Job&lt;/code&gt; without a buffer or a semaphore allows any number of producers to push work.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;missing context propagation&lt;/strong&gt; – workers ignored the request context, so they kept processing even after the client timed out.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;absence of telemetry&lt;/strong&gt; – only occasional log lines were emitted; the CPU and memory usage spiked unnoticed until the pod was evicted.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  a minimal reproducible snippet
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Job&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;startWorkerPool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jobs&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="n"&gt;Job&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;job&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;jobs&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c"&gt;// &amp;lt;- unbounded loop&lt;/span&gt;
        &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="n"&gt;Job&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c"&gt;// heavy processing, no ctx&lt;/span&gt;
            &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}(&lt;/span&gt;&lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;jobs&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="n"&gt;Job&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// unbuffered, producers block only on send&lt;/span&gt;
    &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="n"&gt;startWorkerPool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandleFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/enqueue"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c"&gt;// simulate burst of 10k requests per second&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;jobs&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;Job&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusAccepted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ListenAndServe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;":8080"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  production‑grade fixes
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;bulkhead with semaphore&lt;/strong&gt; – limit concurrent workers to a sane number (e.g., &lt;code&gt;runtime.GOMAXPROCS()*2&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;bounded channel or work‑queue&lt;/strong&gt; – use a buffered channel and drop or back‑pressure when the buffer is full.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;context‑aware workers&lt;/strong&gt; – pass &lt;code&gt;r.Context()&lt;/code&gt; downstream; abort processing when the client disconnects.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;metrics &amp;amp; alerts&lt;/strong&gt; – expose &lt;code&gt;goroutine&lt;/code&gt;, &lt;code&gt;heap&lt;/code&gt;, and queue length via Prometheus; set alerts on 80% memory usage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;chaos testing&lt;/strong&gt; – inject artificial latency on the upstream API and verify that the pool respects the limits without OOM.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  trade‑offs to consider
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;throughput vs latency&lt;/strong&gt; – a tighter semaphore reduces peak throughput but guarantees latency bounds and prevents node eviction.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;queue size&lt;/strong&gt; – a small buffer protects the node but may increase request rejection rates; choose a size based on expected burst and acceptable 503 rate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;cost of retries&lt;/strong&gt; – if you drop jobs, ensure idempotent downstream processing or a durable retry store (e.g., Kafka) to avoid data loss.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;observability overhead&lt;/strong&gt; – adding per‑request tracing adds CPU, but without it you cannot see the back‑pressure chain.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  conclusion
&lt;/h3&gt;

&lt;p&gt;unbounded worker pools are a classic hidden failure mode that surfaces only under load spikes. limiting concurrency, propagating cancellation, and instrumenting the queue are non‑negotiable in production. how does your stack enforce bulkhead isolation for background processing, and what metrics do you rely on to detect a runaway goroutine storm?&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>backend</category>
      <category>go</category>
      <category>performance</category>
    </item>
  </channel>
</rss>
