<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Sam Ortega</title><description>Platform engineer working on Kubernetes, GPUs, and the boring parts of cloud-native infrastructure. Build logs and things learned along the way.</description><link>https://scenery-theme.vercel.app</link><language>en-us</language><item><title>Writing with MDX: callouts, tabs, and snippets</title><link>https://scenery-theme.vercel.app/posts/writing-with-mdx</link><guid isPermaLink="true">https://scenery-theme.vercel.app/posts/writing-with-mdx</guid><description>A tour of the authoring components this theme ships with — how to use them, and when plain Markdown is the better choice.</description><pubDate>Sun, 05 Jul 2026 00:00:00 GMT</pubDate><category>writing</category><category>this-site</category><author>hello@example.com (Sam Ortega)</author></item><item><title>Sharing a GPU without regret</title><link>https://scenery-theme.vercel.app/posts/gpus-on-kubernetes/part-3-sharing-a-gpu-without-regret</link><guid isPermaLink="true">https://scenery-theme.vercel.app/posts/gpus-on-kubernetes/part-3-sharing-a-gpu-without-regret</guid><description>Time-slicing, MIG and MPS solve the same problem three different ways. Here is how to pick, and the failure mode of each.</description><pubDate>Fri, 05 Jun 2026 00:00:00 GMT</pubDate><category>kubernetes</category><category>gpu</category><category>scheduling</category><author>hello@example.com (Sam Ortega)</author></item><item><title>How the scheduler sees a GPU</title><link>https://scenery-theme.vercel.app/posts/gpus-on-kubernetes/part-2-how-the-scheduler-sees-a-gpu</link><guid isPermaLink="true">https://scenery-theme.vercel.app/posts/gpus-on-kubernetes/part-2-how-the-scheduler-sees-a-gpu</guid><description>Device plugins, extended resources, and why a GPU request is an integer with no idea what it is counting.</description><pubDate>Wed, 03 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Kubernetes knows about two resources: CPU and memory. Everything else — GPUs, FPGAs, RDMA NICs — is an &lt;strong&gt;extended resource&lt;/strong&gt;, which is a polite way of saying “an integer the kubelet has been told about”.&lt;/p&gt;
&lt;p&gt;This post is about how that integer gets there, what the scheduler does with it, and where it falls short.&lt;/p&gt;
&lt;h2 id=&quot;the-device-plugin-handshake&quot;&gt;The device plugin handshake&lt;/h2&gt;
&lt;p&gt;A GPU node runs a small daemon, the device plugin, that registers with the kubelet over a Unix socket and advertises a list of device IDs. The kubelet turns that list into a capacity on the node:&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;link rel=&quot;stylesheet&quot; href=&quot;/_astro/ec.js1sw.css&quot;&gt;&lt;script type=&quot;module&quot; src=&quot;/_astro/ec.0vx5m.js&quot;&gt;&lt;/script&gt;&lt;figure class=&quot;frame is-terminal has-title&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;span class=&quot;title&quot;&gt;kubectl describe node gpu-a100-04&lt;/span&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#FFA657&quot;&gt;Capacity:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FFA657&quot;&gt;cpu:&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;                &lt;/span&gt;&lt;span style=&quot;--0:#79C0FF&quot;&gt;64&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FFA657&quot;&gt;memory:&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;             &lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF&quot;&gt;515Gi&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FFA657&quot;&gt;nvidia.com/gpu:&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;     &lt;/span&gt;&lt;span style=&quot;--0:#79C0FF&quot;&gt;4&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#FFA657&quot;&gt;Allocatable:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FFA657&quot;&gt;cpu:&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;                &lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF&quot;&gt;63500m&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FFA657&quot;&gt;memory:&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;             &lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF&quot;&gt;500Gi&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FFA657&quot;&gt;nvidia.com/gpu:&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;     &lt;/span&gt;&lt;span style=&quot;--0:#79C0FF&quot;&gt;4&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;Capacity:  cpu:                64  memory:             515Gi  nvidia.com/gpu:     4Allocatable:  cpu:                63500m  memory:             500Gi  nvidia.com/gpu:     4&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;From here on the scheduler treats &lt;code&gt;nvidia.com/gpu&lt;/code&gt; exactly like memory: a number to subtract from. A pod asks for some, the node has enough or it does not.&lt;/p&gt;
&lt;h2 id=&quot;requesting-a-gpu&quot;&gt;Requesting a GPU&lt;/h2&gt;
&lt;p&gt;The request lives in the container spec alongside CPU and memory. Note the highlighted lines — GPUs must be requested as &lt;strong&gt;limits&lt;/strong&gt;, and requests and limits must be equal; you cannot overcommit an extended resource.&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame has-title&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;span class=&quot;title&quot;&gt;inference-deployment.yaml&lt;/span&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;yaml&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#7EE787&quot;&gt;apiVersion&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF&quot;&gt;apps/v1&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#7EE787&quot;&gt;kind&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF&quot;&gt;Deployment&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#7EE787&quot;&gt;metadata&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#7EE787&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF&quot;&gt;llama-3-8b&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#7EE787&quot;&gt;spec&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#7EE787&quot;&gt;replicas&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--0:#79C0FF&quot;&gt;2&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#7EE787&quot;&gt;template&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#7EE787&quot;&gt;spec&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--0:#7EE787&quot;&gt;containers&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;- &lt;/span&gt;&lt;span style=&quot;--0:#7EE787&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF&quot;&gt;vllm&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;          &lt;/span&gt;&lt;span style=&quot;--0:#7EE787&quot;&gt;image&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF&quot;&gt;ghcr.io/example/vllm:0.9.1&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;          &lt;/span&gt;&lt;span style=&quot;--0:#7EE787&quot;&gt;resources&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line highlight mark&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;            &lt;/span&gt;&lt;span style=&quot;--0:#7EE787&quot;&gt;limits&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line highlight mark&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;              &lt;/span&gt;&lt;span style=&quot;--0:#7EE787&quot;&gt;nvidia.com/gpu&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--0:#79C0FF&quot;&gt;1&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line highlight mark&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;              &lt;/span&gt;&lt;span style=&quot;--0:#7EE787&quot;&gt;memory&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF&quot;&gt;48Gi&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;            &lt;/span&gt;&lt;span style=&quot;--0:#7EE787&quot;&gt;requests&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;              &lt;/span&gt;&lt;span style=&quot;--0:#7EE787&quot;&gt;cpu&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF&quot;&gt;&apos;8&apos;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;              &lt;/span&gt;&lt;span style=&quot;--0:#7EE787&quot;&gt;memory&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF&quot;&gt;48Gi&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--0:#7EE787&quot;&gt;nodeSelector&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;--0:#7EE787&quot;&gt;nvidia.com/gpu.product&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF&quot;&gt;NVIDIA-A100-SXM4-80GB&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;apiVersion: apps/v1kind: Deploymentmetadata:  name: llama-3-8bspec:  replicas: 2  template:    spec:      containers:        - name: vllm          image: ghcr.io/example/vllm:0.9.1          resources:            limits:              nvidia.com/gpu: 1              memory: 48Gi            requests:              cpu: &amp;#x27;8&amp;#x27;              memory: 48Gi      nodeSelector:        nvidia.com/gpu.product: NVIDIA-A100-SXM4-80GB&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;When the pod is scheduled, the device plugin’s &lt;code&gt;Allocate&lt;/code&gt; call hands back environment variables and device mounts (&lt;code&gt;/dev/nvidia0&lt;/code&gt;, the driver libraries) so the container sees exactly one card.&lt;/p&gt;
&lt;h2 id=&quot;what-the-integer-does-not-know&quot;&gt;What the integer does not know&lt;/h2&gt;
&lt;p&gt;Here is the part that bit us. The scheduler counts devices; it knows nothing about them.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;The scheduler knows&lt;/th&gt;
&lt;th&gt;The scheduler does not know&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;how many GPUs&lt;/td&gt;
&lt;td&gt;which model (A100 vs L4) unless you label nodes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;how many are free&lt;/td&gt;
&lt;td&gt;how much GPU memory is free on a shared card&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;node affinity rules&lt;/td&gt;
&lt;td&gt;whether two GPUs share an NVLink or cross the PCIe&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;pod priority&lt;/td&gt;
&lt;td&gt;that your job holds 8 GPUs across 4 nodes as a unit&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Each gap has a workaround, and each workaround is a project:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Model selection&lt;/strong&gt; — labels from the GPU feature discovery daemon and a &lt;code&gt;nodeSelector&lt;/code&gt; (see the YAML above).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Topology&lt;/strong&gt; — the &lt;code&gt;topology-manager&lt;/code&gt; policy on the kubelet plus a scheduler plugin that understands NVLink domains.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Gang scheduling&lt;/strong&gt; — a batch scheduler (Kueue, Volcano) that admits all-or-nothing, so a 4-node training job never sits half-scheduled holding GPUs hostage.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fractional GPUs&lt;/strong&gt; — the subject of &lt;a href=&quot;/posts/gpus-on-kubernetes/part-3-sharing-a-gpu-without-regret&quot;&gt;part 3&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;a-quick-sanity-check&quot;&gt;A quick sanity check&lt;/h2&gt;
&lt;p&gt;Before trusting any dashboard, I run this on a node and compare it with what the scheduler thinks:&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame is-terminal&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;span class=&quot;title&quot;&gt;&lt;/span&gt;&lt;span class=&quot;sr-only&quot;&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#FFA657&quot;&gt;nvidia-smi&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#79C0FF&quot;&gt;--query-gpu=index,utilization.gpu,memory.used,memory.total&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#79C0FF&quot;&gt;--format=csv&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;nvidia-smi --query-gpu=index,utilization.gpu,memory.used,memory.total --format=csv&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;If the scheduler says the node is fully allocated and &lt;code&gt;nvidia-smi&lt;/code&gt; says utilisation is 11 %, the platform is paying for four GPUs and using half of one. That gap — allocated versus busy — is the single most useful number a GPU platform can put on a graph, and the integer the scheduler works with cannot see it.&lt;/p&gt;
</content:encoded><category>kubernetes</category><category>gpu</category><category>scheduling</category><author>hello@example.com (Sam Ortega)</author></item><item><title>Why GPUs change the bottleneck</title><link>https://scenery-theme.vercel.app/posts/gpus-on-kubernetes/part-1-why-gpus-change-the-bottleneck</link><guid isPermaLink="true">https://scenery-theme.vercel.app/posts/gpus-on-kubernetes/part-1-why-gpus-change-the-bottleneck</guid><description>AI workloads look like microservices from the outside — containers, APIs, autoscaling. The difference is where the scarce resource sits.</description><pubDate>Mon, 01 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I have spent most of my working life on Kubernetes, observability and the plumbing between them. When the first GPU node pool landed in our cluster I asked the obvious question:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why can’t AI workloads run like normal microservices?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;At first glance they are the same shape. Both are packaged into containers. Both run on Kubernetes. Both expose an API. Both scale on demand. So why is there an entire ecosystem growing around “AI infrastructure”?&lt;/p&gt;
&lt;p&gt;After a few weeks of breaking things, the answer turned out to be simple: it is all about where the bottleneck lives.&lt;/p&gt;
&lt;h2 id=&quot;a-traditional-request&quot;&gt;A traditional request&lt;/h2&gt;
&lt;p&gt;Most services follow a familiar pattern.&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;link rel=&quot;stylesheet&quot; href=&quot;/_astro/ec.js1sw.css&quot;&gt;&lt;script type=&quot;module&quot; src=&quot;/_astro/ec.0vx5m.js&quot;&gt;&lt;/script&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#e6edf3&quot;&gt;user → api → database&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;user → api → database&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;A request reaches an API pod, some business logic runs, a row is fetched from a database, and a response goes back. The things we watch are CPU, memory, database latency, network round trips, and how many replicas we need to keep p99 under the SLO.&lt;/p&gt;
&lt;p&gt;Platform teams have two decades of tooling for this. Horizontal scaling is cheap because pods are cheap. An idle CPU for a few minutes is nothing to worry about.&lt;/p&gt;
&lt;h2 id=&quot;an-inference-request&quot;&gt;An inference request&lt;/h2&gt;
&lt;p&gt;An inference request looks almost the same on the wire, and completely different underneath.&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#e6edf3&quot;&gt;user → inference server → gpu → model weights&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;user → inference server → gpu → model weights&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Instead of querying a database, the process has to hold a multi-gigabyte model in GPU memory and push every token through it. The bottleneck stops being I/O and becomes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;GPU memory (does the model even fit?)&lt;/li&gt;
&lt;li&gt;GPU utilisation (is the card actually busy, or waiting on the CPU?)&lt;/li&gt;
&lt;li&gt;model loading time (minutes, not milliseconds)&lt;/li&gt;
&lt;li&gt;memory bandwidth between host and device&lt;/li&gt;
&lt;li&gt;network throughput between nodes for anything multi-GPU&lt;/li&gt;
&lt;/ul&gt;
&lt;figure class=&quot;post-figure&quot;&gt;&lt;img src=&quot;/images/posts/request-path.svg&quot; alt=&quot;The scarce resource moves from the database to the accelerator.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;&lt;figcaption class=&quot;post-figcaption&quot;&gt;The scarce resource moves from the database to the accelerator.&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;The challenge shifts from serving &lt;em&gt;data&lt;/em&gt; efficiently to serving &lt;em&gt;compute&lt;/em&gt; efficiently.&lt;/p&gt;
&lt;h2 id=&quot;gpus-are-expensive-so-idle-is-a-bug&quot;&gt;GPUs are expensive, so idle is a bug&lt;/h2&gt;
&lt;p&gt;One insight reframed everything for me.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;In traditional infrastructure, an idle CPU is an acceptable cost of headroom. On a GPU node, idle is a bug you can measure in dollars per hour.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;A single accelerator can cost more per hour than the rest of the node combined. That single fact changes what “good” looks like for a platform:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Bin-packing matters more than spreading.&lt;/strong&gt; The default scheduler likes to spread pods for resilience. With GPUs you often want the opposite: pack the cards you have before you pay for another.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cold starts are the enemy.&lt;/strong&gt; Pulling a 15 GB image and loading weights takes minutes. Scale-to-zero, the darling of serverless, quietly becomes scale-to-a-cold-start.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Utilisation is the KPI.&lt;/strong&gt; Not request rate, not replica count. If the GPU sits at 20 % busy, the platform is wasting most of what it pays for.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;what-this-series-covers&quot;&gt;What this series covers&lt;/h2&gt;
&lt;p&gt;This is the first of three posts written while building a GPU platform for a few dozen product teams. The next two get concrete:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Part 2&lt;/strong&gt; looks at how Kubernetes actually sees a GPU — device plugins, extended resources, and why &lt;code&gt;nvidia.com/gpu: 1&lt;/code&gt; is a much blunter instrument than it looks.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Part 3&lt;/strong&gt; is about sharing a GPU between workloads without regretting it: time-slicing, MIG and MPS, and how to choose.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you only take one thing from this post: AI workloads are not special because of the model. They are special because the scarce resource moved, and the whole platform has to move with it.&lt;/p&gt;
</content:encoded><category>kubernetes</category><category>gpu</category><author>hello@example.com (Sam Ortega)</author></item><item><title>A Kubernetes operator in a weekend</title><link>https://scenery-theme.vercel.app/posts/a-kubernetes-operator-in-a-weekend</link><guid isPermaLink="true">https://scenery-theme.vercel.app/posts/a-kubernetes-operator-in-a-weekend</guid><description>What it actually takes to go from &quot;I wish the cluster did this for me&quot; to a controller that does — with the parts of the tutorials that were wrong.</description><pubDate>Sat, 14 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Every platform team has a script that someone runs by hand every Tuesday. Ours checked Prometheus during rollouts and pasted the graph into Slack. This is the story of turning it into a controller, and the three things the tutorials skipped.&lt;/p&gt;
&lt;h2 id=&quot;start-from-the-resource-not-the-code&quot;&gt;Start from the resource, not the code&lt;/h2&gt;
&lt;p&gt;The temptation is to open the SDK scaffold and start typing. Resist it. The first hour should be spent on a YAML file that does not exist yet — the resource a user would write if the feature already worked:&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;link rel=&quot;stylesheet&quot; href=&quot;/_astro/ec.js1sw.css&quot;&gt;&lt;script type=&quot;module&quot; src=&quot;/_astro/ec.0vx5m.js&quot;&gt;&lt;/script&gt;&lt;figure class=&quot;frame has-title&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;span class=&quot;title&quot;&gt;rollout-analysis.yaml&lt;/span&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;yaml&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#7EE787&quot;&gt;apiVersion&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF&quot;&gt;sentinel.example.com/v1alpha1&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#7EE787&quot;&gt;kind&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF&quot;&gt;RolloutAnalysis&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#7EE787&quot;&gt;metadata&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#7EE787&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF&quot;&gt;checkout&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#7EE787&quot;&gt;spec&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#7EE787&quot;&gt;target&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#7EE787&quot;&gt;kind&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF&quot;&gt;Deployment&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#7EE787&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF&quot;&gt;checkout&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#7EE787&quot;&gt;metrics&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;- &lt;/span&gt;&lt;span style=&quot;--0:#7EE787&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF&quot;&gt;p99-latency&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--0:#7EE787&quot;&gt;query&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF&quot;&gt;histogram_quantile(0.99, rate(http_request_duration_seconds_bucket{app=&quot;checkout&quot;}[5m]))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--0:#7EE787&quot;&gt;maxValue&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--0:#79C0FF&quot;&gt;0.25&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#7EE787&quot;&gt;interval&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF&quot;&gt;30s&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#7EE787&quot;&gt;failureLimit&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--0:#79C0FF&quot;&gt;3&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;apiVersion: sentinel.example.com/v1alpha1kind: RolloutAnalysismetadata:  name: checkoutspec:  target:    kind: Deployment    name: checkout  metrics:    - name: p99-latency      query: histogram_quantile(0.99, rate(http_request_duration_seconds_bucket{app=&amp;quot;checkout&amp;quot;}[5m]))      maxValue: 0.25  interval: 30s  failureLimit: 3&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;If you cannot write this file, you do not know what you are building. Once you can, the controller almost writes itself: watch these, query that, compare, act.&lt;/p&gt;
&lt;h2 id=&quot;the-reconcile-loop-is-the-whole-program&quot;&gt;The reconcile loop is the whole program&lt;/h2&gt;
&lt;p&gt;Everything an operator does lives in one function that gets called with a name and returns “done” or “try again later”. The scaffold hides that behind a lot of generated code; the part you own is this:&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame has-title&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;span class=&quot;title&quot;&gt;controllers/rolloutanalysis_controller.go&lt;/span&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;go&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;gutter&quot;&gt;&lt;div class=&quot;ln&quot; aria-hidden=&quot;true&quot;&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#FF7B72&quot;&gt;func&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;--0:#FFA657&quot;&gt;r &lt;/span&gt;&lt;span style=&quot;--0:#FF7B72&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;--0:#FFA657&quot;&gt;Reconciler&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;--0:#D2A8FF&quot;&gt;Reconcile&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--0:#FFA657&quot;&gt;ctx&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FFA657&quot;&gt;context&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#FFA657&quot;&gt;Context&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--0:#FFA657&quot;&gt;req&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FFA657&quot;&gt;ctrl&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#FFA657&quot;&gt;Request&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;) (&lt;/span&gt;&lt;span style=&quot;--0:#FFA657&quot;&gt;ctrl&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#FFA657&quot;&gt;Result&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--0:#FF7B72&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;gutter&quot;&gt;&lt;div class=&quot;ln&quot; aria-hidden=&quot;true&quot;&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FF7B72&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt; analysis &lt;/span&gt;&lt;span style=&quot;--0:#FFA657&quot;&gt;sentinelv1alpha1&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#FFA657&quot;&gt;RolloutAnalysis&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;gutter&quot;&gt;&lt;div class=&quot;ln&quot; aria-hidden=&quot;true&quot;&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FF7B72&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt; err &lt;/span&gt;&lt;span style=&quot;--0:#FF7B72&quot;&gt;:=&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt; r.&lt;/span&gt;&lt;span style=&quot;--0:#D2A8FF&quot;&gt;Get&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;(ctx, req.NamespacedName, &lt;/span&gt;&lt;span style=&quot;--0:#FF7B72&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;analysis); err &lt;/span&gt;&lt;span style=&quot;--0:#FF7B72&quot;&gt;!=&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#79C0FF&quot;&gt;nil&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;gutter&quot;&gt;&lt;div class=&quot;ln&quot; aria-hidden=&quot;true&quot;&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#FF7B72&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FFA657&quot;&gt;ctrl&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#FFA657&quot;&gt;Result&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;{}, client.&lt;/span&gt;&lt;span style=&quot;--0:#D2A8FF&quot;&gt;IgnoreNotFound&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;(err)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;gutter&quot;&gt;&lt;div class=&quot;ln&quot; aria-hidden=&quot;true&quot;&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;gutter&quot;&gt;&lt;div class=&quot;ln&quot; aria-hidden=&quot;true&quot;&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;gutter&quot;&gt;&lt;div class=&quot;ln&quot; aria-hidden=&quot;true&quot;&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;verdict, err &lt;/span&gt;&lt;span style=&quot;--0:#FF7B72&quot;&gt;:=&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt; r.&lt;/span&gt;&lt;span style=&quot;--0:#D2A8FF&quot;&gt;evaluate&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;(ctx, analysis.Spec.Metrics)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;gutter&quot;&gt;&lt;div class=&quot;ln&quot; aria-hidden=&quot;true&quot;&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FF7B72&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt; err &lt;/span&gt;&lt;span style=&quot;--0:#FF7B72&quot;&gt;!=&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#79C0FF&quot;&gt;nil&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;gutter&quot;&gt;&lt;div class=&quot;ln&quot; aria-hidden=&quot;true&quot;&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#FF7B72&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FFA657&quot;&gt;ctrl&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#FFA657&quot;&gt;Result&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;{RequeueAfter: analysis.Spec.Interval.Duration}, err&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;gutter&quot;&gt;&lt;div class=&quot;ln&quot; aria-hidden=&quot;true&quot;&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;gutter&quot;&gt;&lt;div class=&quot;ln&quot; aria-hidden=&quot;true&quot;&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;gutter&quot;&gt;&lt;div class=&quot;ln&quot; aria-hidden=&quot;true&quot;&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;analysis.Status.LastVerdict &lt;/span&gt;&lt;span style=&quot;--0:#FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt; verdict&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;gutter&quot;&gt;&lt;div class=&quot;ln&quot; aria-hidden=&quot;true&quot;&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;analysis.Status.Failures &lt;/span&gt;&lt;span style=&quot;--0:#FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#D2A8FF&quot;&gt;countFailures&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;(analysis.Status, verdict)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line highlight mark&quot;&gt;&lt;div class=&quot;gutter&quot;&gt;&lt;div class=&quot;ln&quot; aria-hidden=&quot;true&quot;&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#ff7f76&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt; analysis.Status.Failures &lt;/span&gt;&lt;span style=&quot;--0:#ff7f76&quot;&gt;&amp;gt;=&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt; analysis.Spec.FailureLimit {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line highlight mark&quot;&gt;&lt;div class=&quot;gutter&quot;&gt;&lt;div class=&quot;ln&quot; aria-hidden=&quot;true&quot;&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#ff7f76&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt; err &lt;/span&gt;&lt;span style=&quot;--0:#ff7f76&quot;&gt;:=&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt; r.&lt;/span&gt;&lt;span style=&quot;--0:#D2A8FF&quot;&gt;pauseRollout&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;(ctx, analysis.Spec.Target); err &lt;/span&gt;&lt;span style=&quot;--0:#ff7f76&quot;&gt;!=&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#79C0FF&quot;&gt;nil&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line highlight mark&quot;&gt;&lt;div class=&quot;gutter&quot;&gt;&lt;div class=&quot;ln&quot; aria-hidden=&quot;true&quot;&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--0:#ff7f76&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FFA657&quot;&gt;ctrl&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#FFA657&quot;&gt;Result&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;{}, err&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line highlight mark&quot;&gt;&lt;div class=&quot;gutter&quot;&gt;&lt;div class=&quot;ln&quot; aria-hidden=&quot;true&quot;&gt;17&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line highlight mark&quot;&gt;&lt;div class=&quot;gutter&quot;&gt;&lt;div class=&quot;ln&quot; aria-hidden=&quot;true&quot;&gt;18&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;r.Recorder.&lt;/span&gt;&lt;span style=&quot;--0:#D2A8FF&quot;&gt;Event&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--0:#ff7f76&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;analysis, &lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF&quot;&gt;&quot;Warning&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF&quot;&gt;&quot;RolloutPaused&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF&quot;&gt;&quot;metric regression detected&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;gutter&quot;&gt;&lt;div class=&quot;ln&quot; aria-hidden=&quot;true&quot;&gt;19&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;gutter&quot;&gt;&lt;div class=&quot;ln&quot; aria-hidden=&quot;true&quot;&gt;20&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;gutter&quot;&gt;&lt;div class=&quot;ln&quot; aria-hidden=&quot;true&quot;&gt;21&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FF7B72&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt; err &lt;/span&gt;&lt;span style=&quot;--0:#FF7B72&quot;&gt;:=&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt; r.&lt;/span&gt;&lt;span style=&quot;--0:#D2A8FF&quot;&gt;Status&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;().&lt;/span&gt;&lt;span style=&quot;--0:#D2A8FF&quot;&gt;Update&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;(ctx, &lt;/span&gt;&lt;span style=&quot;--0:#FF7B72&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;analysis); err &lt;/span&gt;&lt;span style=&quot;--0:#FF7B72&quot;&gt;!=&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#79C0FF&quot;&gt;nil&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;gutter&quot;&gt;&lt;div class=&quot;ln&quot; aria-hidden=&quot;true&quot;&gt;22&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#FF7B72&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FFA657&quot;&gt;ctrl&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#FFA657&quot;&gt;Result&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;{}, err&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;gutter&quot;&gt;&lt;div class=&quot;ln&quot; aria-hidden=&quot;true&quot;&gt;23&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;gutter&quot;&gt;&lt;div class=&quot;ln&quot; aria-hidden=&quot;true&quot;&gt;24&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FF7B72&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FFA657&quot;&gt;ctrl&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#FFA657&quot;&gt;Result&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;{RequeueAfter: analysis.Spec.Interval.Duration}, &lt;/span&gt;&lt;span style=&quot;--0:#79C0FF&quot;&gt;nil&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;gutter&quot;&gt;&lt;div class=&quot;ln&quot; aria-hidden=&quot;true&quot;&gt;25&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {  var analysis sentinelv1alpha1.RolloutAnalysis  if err := r.Get(ctx, req.NamespacedName, &amp;amp;analysis); err != nil {    return ctrl.Result{}, client.IgnoreNotFound(err)  }  verdict, err := r.evaluate(ctx, analysis.Spec.Metrics)  if err != nil {    return ctrl.Result{RequeueAfter: analysis.Spec.Interval.Duration}, err  }  analysis.Status.LastVerdict = verdict  analysis.Status.Failures = countFailures(analysis.Status, verdict)  if analysis.Status.Failures &gt;= analysis.Spec.FailureLimit {    if err := r.pauseRollout(ctx, analysis.Spec.Target); err != nil {      return ctrl.Result{}, err    }    r.Recorder.Event(&amp;amp;analysis, &amp;quot;Warning&amp;quot;, &amp;quot;RolloutPaused&amp;quot;, &amp;quot;metric regression detected&amp;quot;)  }  if err := r.Status().Update(ctx, &amp;amp;analysis); err != nil {    return ctrl.Result{}, err  }  return ctrl.Result{RequeueAfter: analysis.Spec.Interval.Duration}, nil}&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The highlighted block is the only part that &lt;em&gt;does&lt;/em&gt; anything. Everything else is fetching state and writing it back. That ratio is normal; if your reconciler is mostly action, it is probably not idempotent.&lt;/p&gt;
&lt;h2 id=&quot;three-things-the-tutorials-skipped&quot;&gt;Three things the tutorials skipped&lt;/h2&gt;
&lt;h3 id=&quot;1-status-is-an-api-not-a-log&quot;&gt;1. Status is an API, not a log&lt;/h3&gt;
&lt;p&gt;Early versions wrote a human-readable message into &lt;code&gt;status&lt;/code&gt;. Users then wrote scripts that grep’d that message, and the first wording change broke them. &lt;code&gt;Status&lt;/code&gt; needs the same discipline as &lt;code&gt;Spec&lt;/code&gt;: typed fields, conditions with stable reasons, and a promise not to change them casually.&lt;/p&gt;
&lt;h3 id=&quot;2-requeue-is-not-a-retry&quot;&gt;2. Requeue is not a retry&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;RequeueAfter&lt;/code&gt; says “look at this again in 30 s whether or not anything changed”. Returning an error says “something went wrong, back off exponentially”. Mixing them up gives you either a controller that hammers Prometheus during an outage or one that never notices the outage ended.&lt;/p&gt;
&lt;h3 id=&quot;3-test-with-envtest-then-with-a-real-cluster-then-with-a-bad-one&quot;&gt;3. Test with envtest, then with a real cluster, then with a bad one&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;envtest&lt;/code&gt; spins up a real API server without nodes and catches every schema and RBAC mistake in seconds. What it does not catch is the behaviour when Prometheus returns a 502, when the target deployment is deleted mid-analysis, or when two analyses point at one deployment. Those need a kind cluster and deliberate sabotage:&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame is-terminal&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;span class=&quot;title&quot;&gt;&lt;/span&gt;&lt;span class=&quot;sr-only&quot;&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#FFA657&quot;&gt;kubectl&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#79C0FF&quot;&gt;-n&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF&quot;&gt;monitoring&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF&quot;&gt;scale&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF&quot;&gt;deploy/prometheus&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#79C0FF&quot;&gt;--replicas=0&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#FFA657&quot;&gt;kubectl&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF&quot;&gt;rolloutanalysis&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#A5D6FF&quot;&gt;checkout&lt;/span&gt;&lt;span style=&quot;--0:#E6EDF3&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#79C0FF&quot;&gt;-w&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;kubectl -n monitoring scale deploy/prometheus --replicas=0kubectl get rolloutanalysis checkout -w&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Watching the status conditions flip to &lt;code&gt;MetricsUnavailable&lt;/code&gt; and back — without pausing anything — was the moment the thing felt finished.&lt;/p&gt;
&lt;h2 id=&quot;was-it-a-weekend&quot;&gt;Was it a weekend?&lt;/h2&gt;
&lt;p&gt;The first working version, yes. The version that could be trusted with production rollouts took another three weekends and one incident. That version became &lt;a href=&quot;/projects/rollout-sentinel&quot;&gt;Rollout Sentinel&lt;/a&gt;, and the Tuesday script has been deleted.&lt;/p&gt;
</content:encoded><category>go</category><category>kubernetes</category><category>operators</category><author>hello@example.com (Sam Ortega)</author></item><item><title>Talk: tail latency and hedged requests</title><link>https://example.com/slides/hedged-requests</link><guid isPermaLink="true">https://example.com/slides/hedged-requests</guid><description>Slides from a meetup talk on cutting p99 latency in a metrics store by sending a second request when the first one is slow.</description><pubDate>Wed, 08 Oct 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This entry links to the slides directly; no page is rendered for it.&lt;/p&gt;
</content:encoded><category>talks</category><category>latency</category><author>hello@example.com (Sam Ortega)</author></item><item><title>What a year on call taught me</title><link>https://scenery-theme.vercel.app/posts/what-a-year-on-call-taught-me</link><guid isPermaLink="true">https://scenery-theme.vercel.app/posts/what-a-year-on-call-taught-me</guid><description>Twelve months, forty-one pages, three real incidents. The pager taught me more about systems than any book — mostly about people.</description><pubDate>Mon, 20 Jan 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I joined the on-call rotation in January with a fresh laptop and a strong opinion that alerts should be fixed at the source. A year later the opinion survives, but it has company.&lt;/p&gt;
&lt;h2 id=&quot;the-pager-is-a-product&quot;&gt;The pager is a product&lt;/h2&gt;
&lt;p&gt;The first month I treated every page as a fact about the system. By March I understood that every page is a fact about &lt;em&gt;a person’s decision&lt;/em&gt;, made months ago, about what they thought would matter. Half of ours were wrong, and nobody had been given permission to delete them.&lt;/p&gt;
&lt;p&gt;So we gave permission. One rule: an alert that pages must have a runbook that says what to do, and if the answer is “look and see”, it is not a page, it is a dashboard.&lt;/p&gt;
&lt;p&gt;Forty-one pages in a year, down from an estimated three hundred the year before. The three that mattered still paged.&lt;/p&gt;
&lt;h2 id=&quot;things-i-believed-and-no-longer-do&quot;&gt;Things I believed and no longer do&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;“Alert on symptoms, not causes.”&lt;/strong&gt; Half true. Alert on symptoms &lt;em&gt;to page&lt;/em&gt;; alert on causes &lt;em&gt;to annotate&lt;/em&gt;, so the person who is paged sees the likely cause next to the symptom instead of hunting for it at 3 a.m.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;“Automate the response.”&lt;/strong&gt; Only after the third time. The first time you do not understand it. The second time you understand it but you are wrong. The third time, maybe.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;“On-call should be a burden shared equally.”&lt;/strong&gt; Shared, yes. Equally, no. The person who built the thing should carry more of it for the first quarter. Nothing improves a design like being woken up by it.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;the-three-real-incidents&quot;&gt;The three real incidents&lt;/h2&gt;
&lt;p&gt;I will not relive them here, but each one left a rule behind.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;The certificate.&lt;/strong&gt; &lt;em&gt;Every&lt;/em&gt; expiring thing gets a calendar reminder thirty days out and an alert seven days out. Both. The alert alone was silenced during an unrelated incident and nobody unsilenced it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The autoscaler.&lt;/strong&gt; A scale-down policy that was correct for the traffic pattern of the month it was written. Now every scaling policy has a comment with the date and the assumption, so the next person knows what it was solving for.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The upgrade.&lt;/strong&gt; We upgraded the control plane on a Friday because “it’s just a patch release”. It is never just a patch release. Upgrades go out Tuesday to Thursday, before noon, with someone watching who did not do the upgrade.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;blockquote&gt;
&lt;p&gt;The system is not the thing in the cluster. The system is the cluster plus everyone who touches it, including the version of you that is asleep.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;what-id-tell-january-me&quot;&gt;What I’d tell January me&lt;/h2&gt;
&lt;p&gt;Learn the runbooks before you need them. Fix one alert every week, not all of them at once. Write the incident review the same day, while it hurts. And keep a file of the pages that were &lt;em&gt;right&lt;/em&gt; — on the bad weeks, that file is the reason you are still doing this.&lt;/p&gt;
</content:encoded><category>on-call</category><category>sre</category><author>hello@example.com (Sam Ortega)</author></item></channel></rss>