<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Goutham K</title><link>https://gouthamhusky.github.io/blogsite/</link><description>Recent content on Goutham K</description><generator>Hugo</generator><language>en-us</language><lastBuildDate>Mon, 24 Aug 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://gouthamhusky.github.io/blogsite/index.xml" rel="self" type="application/rss+xml"/><item><title>TIL: the ingress-nginx 404 page is just another Service</title><link>https://gouthamhusky.github.io/blogsite/til/nginx-ingress-default-backend/</link><pubDate>Mon, 24 Aug 2026 00:00:00 +0000</pubDate><guid>https://gouthamhusky.github.io/blogsite/til/nginx-ingress-default-backend/</guid><description>&lt;p&gt;I&amp;rsquo;d always assumed a 404 from ingress-nginx was nginx doing something
built-in and untouchable. It isn&amp;rsquo;t — it&amp;rsquo;s routing you to an actual
Kubernetes Service, and you can point it at your own.&lt;/p&gt;
&lt;p&gt;Every Ingress rule you write matches on a host and a set of paths. If a
request comes in that doesn&amp;rsquo;t match any rule on any Ingress in the
cluster, ingress-nginx doesn&amp;rsquo;t invent a response — it forwards the
request to whatever Service is configured as its &lt;strong&gt;default backend&lt;/strong&gt;,
and that Service&amp;rsquo;s response is what the client sees. Out of the box,
that&amp;rsquo;s the small &lt;code&gt;defaultbackend-amd64&lt;/code&gt; pod the chart installs for you,
which does nothing but serve a plain 404.&lt;/p&gt;</description></item><item><title>TIL: matchPolicy: Equivalent means your webhook still runs on versions you never listed</title><link>https://gouthamhusky.github.io/blogsite/til/matchpolicy-equivalent/</link><pubDate>Wed, 19 Aug 2026 00:00:00 +0000</pubDate><guid>https://gouthamhusky.github.io/blogsite/til/matchpolicy-equivalent/</guid><description>&lt;p&gt;I assumed a webhook registered against one API version simply didn&amp;rsquo;t see
requests on any other version. Turns out that&amp;rsquo;s only true if you set
&lt;code&gt;matchPolicy: Exact&lt;/code&gt; — and that&amp;rsquo;s not the default.&lt;/p&gt;
&lt;p&gt;A &lt;code&gt;MutatingWebhookConfiguration&lt;/code&gt; declares &lt;code&gt;rules&lt;/code&gt; for the group/version/
resource it cares about:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f7f7f7;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#0550ae"&gt;rules&lt;/span&gt;&lt;span style="color:#1f2328"&gt;:&lt;/span&gt;&lt;span style="color:#fff"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#fff"&gt; &lt;/span&gt;- &lt;span style="color:#0550ae"&gt;apiGroups&lt;/span&gt;&lt;span style="color:#1f2328"&gt;:&lt;/span&gt;&lt;span style="color:#fff"&gt; &lt;/span&gt;&lt;span style="color:#1f2328"&gt;[&lt;/span&gt;&lt;span style="color:#0a3069"&gt;&amp;#34;example.io&amp;#34;&lt;/span&gt;&lt;span style="color:#1f2328"&gt;]&lt;/span&gt;&lt;span style="color:#fff"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#fff"&gt; &lt;/span&gt;&lt;span style="color:#0550ae"&gt;apiVersions&lt;/span&gt;&lt;span style="color:#1f2328"&gt;:&lt;/span&gt;&lt;span style="color:#fff"&gt; &lt;/span&gt;&lt;span style="color:#1f2328"&gt;[&lt;/span&gt;&lt;span style="color:#0a3069"&gt;&amp;#34;v1beta1&amp;#34;&lt;/span&gt;&lt;span style="color:#1f2328"&gt;]&lt;/span&gt;&lt;span style="color:#fff"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#fff"&gt; &lt;/span&gt;&lt;span style="color:#0550ae"&gt;resources&lt;/span&gt;&lt;span style="color:#1f2328"&gt;:&lt;/span&gt;&lt;span style="color:#fff"&gt; &lt;/span&gt;&lt;span style="color:#1f2328"&gt;[&lt;/span&gt;&lt;span style="color:#0a3069"&gt;&amp;#34;widgets&amp;#34;&lt;/span&gt;&lt;span style="color:#1f2328"&gt;]&lt;/span&gt;&lt;span style="color:#fff"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Fine if the resource only has one version. But plenty of resources — CRDs
especially — have several, wired together by a conversion webhook. What
happens on a version your &lt;code&gt;rules&lt;/code&gt; didn&amp;rsquo;t list is exactly what
&lt;code&gt;matchPolicy&lt;/code&gt; decides.&lt;/p&gt;</description></item><item><title>TIL: why containers need a real init process</title><link>https://gouthamhusky.github.io/blogsite/til/pid1-containers/</link><pubDate>Tue, 18 Aug 2026 00:00:00 +0000</pubDate><guid>https://gouthamhusky.github.io/blogsite/til/pid1-containers/</guid><description>&lt;p&gt;I&amp;rsquo;d always waved away the &amp;ldquo;use tini&amp;rdquo; advice in Dockerfiles as boilerplate
cargo-culting. Turns out PID 1 is genuinely special to the kernel, and
skipping it is exactly why containers hang on shutdown and quietly
accumulate zombie processes.&lt;/p&gt;
&lt;p&gt;Two things are true about PID 1 that aren&amp;rsquo;t true for any other process:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;It&amp;rsquo;s immune to the default action of unhandled signals. For every
other process, an unhandled &lt;code&gt;SIGTERM&lt;/code&gt; just kills it. PID 1 ignores it
unless the program explicitly installs a handler.&lt;/li&gt;
&lt;li&gt;It&amp;rsquo;s responsible for reaping orphaned children via &lt;code&gt;wait()&lt;/code&gt; — if it
doesn&amp;rsquo;t, they pile up as zombies.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Most application binaries do neither, because they were never written to
be an init system. So when your image runs the app directly as PID 1:&lt;/p&gt;</description></item><item><title>TIL: Kubernetes doesn't restart pods when a Secret changes — sometimes it doesn't even need to</title><link>https://gouthamhusky.github.io/blogsite/til/secrets-dont-restart-pods/</link><pubDate>Fri, 14 Aug 2026 00:00:00 +0000</pubDate><guid>https://gouthamhusky.github.io/blogsite/til/secrets-dont-restart-pods/</guid><description>&lt;p&gt;I assumed updating a Secret would eventually reach every pod using it, one
way or another. It doesn&amp;rsquo;t — and the two ways pods consume Secrets behave
completely differently once the Secret changes.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re pulling a Secret in as an &lt;strong&gt;environment variable&lt;/strong&gt;, it&amp;rsquo;s
resolved once at pod creation and frozen. Update the Secret, and the
running container&amp;rsquo;s environment never changes. Nothing short of a new pod
picks up the new value.&lt;/p&gt;</description></item><item><title>TIL: Argo CD's dry-run can fail before your manifests even get a chance</title><link>https://gouthamhusky.github.io/blogsite/til/argo-skip-dry-run-missing-resource/</link><pubDate>Tue, 11 Aug 2026 00:00:00 +0000</pubDate><guid>https://gouthamhusky.github.io/blogsite/til/argo-skip-dry-run-missing-resource/</guid><description>&lt;p&gt;I&amp;rsquo;ve hit this exact failure on so many Argo apps that I probably should
have figured out what was actually going on years ago: a CRD and a custom
resource that depends on it, installed in the same sync, blowing up
before either one gets a real chance.&lt;/p&gt;
&lt;p&gt;Before Argo CD actually applies anything, it runs each resource through
&lt;code&gt;kubectl apply --dry-run=server&lt;/code&gt; as a sanity check. That&amp;rsquo;s normally a good
thing — it catches broken manifests before they touch the cluster. But the
dry-run for the CR can hit the API server before the CRD has registered.
The API server has no idea what kind you&amp;rsquo;re talking about, so it rejects
the dry-run with something like &lt;code&gt;no matches for kind &amp;quot;Foo&amp;quot; in version &amp;quot;v1&amp;quot;&lt;/code&gt;. The error reads like a Kubernetes problem — because it is one — but
it&amp;rsquo;s easy to go looking for it in the chart templating first.&lt;/p&gt;</description></item><item><title>TIL: a Service doesn't know what a Deployment is</title><link>https://gouthamhusky.github.io/blogsite/til/service-label-canary/</link><pubDate>Mon, 10 Aug 2026 00:00:00 +0000</pubDate><guid>https://gouthamhusky.github.io/blogsite/til/service-label-canary/</guid><description>&lt;p&gt;I&amp;rsquo;d always assumed a Service was wired to a specific Deployment. It isn&amp;rsquo;t
— a Service only matches on pod labels. It has no concept of Deployment or
ReplicaSet at all, which means nothing stops two different Deployments
from feeding the same Service.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s enough to fake a canary rollout without any extra tooling. Give
your canary Deployment a single replica, your stable Deployment nine, and
make sure both pod templates carry the label your Service selects on:&lt;/p&gt;</description></item><item><title>TIL: making CRD lists behave like maps under server-side apply</title><link>https://gouthamhusky.github.io/blogsite/til/list-type-map/</link><pubDate>Thu, 06 Aug 2026 00:00:00 +0000</pubDate><guid>https://gouthamhusky.github.io/blogsite/til/list-type-map/</guid><description>&lt;p&gt;I&amp;rsquo;d been quietly assuming Kubernetes lists were just lists. Turns out some
of them are undercover maps, and not knowing which is which explains a
whole class of server-side apply conflicts I used to debug by vibes.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s the actual mechanism: when a CRD field is a list of objects, SSA
treats the whole list as one atomic value by default. It doesn&amp;rsquo;t know
entry 3 in my update and entry 3 in yours are supposed to be different
things — as far as it&amp;rsquo;s concerned we&amp;rsquo;re both just yelling into slot 3. Two
controllers each adding or updating their own entry look like they&amp;rsquo;re
fighting over the same field, so you get spurious conflicts, or one write
silently clobbering the other.&lt;/p&gt;</description></item><item><title>some notes on kubernetes ingress</title><link>https://gouthamhusky.github.io/blogsite/blog/notes-on-kubernetes-ingress/</link><pubDate>Mon, 14 Jul 2025 00:00:00 +0000</pubDate><guid>https://gouthamhusky.github.io/blogsite/blog/notes-on-kubernetes-ingress/</guid><description>&lt;p&gt;I keep re-learning how ingress actually routes traffic, so here are the notes I wish I&amp;rsquo;d written the first time.&lt;/p&gt;
&lt;h2 id="the-mental-model"&gt;the mental model&lt;/h2&gt;
&lt;p&gt;An Ingress object is just &lt;em&gt;configuration&lt;/em&gt;. On its own it does nothing — you need an ingress &lt;strong&gt;controller&lt;/strong&gt; running in the cluster to read those objects and actually route traffic.&lt;/p&gt;
&lt;aside class="note"&gt;
 &lt;span class="note-label"&gt;# gotcha&lt;/span&gt;
 &lt;div class="note-body"&gt;
 If your Ingress resource exists but nothing happens, 90% of the time you forgot to install a controller, or installed one that isn&amp;rsquo;t watching your ingress class.
 &lt;/div&gt;
&lt;/aside&gt;

&lt;h2 id="a-minimal-example"&gt;a minimal example&lt;/h2&gt;
&lt;p&gt;Here&amp;rsquo;s the smallest thing that does something useful:&lt;/p&gt;</description></item><item><title>how does systemd actually start things?</title><link>https://gouthamhusky.github.io/blogsite/blog/how-systemd-works/</link><pubDate>Sun, 22 Jun 2025 00:00:00 +0000</pubDate><guid>https://gouthamhusky.github.io/blogsite/blog/how-systemd-works/</guid><description>&lt;p&gt;I&amp;rsquo;ve typed &lt;code&gt;systemctl start&lt;/code&gt; thousands of times without really knowing what happens after I hit enter. Here&amp;rsquo;s what I pieced together.&lt;/p&gt;
&lt;h2 id="units-all-the-way-down"&gt;units all the way down&lt;/h2&gt;
&lt;p&gt;Everything systemd manages is a &lt;strong&gt;unit&lt;/strong&gt;. A service is a unit, a mount is a unit, a timer is a unit. They&amp;rsquo;re defined in plain text files, which is nice — you can read them.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f7f7f7;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;systemctl cat sshd.service
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="dependencies-are-the-interesting-part"&gt;dependencies are the interesting part&lt;/h2&gt;
&lt;p&gt;The thing I didn&amp;rsquo;t appreciate: systemd builds a dependency graph and starts things in parallel wherever it can. &lt;code&gt;Wants=&lt;/code&gt; and &lt;code&gt;Requires=&lt;/code&gt; describe &lt;em&gt;ordering intent&lt;/em&gt;, and &lt;code&gt;After=&lt;/code&gt; / &lt;code&gt;Before=&lt;/code&gt; describe &lt;em&gt;actual ordering&lt;/em&gt;.&lt;/p&gt;</description></item><item><title>about</title><link>https://gouthamhusky.github.io/blogsite/about/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://gouthamhusky.github.io/blogsite/about/</guid><description>&lt;p&gt;I&amp;rsquo;m Goutham. At Akamai I build an opinionated Kubernetes platform as a product
— upstream Kubernetes and Cluster API underneath, a set of deliberate choices
on top.&lt;/p&gt;
&lt;p&gt;The opinions are the product. Kubernetes will let you do very nearly anything,
which is not the same thing as helping you. A platform has to decide on its
users&amp;rsquo; behalf: what to expose, what to hide, which defaults to enforce, and
which escape hatches to leave unlocked. Hold too few opinions and you&amp;rsquo;ve
shipped raw Kubernetes with a support contract. Hold too many and you&amp;rsquo;ve built
a cage with good docs. Finding the balance between those two is the job.&lt;/p&gt;</description></item><item><title>community</title><link>https://gouthamhusky.github.io/blogsite/community/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://gouthamhusky.github.io/blogsite/community/</guid><description>&lt;p&gt;Talks, CNCF organizing, and conference appearances. Newest first — see
&lt;a href="https://gouthamhusky.github.io/blogsite/work/"&gt;work&lt;/a&gt; for the paid stuff.&lt;/p&gt;
&lt;h2 id="cloud-native-computing-foundation-cncf"&gt;&lt;a href="https://www.cncf.io"&gt;Cloud Native Computing Foundation (CNCF)&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Event Coordinator&lt;/strong&gt; — Jun 2026 to present — Austin, TX (Remote)&lt;/p&gt;
&lt;p&gt;Volunteer organizer for the CNCF local chapter in Austin.&lt;/p&gt;</description></item><item><title>work</title><link>https://gouthamhusky.github.io/blogsite/work/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://gouthamhusky.github.io/blogsite/work/</guid><description>&lt;p&gt;The résumé version, for people who want dates and bullets instead of my
opinions about control planes. Newest first — see &lt;a href="https://gouthamhusky.github.io/blogsite/about/"&gt;about&lt;/a&gt;
for the opinions, or &lt;a href="https://gouthamhusky.github.io/blogsite/community/"&gt;community&lt;/a&gt; for talks and
CNCF stuff.&lt;/p&gt;
&lt;h2 id="akamai-technologies"&gt;&lt;a href="https://www.akamai.com"&gt;Akamai Technologies&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Software Engineer II&lt;/strong&gt; — Sep 2024 to present — Cambridge, MA (Remote)&lt;/p&gt;
&lt;p&gt;Member of the Kubernetes Product Platform (KPP) team. We build a curated,
opinionated Kubernetes platform — the platform I ramble about on the
&lt;a href="https://gouthamhusky.github.io/blogsite/about/"&gt;about&lt;/a&gt; page is this one, so at least I have to
live with my own opinions. Built-in infrastructure provisioning, metrics,
logging, monitoring, and alerting. My job is to keep it highly available,
easily scalable, and boring in the specific ways PCI and CIS auditors like.&lt;/p&gt;</description></item></channel></rss>