<?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: cloud-sky-ops</title>
    <description>The latest articles on DEV Community by cloud-sky-ops (@cloud-sky-ops).</description>
    <link>https://dev.to/cloud-sky-ops</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1072315%2F21071cf4-0b1b-4150-b70f-fa13d36a39e1.jpg</url>
      <title>DEV Community: cloud-sky-ops</title>
      <link>https://dev.to/cloud-sky-ops</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/cloud-sky-ops"/>
    <language>en</language>
    <item>
      <title>Post 6/10 — Helm Fundamentals Done Right: Chart Architecture, Values Schema, and Reuse</title>
      <dc:creator>cloud-sky-ops</dc:creator>
      <pubDate>Sun, 18 Jan 2026 20:22:48 +0000</pubDate>
      <link>https://dev.to/cloud-sky-ops/post-610-helm-fundamentals-done-right-chart-architecture-values-schema-and-reuse-3bah</link>
      <guid>https://dev.to/cloud-sky-ops/post-610-helm-fundamentals-done-right-chart-architecture-values-schema-and-reuse-3bah</guid>
      <description>&lt;p&gt;If you're reading this blog I'd guess you're familiar with Helm and want to utilize if effectively. In case you aren't, I got you. Helm is a powerful packaging tool that help scale the Kubernetes manifest in a structured manner. I have highlighted the importance of Helm in some of my previous blogs and I'm covering some extra bits in this one. Just want my DevOps Engineer to not miss out on this simple yet effective tool. Do check my previous releases on Helm on my profile.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;1) Executive Summary&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Helm charts can be a blessing or a mess — depending on how disciplined your structure is. In this post, I’ll show how to design charts that scale: clean folder layout, validated values, reusable helpers, and consistent environment overlays.&lt;br&gt;
Think of this as the point where “copy-pasted YAML” evolves into a &lt;strong&gt;templatized, validated, and reusable release artifact&lt;/strong&gt;.&lt;/p&gt;


&lt;h2&gt;
  
  
  2) Prereqs
&lt;/h2&gt;

&lt;p&gt;You should already be comfortable with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;kubectl&lt;/code&gt; basics and manifests (&lt;code&gt;Deployment&lt;/code&gt;, &lt;code&gt;Service&lt;/code&gt;, &lt;code&gt;Ingress&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;helm install&lt;/code&gt;, &lt;code&gt;upgrade&lt;/code&gt;, and &lt;code&gt;uninstall&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;YAML indentation and Go template syntax (&lt;code&gt;{{ .Values.image.repository }}&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If not, go back to Post 5 where we replaced Ingress with Gateway API — that’s the level of confidence you need.&lt;/p&gt;


&lt;h2&gt;
  
  
  &lt;strong&gt;3) Concepts&lt;/strong&gt;
&lt;/h2&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;a) Chart layout &amp;amp; sane defaults&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;A Helm chart is just structured YAML + templates + metadata.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Default layout:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mychart/
├── Chart.yaml
├── values.yaml
├── values.schema.json
├── templates/
│   ├── deployment.yaml
│   ├── service.yaml
│   └── _helpers.tpl
└── charts/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why it matters:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Chart.yaml&lt;/code&gt; defines metadata and dependencies.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;values.yaml&lt;/code&gt; holds configuration knobs (default values).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;templates/&lt;/code&gt; defines the actual Kubernetes manifests.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;_helpers.tpl&lt;/code&gt; hosts shared snippets like names and labels.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Decision cue:&lt;/strong&gt;&lt;br&gt;
If you can’t read your chart structure at a glance — it’s probably too magical. Keep it explicit.&lt;/p&gt;


&lt;h3&gt;
  
  
  &lt;strong&gt;b) &lt;code&gt;values.schema.json&lt;/code&gt; validation&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;This file enforces structure and type-checking for your &lt;code&gt;values.yaml&lt;/code&gt;.&lt;br&gt;
Without it, Helm silently ignores typos — your app may deploy with wrong configs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before → After&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before:&lt;/strong&gt;&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="c1"&gt;# values.yaml&lt;/span&gt;
&lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;repo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx&lt;/span&gt;
  &lt;span class="na"&gt;tag&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;latest&lt;/span&gt;  &lt;span class="c1"&gt;# typo: should be 'repository'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;After:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;values.schema.json&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"$schema"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://json-schema.org/draft-07/schema#"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"image"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"repository"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"tag"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"required"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"repository"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, &lt;code&gt;helm lint&lt;/code&gt; will fail fast if someone mis-keys a field.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Decision cue:&lt;/strong&gt;&lt;br&gt;
If multiple people edit &lt;code&gt;values.yaml&lt;/code&gt;, a schema is mandatory.&lt;/p&gt;


&lt;h3&gt;
  
  
  &lt;strong&gt;c) Helpers/partials &amp;amp; library charts&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Avoid repetition. Use helpers (&lt;code&gt;_helpers.tpl&lt;/code&gt;) for common names, labels, and annotations.&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="c1"&gt;# _helpers.tpl&lt;/span&gt;
&lt;span class="pi"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;- define "app.fullname" -&lt;/span&gt;&lt;span class="pi"&gt;}}&lt;/span&gt;
&lt;span class="pi"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;printf "%s-%s" .Release.Name .Chart.Name&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt;
&lt;span class="pi"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;- end -&lt;/span&gt;&lt;span class="pi"&gt;}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then reuse it:&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;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="pi"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;include "app.fullname" .&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When multiple services share common patterns (e.g., &lt;code&gt;serviceaccount.yaml&lt;/code&gt;, probes, labels), extract them into a &lt;strong&gt;library chart&lt;/strong&gt;.&lt;br&gt;
Library charts have no manifests — just helpers and templates that other charts can import.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Decision cue:&lt;/strong&gt;&lt;br&gt;
When you copy the same YAML block in more than two charts — extract it into a helper or library.&lt;/p&gt;


&lt;h3&gt;
  
  
  &lt;strong&gt;d) Environment overlays strategy&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Instead of maintaining &lt;code&gt;values-dev.yaml&lt;/code&gt;, &lt;code&gt;values-stg.yaml&lt;/code&gt;, and &lt;code&gt;values-prod.yaml&lt;/code&gt; in chaos, organize by &lt;strong&gt;overlays&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;values/
  base.yaml
  dev.yaml
  stg.yaml
  prod.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each extends the base:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;helm &lt;span class="nb"&gt;install &lt;/span&gt;myapp &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; values/base.yaml &lt;span class="nt"&gt;-f&lt;/span&gt; values/prod.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Best practice:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keep common knobs in &lt;code&gt;base.yaml&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Only override what truly differs across envs.&lt;/li&gt;
&lt;li&gt;Avoid env-specific conditionals in templates (&lt;code&gt;if .Values.env == "prod"&lt;/code&gt; → smells).&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;4) Mini-Lab — From Raw Manifests to Chart&lt;/strong&gt;
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Start with raw YAML:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   kubectl create deploy web &lt;span class="nt"&gt;--image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;nginx &lt;span class="nt"&gt;--dry-run&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;client &lt;span class="nt"&gt;-o&lt;/span&gt; yaml &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; deployment.yaml
   kubectl expose deploy web &lt;span class="nt"&gt;--port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;80 &lt;span class="nt"&gt;--dry-run&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;client &lt;span class="nt"&gt;-o&lt;/span&gt; yaml &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; service.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Create a chart:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   helm create webapp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Move manifests:&lt;/strong&gt; replace defaults with your generated files under &lt;code&gt;templates/&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add schema validation:&lt;/strong&gt;
Create &lt;code&gt;values.schema.json&lt;/code&gt; to validate image &amp;amp; replica fields.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extract helpers:&lt;/strong&gt;
Move naming logic to &lt;code&gt;_helpers.tpl&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test locally:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   helm template webapp &lt;span class="nb"&gt;.&lt;/span&gt;
   helm lint webapp
   helm &lt;span class="nb"&gt;install &lt;/span&gt;webapp &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--dry-run&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Result: a reusable, validated chart with clear structure.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhwb8opaq4vdell846ek2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhwb8opaq4vdell846ek2.png" alt="Diagram" width="558" height="575"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;5) Cheatsheet&lt;/strong&gt;
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;helm show values &amp;lt;chart&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Display default values&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;helm lint .&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Validate chart + schema&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;helm template .&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Render YAML locally&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;helm upgrade --install &amp;lt;release&amp;gt; . -f values/prod.yaml&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Idempotent deploy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Schema tip:&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Run &lt;code&gt;jq . values.schema.json&lt;/code&gt; to quickly validate structure&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;6) Pitfalls&lt;/strong&gt;
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pitfall&lt;/th&gt;
&lt;th&gt;Why It Hurts&lt;/th&gt;
&lt;th&gt;Fix&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Values drift&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Each team edits &lt;code&gt;values.yaml&lt;/code&gt; differently.&lt;/td&gt;
&lt;td&gt;Enforce schema + overlays.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Over-templating&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Logic explodes inside YAML.&lt;/td&gt;
&lt;td&gt;Keep logic minimal; pre-compute in CI/CD.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;No library reuse&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Duplication everywhere.&lt;/td&gt;
&lt;td&gt;Move shared blocks to &lt;code&gt;_helpers.tpl&lt;/code&gt; or library charts.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Missing schema&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Silent deployment errors.&lt;/td&gt;
&lt;td&gt;Add &lt;code&gt;values.schema.json&lt;/code&gt; early.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;7) Wrap-up — Next: Shipping Charts with Confidence: Lint, Tests, Diff, OCI &amp;amp; Provenance (Post 7)&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;We now have clean, validated, reusable Helm charts that can be tested in isolation.&lt;br&gt;
Next, in &lt;strong&gt;Post 7&lt;/strong&gt;, we’ll cover how to ship charts with confidence: Lint, Tests, Diff, OCI &amp;amp; Provenance.&lt;/p&gt;

&lt;p&gt;Helm is powerful when it’s predictable.&lt;br&gt;
Do it right once, and every future deployment becomes safer, faster, and auditable. Thanks for sticking around till the end, drop your comments and feedback below.&lt;/p&gt;




</description>
      <category>architecture</category>
      <category>devops</category>
      <category>kubernetes</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Post 5/10 — From Ingress to Gateway API: Safer, Smarter Traffic Control</title>
      <dc:creator>cloud-sky-ops</dc:creator>
      <pubDate>Sun, 07 Dec 2025 10:52:32 +0000</pubDate>
      <link>https://dev.to/cloud-sky-ops/post-510-from-ingress-to-gateway-api-safer-smarter-traffic-control-4od4</link>
      <guid>https://dev.to/cloud-sky-ops/post-510-from-ingress-to-gateway-api-safer-smarter-traffic-control-4od4</guid>
      <description>&lt;p&gt;The question of why traffic should be controlled using additional means when Kubernetes offers in-built traffic management capabilities. Many Cloud Service Providers also have offerings around the same but having a set of defined policies to execute traffic management is necessary. This helps the application distribute the load in the system strategically. It also ensures lower latency in fulfilling request. Moreover, these policies make application secure my allowing limited access of resources both in and out of the Kubernetes cluster.&lt;/p&gt;




&lt;h2&gt;
  
  
  1) Executive Summary
&lt;/h2&gt;

&lt;p&gt;When I first learned Kubernetes networking, &lt;strong&gt;Ingress&lt;/strong&gt; felt like the magic front-door. But as clusters, tenants, and compliance needs grew, that door started to creak. Enter &lt;strong&gt;Gateway API&lt;/strong&gt; — the modern, role-aware evolution that finally decouples platform and app teams safely.&lt;/p&gt;

&lt;p&gt;This post walks through what changes (and why), shows a &lt;strong&gt;Before → After&lt;/strong&gt; migration, and ends with a mini-lab you can run on any cluster.&lt;/p&gt;




&lt;h2&gt;
  
  
  2) Prereqs
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A working cluster (minikube/kind/EKS/GKE).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;kubectl ≥ 1.28&lt;/code&gt; + &lt;code&gt;gateway-api&lt;/code&gt; CRDs installed (&lt;code&gt;kubectl apply k https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.0.0/standard-install.yaml&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Basic understanding of Ingress objects, Services, and TLS secrets.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  3) Concepts
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Ingress vs Gateway mental model
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspect&lt;/th&gt;
&lt;th&gt;Ingress&lt;/th&gt;
&lt;th&gt;Gateway API&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Scope&lt;/td&gt;
&lt;td&gt;Cluster-wide front door&lt;/td&gt;
&lt;td&gt;Namespaced, multi-tenant gateways&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Roles&lt;/td&gt;
&lt;td&gt;One YAML = shared config&lt;/td&gt;
&lt;td&gt;Split into Gateway (ops) + HTTPRoute (dev)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Extensibility&lt;/td&gt;
&lt;td&gt;Annotations galore&lt;/td&gt;
&lt;td&gt;Typed fields + policies&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Status&lt;/td&gt;
&lt;td&gt;Controller-specific&lt;/td&gt;
&lt;td&gt;Standardized conditions&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Think of &lt;strong&gt;Ingress&lt;/strong&gt; as a flat router file. &lt;strong&gt;Gateway API&lt;/strong&gt; adds layers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GatewayClass&lt;/strong&gt; – defines the implementation (e.g., Istio, NGINX).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gateway&lt;/strong&gt; – deployed by platform teams (where traffic enters).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HTTPRoute&lt;/strong&gt; – attached by app teams (what traffic does).&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Gateway + HTTPRoute basics
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Gateway:&lt;/strong&gt; specifies listeners, ports, and TLS termination.&lt;br&gt;
&lt;strong&gt;HTTPRoute:&lt;/strong&gt; defines matches (hosts, paths, headers) and forwards traffic to services.&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="c1"&gt;# gateway.yaml&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gateway.networking.k8s.io/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;Gateway&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;web-gw&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;platform&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;gatewayClassName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx&lt;/span&gt;
  &lt;span class="na"&gt;listeners&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;https&lt;/span&gt;
    &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;443&lt;/span&gt;
    &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;HTTPS&lt;/span&gt;
    &lt;span class="na"&gt;tls&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Terminate&lt;/span&gt;
      &lt;span class="na"&gt;certificateRefs&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;my-cert&lt;/span&gt;
    &lt;span class="na"&gt;allowedRoutes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;namespaces&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;All&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# route.yaml&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gateway.networking.k8s.io/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;HTTPRoute&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;shop-route&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;shop&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;parentRefs&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;web-gw&lt;/span&gt;
    &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;platform&lt;/span&gt;
  &lt;span class="na"&gt;hostnames&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;shop.example.com"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;matches&lt;/span&gt;&lt;span class="pi"&gt;:&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="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PathPrefix&lt;/span&gt;
        &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/&lt;/span&gt;
    &lt;span class="na"&gt;backendRefs&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;shop-svc&lt;/span&gt;
      &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  TLS, Retries, Timeouts, Header Matching
&lt;/h3&gt;

&lt;p&gt;Gateway API formalizes what used to be annotation chaos:&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;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;matches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;headers&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;x-region&lt;/span&gt;
        &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;eu&lt;/span&gt;
    &lt;span class="na"&gt;backendRefs&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;shop-svc-eu&lt;/span&gt;
      &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
  &lt;span class="na"&gt;filters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;RequestTimeout&lt;/span&gt;
    &lt;span class="na"&gt;requestTimeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5s&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Retry&lt;/span&gt;
    &lt;span class="na"&gt;retry&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
      &lt;span class="na"&gt;statusCodes&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;5xx"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Canary / Mirroring / Multi-tenant Gateways
&lt;/h3&gt;

&lt;p&gt;Weighted traffic splits and mirrored routes are first-class now:&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;backendRefs&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;shop-svc&lt;/span&gt;
    &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
    &lt;span class="na"&gt;weight&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&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;shop-svc-canary&lt;/span&gt;
    &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
    &lt;span class="na"&gt;weight&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Multiple &lt;strong&gt;HTTPRoutes&lt;/strong&gt; can attach to one &lt;strong&gt;Gateway&lt;/strong&gt;, safely namespaced.&lt;br&gt;
Ops maintain TLS + exposure policies; devs just ship routes.&lt;/p&gt;


&lt;h2&gt;
  
  
  4) Mini-Lab — Replace Ingress with Gateway/HTTPRoute + Canary + TLS
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Before (legacy Ingress):&lt;/strong&gt;&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;networking.k8s.io/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;Ingress&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;shop&lt;/span&gt;
  &lt;span class="na"&gt;annotations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;nginx.ingress.kubernetes.io/rewrite-target&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/&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;tls&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;shop.example.com&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;secretName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-cert&lt;/span&gt;
  &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;shop.example.com&lt;/span&gt;
    &lt;span class="na"&gt;http&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&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;/&lt;/span&gt;
        &lt;span class="na"&gt;pathType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Prefix&lt;/span&gt;
        &lt;span class="na"&gt;backend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;service&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;shop-svc&lt;/span&gt;
            &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;number&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;After (Gateway API):&lt;/strong&gt;&lt;br&gt;
Apply &lt;code&gt;gateway.yaml&lt;/code&gt; and &lt;code&gt;route.yaml&lt;/code&gt; from above.&lt;/p&gt;

&lt;p&gt;Verify:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get gateways &lt;span class="nt"&gt;-A&lt;/span&gt;
kubectl get httproutes &lt;span class="nt"&gt;-A&lt;/span&gt;
kubectl describe httproute shop-route
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add a canary service and observe:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get endpoints shop-svc-canary &lt;span class="nt"&gt;-o&lt;/span&gt; wide
curl &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"x-canary:true"&lt;/span&gt; https://shop.example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You’ll see controlled traffic flow, better observability, and cleaner ownership.&lt;/p&gt;




&lt;h2&gt;
  
  
  5) Cheatsheet
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Task&lt;/th&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;List GatewayClasses&lt;/td&gt;
&lt;td&gt;&lt;code&gt;kubectl get gatewayclass&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Inspect listeners&lt;/td&gt;
&lt;td&gt;&lt;code&gt;kubectl describe gateway &amp;lt;name&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;List routes per gateway&lt;/td&gt;
&lt;td&gt;&lt;code&gt;kubectl get httproute -A --field-selector spec.parentRefs.name=&amp;lt;gw&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Apply TLS secret&lt;/td&gt;
&lt;td&gt;&lt;code&gt;kubectl create secret tls my-cert --cert cert.pem --key key.pem -n platform&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Test connectivity&lt;/td&gt;
&lt;td&gt;&lt;code&gt;curl -k https://host&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Key fields:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;listeners.protocol&lt;/code&gt;, &lt;code&gt;tls.mode&lt;/code&gt;, &lt;code&gt;allowedRoutes&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;rules.matches&lt;/code&gt;, &lt;code&gt;filters&lt;/code&gt;, &lt;code&gt;backendRefs.weight&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  6) Pitfalls
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Policy attachment order:&lt;/strong&gt; Gateway policies apply before Route policies → watch precedence.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Overlapping routes:&lt;/strong&gt; Multiple HTTPRoutes with same host/path → controller decides priority.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-namespace refs:&lt;/strong&gt; If &lt;code&gt;allowedRoutes.from=Same&lt;/code&gt;, dev teams can’t attach externally.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Controller support:&lt;/strong&gt; Check &lt;code&gt;status.conditions&lt;/code&gt; for accepted listeners.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  7) Wrap-Up
&lt;/h2&gt;

&lt;p&gt;With Gateway API, Kubernetes finally gives us &lt;strong&gt;policy-aware traffic control&lt;/strong&gt; that scales across teams and environments — safer, cleaner, and built for automation.&lt;/p&gt;

&lt;p&gt;Next up in the series: &lt;strong&gt;Helm Fundamentals (Post 6)&lt;/strong&gt; — we’ll templatize these Gateway objects and parameterize routes the right way.&lt;/p&gt;




</description>
      <category>programming</category>
      <category>kubernetes</category>
      <category>devops</category>
    </item>
    <item>
      <title>Post 4/10 — Smart Scaling &amp; Cost Control: HPA/KEDA + Cluster Autoscaler vs Karpenter</title>
      <dc:creator>cloud-sky-ops</dc:creator>
      <pubDate>Wed, 15 Oct 2025 03:43:00 +0000</pubDate>
      <link>https://dev.to/cloud-sky-ops/post-410-smart-scaling-cost-control-hpakeda-cluster-autoscaler-vs-karpenter-hm3</link>
      <guid>https://dev.to/cloud-sky-ops/post-410-smart-scaling-cost-control-hpakeda-cluster-autoscaler-vs-karpenter-hm3</guid>
      <description>&lt;p&gt;All businesses need a means to minimize cash leaks in its infrastructure. A DevOps engineer with the understand of autoscaling can help with this significantly. While the aspect of writing lightweight applications is still crucial, there still might be cases where the business use case is expecting inconsistent spikes in traffic, example: on a ticketing platform when tickets are live for a popular music tour. This is where we need a reliable system that can manage this sudden spike gracefully. “Scaling is easy — until it isn’t. Then it’s your cloud bill that reminds you.”&lt;/p&gt;




&lt;h2&gt;
  
  
  1️⃣ Executive Summary
&lt;/h2&gt;

&lt;p&gt;This post is about &lt;strong&gt;smart elasticity&lt;/strong&gt; — how Kubernetes scales workloads and clusters while keeping cost in check.&lt;br&gt;
I’ll walk through the difference between &lt;strong&gt;workload-level autoscaling (HPA/KEDA)&lt;/strong&gt; and &lt;strong&gt;cluster-level autoscaling (CA vs Karpenter)&lt;/strong&gt;, with a hands-on demo and a practical comparison.&lt;/p&gt;

&lt;p&gt;By the end, you’ll know:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How &lt;code&gt;requests&lt;/code&gt; + &lt;code&gt;limits&lt;/code&gt; shape node packing and spend.&lt;/li&gt;
&lt;li&gt;When to use &lt;strong&gt;HPA&lt;/strong&gt; vs &lt;strong&gt;KEDA&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Why &lt;strong&gt;Karpenter&lt;/strong&gt; often replaces the traditional &lt;strong&gt;Cluster Autoscaler&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;How to avoid the most common scaling pitfalls — thrash, cold starts, and QoS chaos.&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  2️⃣ Prereqs
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A basic Kubernetes cluster (minikube/kind/EKS/GKE).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;kubectl&lt;/code&gt;, &lt;code&gt;helm&lt;/code&gt;, &lt;code&gt;kubectl top&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Sample deployment (e.g., an Nginx or Go web app).&lt;/li&gt;
&lt;li&gt;Optional: KEDA + Metrics Server installed.&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  3️⃣ Concepts
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Requests / Limits &amp;amp; Rightsizing
&lt;/h3&gt;

&lt;p&gt;Each container declares:&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;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="s"&gt;100m&lt;/span&gt;
    &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;128Mi&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="s"&gt;200m&lt;/span&gt;
    &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;256Mi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Requests&lt;/strong&gt; → scheduler guarantee.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Limits&lt;/strong&gt; → runtime cap.&lt;/li&gt;
&lt;li&gt;Under-request = OOM; over-request = waste.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Decision cue:&lt;/em&gt; profile your pods with &lt;code&gt;kubectl top pod&lt;/code&gt; and rightsize before enabling autoscaling; otherwise, autoscalers just magnify inefficiency.&lt;/p&gt;




&lt;h3&gt;
  
  
  HPA (Horizontal Pod Autoscaler)
&lt;/h3&gt;

&lt;p&gt;HPA scales replicas based on metrics — typically CPU or memory:&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;autoscaling/v2&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;HorizontalPodAutoscaler&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;web-hpa&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;scaleTargetRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/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;Deployment&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;web&lt;/span&gt;
  &lt;span class="na"&gt;minReplicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
  &lt;span class="na"&gt;maxReplicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
  &lt;span class="na"&gt;metrics&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Resource&lt;/span&gt;
    &lt;span class="na"&gt;resource&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;cpu&lt;/span&gt;
      &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;averageUtilization&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;70&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Utilization&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;✅ &lt;strong&gt;Before:&lt;/strong&gt; Fixed 3 replicas → wasted CPU at night.&lt;br&gt;
✅ &lt;strong&gt;After:&lt;/strong&gt; HPA 2-10 replicas → 40 % cost reduction, stable latency.&lt;/p&gt;


&lt;h3&gt;
  
  
  🔁 KEDA ( Kubernetes Event-Driven Autoscaler )
&lt;/h3&gt;

&lt;p&gt;KEDA extends HPA with external triggers (Queue, Kafka, Prometheus, etc.):&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;keda.sh/v1alpha1&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;ScaledObject&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;queue-scaledobject&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;scaleTargetRef&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;triggers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;azure-queue&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;queueName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;orders&lt;/span&gt;
      &lt;span class="na"&gt;connection&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AzureWebJobsStorage&lt;/span&gt;
      &lt;span class="na"&gt;queueLength&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;5'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Decision cue:&lt;/em&gt; use &lt;strong&gt;KEDA&lt;/strong&gt; when metrics are &lt;em&gt;outside&lt;/em&gt; Kubernetes — SQS depth, Kafka lag, HTTP requests/sec, etc.&lt;/p&gt;




&lt;h3&gt;
  
  
  Cluster Autoscaler (CA) Overview
&lt;/h3&gt;

&lt;p&gt;CA watches unschedulable pods and adds/removes nodes via your cloud provider’s ASG or NodePool.&lt;br&gt;
It’s conservative — scales in minutes, not seconds — and tied to fixed instance groups.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Decision cue:&lt;/em&gt; CA is fine for &lt;strong&gt;homogeneous workloads&lt;/strong&gt; with &lt;strong&gt;predictable demand&lt;/strong&gt;.&lt;/p&gt;


&lt;h3&gt;
  
  
  Karpenter
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Karpenter&lt;/strong&gt; is the next-gen autoscaler for AWS (and now CNCF incubating):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Launches &lt;em&gt;any&lt;/em&gt; instance type on demand, not limited by node groups.&lt;/li&gt;
&lt;li&gt;Supports &lt;em&gt;Spot&lt;/em&gt; + &lt;em&gt;On-Demand&lt;/em&gt; mix.&lt;/li&gt;
&lt;li&gt;Consolidates underutilized nodes automatically.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example Provisioner:&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;karpenter.sh/v1beta1&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;Provisioner&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;default&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;limits&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;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1000&lt;/span&gt;
  &lt;span class="na"&gt;requirements&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;node.kubernetes.io/instance-type"&lt;/span&gt;
    &lt;span class="na"&gt;operator&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;In&lt;/span&gt;
    &lt;span class="na"&gt;values&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;m6i.large"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;m6a.large"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;consolidation&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Decision cue:&lt;/em&gt; choose &lt;strong&gt;Karpenter&lt;/strong&gt; when you need &lt;strong&gt;rapid scale-out&lt;/strong&gt; (seconds), &lt;strong&gt;mixed instance types&lt;/strong&gt;, or &lt;strong&gt;Spot savings&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  4️⃣ Mini-Lab — HPA + CA vs Karpenter
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Deploy a sample app&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   kubectl create deploy web &lt;span class="nt"&gt;--image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;nginx &lt;span class="nt"&gt;--replicas&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2
   kubectl expose deploy web &lt;span class="nt"&gt;--port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;80
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Apply HPA&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   kubectl autoscale deploy web &lt;span class="nt"&gt;--cpu-percent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;50 &lt;span class="nt"&gt;--min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2 &lt;span class="nt"&gt;--max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;10
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Generate load&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   kubectl run load &lt;span class="nt"&gt;--image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;busybox &lt;span class="nt"&gt;--&lt;/span&gt; /bin/sh &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="s2"&gt;"while true; do wget -q -O- http://web; done"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Observe scale-up&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   kubectl get hpa
   kubectl get pods -w
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Compare cluster-level response&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;With &lt;strong&gt;Cluster Autoscaler&lt;/strong&gt;, new nodes join slowly, via ASG.&lt;/li&gt;
&lt;li&gt;With &lt;strong&gt;Karpenter&lt;/strong&gt;, new instance spins up in &amp;lt; 60 s.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you can’t run Karpenter (non-EKS), compare &lt;strong&gt;plan latency&lt;/strong&gt; and &lt;strong&gt;node variety&lt;/strong&gt; conceptually.&lt;/p&gt;




&lt;h2&gt;
  
  
  5️⃣ Cheatsheet
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Task&lt;/th&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;View metrics&lt;/td&gt;
&lt;td&gt;&lt;code&gt;kubectl top pods&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deploy HPA&lt;/td&gt;
&lt;td&gt;&lt;code&gt;kubectl autoscale deploy &amp;lt;app&amp;gt; --cpu-percent=X --min=A --max=B&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Install KEDA&lt;/td&gt;
&lt;td&gt;&lt;code&gt;helm install keda kedacore/keda&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Inspect ScaledObjects&lt;/td&gt;
&lt;td&gt;&lt;code&gt;kubectl get scaledobject&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Check node scale&lt;/td&gt;
&lt;td&gt;&lt;code&gt;kubectl get nodes -w&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;View CA logs&lt;/td&gt;
&lt;td&gt;&lt;code&gt;kubectl -n kube-system logs deploy/cluster-autoscaler&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Describe Karpenter provisioners&lt;/td&gt;
&lt;td&gt;&lt;code&gt;kubectl get provisioner&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  6️⃣ Pitfalls
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Thrash:&lt;/strong&gt; aggressive min/max or low cooldown = scale in/out loops.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cold starts:&lt;/strong&gt; node boot latency + image pull → avoid per-request scaling.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;QoS classes:&lt;/strong&gt; BestEffort pods may be evicted first; use &lt;em&gt;Guaranteed&lt;/em&gt; for critical services.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Over-requests:&lt;/strong&gt; rightsizing before autoscaling saves more than any tuning flag.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  7️⃣ Wrap-Up
&lt;/h2&gt;

&lt;p&gt;Kubernetes scaling is now less about &lt;em&gt;can it scale&lt;/em&gt; and more about &lt;em&gt;how smartly it scales&lt;/em&gt;.&lt;br&gt;
&lt;strong&gt;HPA/KEDA&lt;/strong&gt; handle micro elasticity.&lt;br&gt;
&lt;strong&gt;Karpenter&lt;/strong&gt; redefines macro elasticity and cloud efficiency.&lt;/p&gt;

&lt;p&gt;In the next post — &lt;em&gt;Modern Traffic Shaping (Post 5)&lt;/em&gt; — we’ll explore how to handle that scale with smart routing: Ingress, Service mesh, and load-balancing patterns.&lt;/p&gt;




&lt;h3&gt;
  
  
  Diagram 1: Demand vs Capacity Timeline
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgslc2ce28zw6v8csg28l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgslc2ce28zw6v8csg28l.png" alt="Diagram 1" width="800" height="358"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Diagram 2: Provisioning Decision Tree
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgmheqr9cotkevkqro376.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgmheqr9cotkevkqro376.png" alt="Diagram 2" width="559" height="675"&gt;&lt;/a&gt;&lt;/p&gt;




</description>
      <category>kubernetes</category>
      <category>devops</category>
      <category>automation</category>
    </item>
    <item>
      <title>Post 3/10 — Safe Cadence: Kubernetes Upgrades, Version Skew, and Feature Gates</title>
      <dc:creator>cloud-sky-ops</dc:creator>
      <pubDate>Fri, 10 Oct 2025 02:55:00 +0000</pubDate>
      <link>https://dev.to/cloud-sky-ops/post-310-safe-cadence-kubernetes-upgrades-version-skew-and-feature-gates-34mf</link>
      <guid>https://dev.to/cloud-sky-ops/post-310-safe-cadence-kubernetes-upgrades-version-skew-and-feature-gates-34mf</guid>
      <description>&lt;h3&gt;
  
  
  Executive Summary
&lt;/h3&gt;

&lt;p&gt;Upgrades are where clusters live—or die—by their discipline. A rushed &lt;code&gt;kubectl upgrade&lt;/code&gt; can silently break workloads, APIs, or CRDs.&lt;br&gt;
In this post, I’ll walk you through &lt;strong&gt;how to plan, test, and execute Kubernetes upgrades safely&lt;/strong&gt;, understand &lt;strong&gt;version-skew guarantees&lt;/strong&gt;, manage &lt;strong&gt;feature gates&lt;/strong&gt;, and &lt;strong&gt;validate everything before production&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;By the end, you’ll know &lt;em&gt;why cadence matters more than version chasing.&lt;/em&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Prereqs
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Familiarity with &lt;strong&gt;kubectl&lt;/strong&gt;, &lt;strong&gt;kubeadm&lt;/strong&gt;, or &lt;strong&gt;managed control planes&lt;/strong&gt; (EKS, GKE, AKS).&lt;/li&gt;
&lt;li&gt;Working understanding of &lt;strong&gt;pods, CRDs, controllers, and API objects&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Access to a &lt;strong&gt;staging or pre-prod environment&lt;/strong&gt; where you can test.&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  Concepts
&lt;/h2&gt;
&lt;h3&gt;
  
  
  1️⃣ Support Windows &amp;amp; Version Skew
&lt;/h3&gt;

&lt;p&gt;Kubernetes maintains a &lt;strong&gt;1-year support window&lt;/strong&gt; with &lt;strong&gt;three active minor releases&lt;/strong&gt; (e.g., 1.29 → 1.31).&lt;br&gt;
Control plane and kubelet skew rules:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Supported Skew&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;kube-apiserver&lt;/code&gt; vs others&lt;/td&gt;
&lt;td&gt;±1 minor&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;kube-controller-manager&lt;/code&gt;, &lt;code&gt;scheduler&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;same as API Server&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;kubelet&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;up to 1 minor older&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;kubectl&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;±1 minor&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Decision cue:&lt;/strong&gt; If your nodes or clients are &amp;gt;1 minor behind control plane → plan upgrade &lt;strong&gt;immediately&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before → After&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1.24 control plane, 1.21 nodes&lt;/td&gt;
&lt;td&gt;❌ Unsupported (skew = 3)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1.28 control plane, 1.27 nodes&lt;/td&gt;
&lt;td&gt;✅ Supported (skew = 1)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;


&lt;h3&gt;
  
  
  2️⃣ Upgrade Order: Control Plane → Nodes
&lt;/h3&gt;

&lt;p&gt;Always upgrade &lt;strong&gt;control plane first&lt;/strong&gt;, then &lt;strong&gt;worker nodes&lt;/strong&gt;, then &lt;strong&gt;addons&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Flow:&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;etcd → apiserver → controller-manager → scheduler → kubelet → kube-proxy → CNI → CSI → custom controllers&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Decision cue:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Never let newer nodes talk to an older API Server.&lt;br&gt;
The API Server must always be the newest component in the cluster.&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h3&gt;
  
  
  3️⃣ Deprecation Checklist &amp;amp; API Migration
&lt;/h3&gt;

&lt;p&gt;Each minor release deprecates APIs. To find the list, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl krew &lt;span class="nb"&gt;install &lt;/span&gt;deprecations
kubectl deprecations &lt;span class="nt"&gt;--k8s-version&lt;/span&gt; v1.31
kubectl deprecations &lt;span class="nt"&gt;--k8s-version&lt;/span&gt; 1.31
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or if you don’t have that plugin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get &lt;span class="nt"&gt;--raw&lt;/span&gt; /openapi/v2 | &lt;span class="nb"&gt;grep &lt;/span&gt;v1beta1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl convert &lt;span class="nt"&gt;-f&lt;/span&gt; old.yaml &lt;span class="nt"&gt;--output-version&lt;/span&gt; apps/v1 &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; new.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Before → After Example&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before:&lt;/strong&gt;&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;apps/v1beta2&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;Deployment&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;After:&lt;/strong&gt;&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;apps/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;Deployment&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Decision cue:&lt;/strong&gt;&lt;br&gt;
→ Fix API versions &lt;em&gt;before&lt;/em&gt; upgrading; otherwise manifests may fail silently during rollout.&lt;/p&gt;


&lt;h3&gt;
  
  
  4️⃣ Feature Gate Evaluation in Pre-Prod
&lt;/h3&gt;

&lt;p&gt;Feature gates toggle experimental behavior.&lt;br&gt;
Check current state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kube-apiserver &lt;span class="nt"&gt;--help&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;feature-gates
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Enable safely:&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;apiServer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;extraArgs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;feature-gates&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PodDisruptionPolicy=false,JobPodFailurePolicy=true"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Decision cue:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Always enable gates &lt;strong&gt;only in pre-prod&lt;/strong&gt; first; validate e2e conformance before rollout.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  🧪 Mini-Lab — Plan a Minor Upgrade
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Upgrade 1.29 → 1.30 in staging safely.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. View current versions&lt;/span&gt;
kubectl get nodes &lt;span class="nt"&gt;-o&lt;/span&gt; wide

&lt;span class="c"&gt;# 2. Drain one node&lt;/span&gt;
kubectl drain node-1 &lt;span class="nt"&gt;--ignore-daemonsets&lt;/span&gt; &lt;span class="nt"&gt;--delete-emptydir-data&lt;/span&gt;

&lt;span class="c"&gt;# 3. Upgrade control plane&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;kubeadm upgrade apply v1.30.1

&lt;span class="c"&gt;# 4. Upgrade kubelet/kubectl&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;&lt;span class="nv"&gt;kubelet&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1.30.1-00 &lt;span class="nv"&gt;kubectl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1.30.1-00
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart kubelet

&lt;span class="c"&gt;# 5. Uncordon and validate&lt;/span&gt;
kubectl uncordon node-1
kubectl get nodes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Validate with conformance tests:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sonobuoy run &lt;span class="nt"&gt;--mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;certified-conformance
sonobuoy status
sonobuoy retrieve &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Toggle a feature gate safely:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl patch deployment kube-apiserver &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-n&lt;/span&gt; kube-system &lt;span class="nt"&gt;--type&lt;/span&gt; merge &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s1"&gt;'{"spec":{"template":{"spec":{"containers":[{"name":"kube-apiserver","args":["--feature-gates=DynamicResourceAllocation=true"]}]}}}}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rollback with &lt;code&gt;kubectl diff&lt;/code&gt; → &lt;code&gt;kubectl apply -f rollback.yaml --server-side --dry-run=server&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Cheatsheet
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Task&lt;/th&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;List deprecations&lt;/td&gt;
&lt;td&gt;&lt;code&gt;kubectl deprecations --k8s-version X.Y&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Convert manifest&lt;/td&gt;
&lt;td&gt;&lt;code&gt;kubectl convert -f old.yaml --output-version apps/v1&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dry-run on server&lt;/td&gt;
&lt;td&gt;&lt;code&gt;kubectl apply -f file.yaml --server-side --dry-run=server&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Diff check&lt;/td&gt;
&lt;td&gt;&lt;code&gt;kubectl diff -f manifests/&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Upgrade order&lt;/td&gt;
&lt;td&gt;&lt;code&gt;etcd → control plane → nodes → addons → CRDs&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Pitfalls
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;CRD / Version Drift&lt;/strong&gt;&lt;br&gt;
CRDs registered via Helm or Operators may pin old API versions (e.g., apiextensions.k8s.io/v1beta1).&lt;br&gt;
→ Always &lt;code&gt;kubectl get crd&lt;/code&gt; and check &lt;code&gt;.spec.versions&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Admission Policy Surprises&lt;/strong&gt;&lt;br&gt;
Mutating / Validating webhooks compiled against old libraries can block Pods post-upgrade.&lt;br&gt;
→ Audit webhooks with &lt;code&gt;kubectl get validatingwebhookconfigurations&lt;/code&gt; and test dry-runs in staging.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Etcd Storage Version Drift&lt;/strong&gt;&lt;br&gt;
Even after API migrated, etcd may still store old serialized versions.&lt;br&gt;
→ Use &lt;code&gt;kube-storage-version-migrator&lt;/code&gt; to reconcile.&lt;/p&gt;




&lt;h2&gt;
  
  
  Diagram — Upgrade Workflow
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp9da3kesqaq330l8rsvl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp9da3kesqaq330l8rsvl.png" alt="diagram-one" width="292" height="550"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  ✅ Wrap-Up
&lt;/h2&gt;

&lt;p&gt;Upgrades are not a sprint—they’re &lt;strong&gt;a cadence&lt;/strong&gt;.&lt;br&gt;
Each minor version should move through your environments with &lt;strong&gt;predictable rhythm, pre-validation, and rollback clarity.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Next in the series (&lt;strong&gt;Post 4&lt;/strong&gt;) → &lt;em&gt;Smart Scaling &amp;amp; Cost Control&lt;/em&gt;: HPA/KEDA + Cluster Autoscaler vs Karpenter.&lt;/p&gt;




</description>
      <category>devops</category>
      <category>programming</category>
      <category>kubernetes</category>
    </item>
    <item>
      <title>Post 2/10 — Reliability by Design: Probes, PodDisruptionBudgets, and Topology Spread Constraints</title>
      <dc:creator>cloud-sky-ops</dc:creator>
      <pubDate>Sun, 05 Oct 2025 02:37:00 +0000</pubDate>
      <link>https://dev.to/cloud-sky-ops/post-210-reliability-by-design-probes-poddisruptionbudgets-and-topology-spread-constraints-473d</link>
      <guid>https://dev.to/cloud-sky-ops/post-210-reliability-by-design-probes-poddisruptionbudgets-and-topology-spread-constraints-473d</guid>
      <description>&lt;p&gt;Now you’ve laid a baseline of namespace isolation, quotas, network policy, and PSA (if not, see &lt;a href="https://dev.to/cloud-sky-ops/post-110-multi-tenancy-security-baseline-with-namespaces-quotas-networkpolicies-and-pod-2mfj"&gt;Post 1&lt;/a&gt; of the series), the next layer is &lt;strong&gt;reliability&lt;/strong&gt;. In this post I walk you through the key Kubernetes primitives that help your workloads &lt;em&gt;survive disruptions&lt;/em&gt; and &lt;em&gt;evolve safely&lt;/em&gt;: probes, PDBs, topology constraints, and rollout strategies. This blog dives deeper into these nuanced offerings by Kubernetes, buckle up for some fun, hope you enjoy the ride.&lt;/p&gt;




&lt;h2&gt;
  
  
  Executive Summary
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;strong&gt;liveness, readiness, and startup probes&lt;/strong&gt; to let Kubernetes detect and recover from unhealthy application states.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;PodDisruptionBudget (PDB)&lt;/strong&gt; ensures voluntary disruptions (e.g. node drain, rolling upgrades) don’t violate your availability SLO.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TopologySpreadConstraints&lt;/strong&gt; force pods to be balanced across failure domains (zones, nodes) to reduce blast radius.&lt;/li&gt;
&lt;li&gt;Carefully configure &lt;strong&gt;rollout strategies&lt;/strong&gt; (surge, maxUnavailable) in your Deployment to control downtime vs speed.&lt;/li&gt;
&lt;li&gt;Together, these tools let you design reliability from the start—preventing cascading failures rather than firefighting.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Prereqs
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;You already have a Kubernetes cluster with &lt;code&gt;kubectl&lt;/code&gt; access (as assumed in Post 1).&lt;/li&gt;
&lt;li&gt;You have an existing Deployment (or create one) that you can modify.&lt;/li&gt;
&lt;li&gt;You have at least two nodes (ideally in different zones or failure domains) to test spread constraints.&lt;/li&gt;
&lt;li&gt;You can cordon/drain nodes (&lt;code&gt;kubectl drain&lt;/code&gt;) to simulate disruption.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Concepts
&lt;/h2&gt;

&lt;h3&gt;
  
  
  A. Probes: Liveness / Readiness / Startup
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Definition:&lt;/strong&gt; Probes are periodic checks (HTTP, TCP, exec) that Kubernetes makes into containers to detect their health or readiness. Without them, a stuck process can stay “Running” indefinitely, and traffic may go to unhealthy pods.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Best practices:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Always include &lt;strong&gt;readiness&lt;/strong&gt; in your services so endpoints only include truly ready pods.&lt;/li&gt;
&lt;li&gt;Use &lt;strong&gt;startup&lt;/strong&gt; probe for apps with long initialization (so liveness doesn’t kill them prematurely).&lt;/li&gt;
&lt;li&gt;Be conservative: gentle probe intervals and timeouts to avoid false negatives under GC / background load.&lt;/li&gt;
&lt;li&gt;Test locally to find thresholds under load.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Commands / YAML snippets:&lt;/strong&gt;&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;readinessProbe&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;httpGet&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;/health&lt;/span&gt;
    &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8080&lt;/span&gt;
  &lt;span class="na"&gt;initialDelaySeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
  &lt;span class="na"&gt;periodSeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
  &lt;span class="na"&gt;timeoutSeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;

&lt;span class="na"&gt;livenessProbe&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;httpGet&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;/live&lt;/span&gt;
    &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8080&lt;/span&gt;
  &lt;span class="na"&gt;initialDelaySeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;15&lt;/span&gt;
  &lt;span class="na"&gt;periodSeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
  &lt;span class="na"&gt;timeoutSeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;

&lt;span class="na"&gt;startupProbe&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;httpGet&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;/ready&lt;/span&gt;
    &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8080&lt;/span&gt;
  &lt;span class="na"&gt;failureThreshold&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30&lt;/span&gt;
  &lt;span class="na"&gt;periodSeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Before → After:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Before:&lt;/strong&gt; Pod is “Running” perpetually, even if app crashes internally; Service routes traffic to a dead process.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;After:&lt;/strong&gt; Kubernetes restarts the pod automatically (liveness), and the pod is only added to load via readiness when healthy.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;When to use:&lt;/strong&gt; Always for production; startup probes if your app has long boot phases.&lt;/p&gt;




&lt;h3&gt;
  
  
  B. PodDisruptionBudget (PDB)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Definition:&lt;/strong&gt; A PDB is a policy that defines the minimum number (or fraction) of pods that must remain available during voluntary disruptions. Ensures your system doesn’t accidentally violate availability during upgrades, node drains, or autoscaling events.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Best practices:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;minAvailable&lt;/code&gt; when you want a floor on availability, or &lt;code&gt;maxUnavailable&lt;/code&gt; for a cap on disruption.&lt;/li&gt;
&lt;li&gt;Don’t set them too tight (you might block your own updates). Leave wiggle room.&lt;/li&gt;
&lt;li&gt;Align PDBs with your &lt;strong&gt;rolling update strategy&lt;/strong&gt; (surge / unavailable) to avoid deadlocks.&lt;/li&gt;
&lt;li&gt;Monitor PDB status (&lt;code&gt;kubectl get pdb&lt;/code&gt;) to detect stuck updates.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Commands / YAML snippet:&lt;/strong&gt;&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;policy/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;PodDisruptionBudget&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;my-app-pdb&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;minAvailable&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-app&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Before → After:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Before:&lt;/strong&gt; During a node drain, all pods could be disrupted and cause downtime.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;After:&lt;/strong&gt; Only one pod can be evicted at a time, preserving minimal availability.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;When to use:&lt;/strong&gt; For any service with more than one replica; optional for batch jobs but still beneficial.&lt;/p&gt;




&lt;h3&gt;
  
  
  C. TopologySpreadConstraints
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Definition:&lt;/strong&gt; A declaration in pod spec that controls how Kubernetes spreads pods across failure domains (nodes, zones) to enforce balance. Avoid overconcentration: if one zone or node goes down, you don’t lose your entire workload.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Best practices:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use well-known node labels: e.g. &lt;code&gt;topology.kubernetes.io/zone&lt;/code&gt; or &lt;code&gt;kubernetes.io/hostname&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;maxSkew = 1&lt;/code&gt; is a typical starting point (difference &amp;lt;=1 pod across domains).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;whenUnsatisfiable&lt;/code&gt;: use &lt;code&gt;DoNotSchedule&lt;/code&gt; for strict spreading or &lt;code&gt;ScheduleAnyway&lt;/code&gt; for softer enforcement.&lt;/li&gt;
&lt;li&gt;Use the same spread constraints on all revisions of your Deployment to maintain consistency.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Commands / YAML snippet:&lt;/strong&gt;&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;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;topologySpreadConstraints&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;maxSkew&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
    &lt;span class="na"&gt;topologyKey&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;topology.kubernetes.io/zone&lt;/span&gt;
    &lt;span class="na"&gt;whenUnsatisfiable&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DoNotSchedule&lt;/span&gt;
    &lt;span class="na"&gt;labelSelector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-app&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;maxSkew&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
    &lt;span class="na"&gt;topologyKey&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kubernetes.io/hostname&lt;/span&gt;
    &lt;span class="na"&gt;whenUnsatisfiable&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DoNotSchedule&lt;/span&gt;
    &lt;span class="na"&gt;labelSelector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-app&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Before → After:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Before:&lt;/strong&gt; All pods end up in one zone or node (fastest node, better resources).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;After:&lt;/strong&gt; Pods evenly distributed across zones/nodes, reducing risk if one fails.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;When to use:&lt;/strong&gt; For replicated workloads in HA setups, especially multi-zone or multi-node clusters.&lt;/p&gt;




&lt;h3&gt;
  
  
  D. Rollout Strategies (Surge / maxUnavailable)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Definition:&lt;/strong&gt; In a Deployment’s &lt;code&gt;strategy.rollingUpdate&lt;/code&gt;, &lt;code&gt;maxSurge&lt;/code&gt; is how many extra pods can be created during upgrade; &lt;code&gt;maxUnavailable&lt;/code&gt; is how many pods are allowed to drop at once. They control the trade-off between speed and availability during upgrades.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Best practices:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;maxUnavailable: 0&lt;/code&gt; and &lt;code&gt;maxSurge: 1&lt;/code&gt; (or more) for zero-downtime (if resources allow).&lt;/li&gt;
&lt;li&gt;For batch or low-priority workloads, allow some unavailability for faster rollout (e.g. 20–30%).&lt;/li&gt;
&lt;li&gt;Always test with your PDB + spread settings to ensure upgrade doesn’t stall.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Commands / YAML snippet:&lt;/strong&gt;&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;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;RollingUpdate&lt;/span&gt;
  &lt;span class="na"&gt;rollingUpdate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;maxSurge&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
    &lt;span class="na"&gt;maxUnavailable&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Before → After:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Before:&lt;/strong&gt; Default 25% surge/unavailable may drop too many pods at once, violating SLO during high load.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;After:&lt;/strong&gt; You control how many can be updated and how many remain live.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;When to use:&lt;/strong&gt; Always set explicitly rather than relying on defaults; vary per workload type.&lt;/p&gt;




&lt;h2&gt;
  
  
  Mini-Lab
&lt;/h2&gt;

&lt;p&gt;Let’s walk through building an app that has probes, PDB, and topology spread constraints. Then simulate node disruption and see your reliability in action.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Namespace + sample deployment
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl create ns reliability-demo
kubectl config set-context &lt;span class="nt"&gt;--current&lt;/span&gt; &lt;span class="nt"&gt;--namespace&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;reliability-demo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Deploy a simple app (e.g. HTTP echo) with 3 replicas:&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;apps/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;Deployment&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;echo&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;echo&lt;/span&gt; &lt;span class="pi"&gt;}&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;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;echo&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&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;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;echo&lt;/span&gt; &lt;span class="pi"&gt;}&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;hashicorp/http-echo:0.2.3&lt;/span&gt;
        &lt;span class="na"&gt;args&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;-text=hello"&lt;/span&gt;
        &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5678&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; echo.yaml
kubectl get pods &lt;span class="nt"&gt;-o&lt;/span&gt; wide
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Add probes, PDB, and topology constraints
&lt;/h3&gt;

&lt;p&gt;Edit the above spec:&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="c1"&gt;# inside spec.template.spec.containers[0]:&lt;/span&gt;
        &lt;span class="na"&gt;readinessProbe&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;httpGet&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;/health&lt;/span&gt;
            &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5678&lt;/span&gt;
          &lt;span class="na"&gt;initialDelaySeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
          &lt;span class="na"&gt;periodSeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
        &lt;span class="na"&gt;livenessProbe&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;httpGet&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;/live&lt;/span&gt;
            &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5678&lt;/span&gt;
          &lt;span class="na"&gt;initialDelaySeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
          &lt;span class="na"&gt;periodSeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
&lt;span class="c1"&gt;# Also add at spec&lt;/span&gt;
&lt;span class="err"&gt;  &lt;/span&gt;&lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;RollingUpdate&lt;/span&gt;
    &lt;span class="na"&gt;rollingUpdate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;maxSurge&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
      &lt;span class="na"&gt;maxUnavailable&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
  &lt;span class="na"&gt;topologySpreadConstraints&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;maxSkew&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
    &lt;span class="na"&gt;topologyKey&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;topology.kubernetes.io/zone&lt;/span&gt;
    &lt;span class="na"&gt;whenUnsatisfiable&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DoNotSchedule&lt;/span&gt;
    &lt;span class="na"&gt;labelSelector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;echo&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;maxSkew&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
    &lt;span class="na"&gt;topologyKey&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kubernetes.io/hostname&lt;/span&gt;
    &lt;span class="na"&gt;whenUnsatisfiable&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DoNotSchedule&lt;/span&gt;
    &lt;span class="na"&gt;labelSelector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;echo&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a PDB:&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;policy/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;PodDisruptionBudget&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;echo-pdb&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;minAvailable&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;echo&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Apply both:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; modified-echo.yaml
kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; pdb-echo.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check status:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get pods &lt;span class="nt"&gt;-o&lt;/span&gt; wide
kubectl get pdb
kubectl describe pdb echo-pdb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Simulate node drain and verify behavior
&lt;/h3&gt;

&lt;p&gt;Pick one node:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;NODE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;kubectl get nodes &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;jsonpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'{.items[0].metadata.name}'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
kubectl cordon &lt;span class="nv"&gt;$NODE&lt;/span&gt;
kubectl drain &lt;span class="nv"&gt;$NODE&lt;/span&gt; &lt;span class="nt"&gt;--ignore-daemonsets&lt;/span&gt; &lt;span class="nt"&gt;--delete-emptydir-data&lt;/span&gt; &lt;span class="nt"&gt;--disable-eviction&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Watch pods:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get pods &lt;span class="nt"&gt;-o&lt;/span&gt; wide &lt;span class="nt"&gt;-w&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Expectations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One pod evicted (PDB ensures minAvailable 2 remain).&lt;/li&gt;
&lt;li&gt;New pods rescheduled onto other nodes (respecting topology constraints).&lt;/li&gt;
&lt;li&gt;No downtime for readiness endpoints if requests route to healthy pods.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Restore node:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl uncordon &lt;span class="nv"&gt;$NODE&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Bonus: Trigger a rollout:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl &lt;span class="nb"&gt;set &lt;/span&gt;image deployment/echo &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;hashicorp/http-echo:0.3.0
kubectl rollout status deployment/echo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Observe that upgrades are safe, respecting availability and spread.&lt;/p&gt;




&lt;h2&gt;
  
  
  Cheat Sheet Table
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;th&gt;Command / YAML&lt;/th&gt;
&lt;th&gt;Purpose / Note&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Add readiness probe&lt;/td&gt;
&lt;td&gt;see snippet above&lt;/td&gt;
&lt;td&gt;Ensures pod is only traffic-ready when healthy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Add liveness probe&lt;/td&gt;
&lt;td&gt;see snippet above&lt;/td&gt;
&lt;td&gt;Restarts stuck pods&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Add startup probe&lt;/td&gt;
&lt;td&gt;similar pattern as above&lt;/td&gt;
&lt;td&gt;Prevents liveness kill during slow init&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Create PDB&lt;/td&gt;
&lt;td&gt;&lt;code&gt;kubectl apply -f pdb.yaml&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Enforce minimal availability&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Check PDB&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;kubectl get pdb&lt;/code&gt; / &lt;code&gt;kubectl describe pdb&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Confirm eviction limits&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Set rollout strategy&lt;/td&gt;
&lt;td&gt;&lt;code&gt;strategy.rollingUpdate&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Control surge / downtime&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Drain a node&lt;/td&gt;
&lt;td&gt;&lt;code&gt;kubectl drain &amp;lt;node&amp;gt; --ignore-daemonsets&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Simulate voluntary disruption&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Check rollout&lt;/td&gt;
&lt;td&gt;&lt;code&gt;kubectl rollout status deployment/&amp;lt;name&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Wait for safe update&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;View pod distribution&lt;/td&gt;
&lt;td&gt;&lt;code&gt;kubectl get pods -o wide&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Inspect zone/node distribution&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Pitfalls &amp;amp; Gotchas
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Misconfigured probes kill healthy pods / false positives.&lt;/strong&gt;&lt;br&gt;
Probe timeouts too aggressive lead to unnecessary restarts. Tune after load testing.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;PDB deadlocks your upgrades.&lt;/strong&gt;&lt;br&gt;
If your PDB demands &lt;code&gt;minAvailable = replicas&lt;/code&gt;, plus you set &lt;code&gt;maxUnavailable = 0&lt;/code&gt; and &lt;code&gt;maxSurge = 0&lt;/code&gt;, your rollout cannot make progress. Always allow &lt;em&gt;some&lt;/em&gt; headroom (e.g. &lt;code&gt;maxSurge: 1&lt;/code&gt;) or loosen PDB.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Skew violations during rolling updates.&lt;/strong&gt;&lt;br&gt;
Spread constraints sometimes misbehave during updates: the scheduler considers old and new pods together in balancing, so you may temporarily skew. Use &lt;code&gt;ScheduleAnyway&lt;/code&gt; as fallback or trigger rescheduling.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Asymmetric zones or resource imbalance.&lt;/strong&gt;&lt;br&gt;
If one zone has less capacity, strict constraints may block scheduling. Use a softer &lt;code&gt;whenUnsatisfiable: ScheduleAnyway&lt;/code&gt; or allow some skew.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Spread constraints don’t rebalance after scale-down.&lt;/strong&gt;&lt;br&gt;
When pods are removed, existing pods may end up unevenly distributed. Use a Descheduler or manual intervention to rebalance.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Startup probe too permissive or missing disables liveness protection.&lt;/strong&gt;&lt;br&gt;
Without startup probe, a slow boot may trigger liveness failure. With one, if too lenient, you delay detecting broken pods.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Wrap-up &amp;amp; Bridge to Post 3
&lt;/h2&gt;

&lt;p&gt;With probes, PDBs, topology spread, and rollout control, you now have a &lt;strong&gt;robust reliability foundation&lt;/strong&gt;. Your services will survive node drains, upgrades, and zone outages while satisfying availability SLOs.&lt;/p&gt;

&lt;p&gt;In &lt;strong&gt;Post 3&lt;/strong&gt;, we’ll build on this: &lt;strong&gt;version upgrades, canary/blue-green deployments, cluster upgrades, and rollback strategies&lt;/strong&gt;, so you can evolve your system safely under load.&lt;/p&gt;




&lt;h3&gt;
  
  
  Diagram 1 — Rolling update timeline
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fikl5kz1eruwg51f5bq0y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fikl5kz1eruwg51f5bq0y.png" alt="Diagram 1" width="800" height="463"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Diagram 2 — Zone spread visualization
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqg5cygkeeuobxwx11t8u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqg5cygkeeuobxwx11t8u.png" alt="Diagram 2" width="800" height="477"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Drop a comment if you learned something new and share your thoughts. Thank You.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>devops</category>
      <category>learning</category>
      <category>programming</category>
    </item>
    <item>
      <title>Mastering GitHub Actions: Insights and pitfalls of "if conditions"</title>
      <dc:creator>cloud-sky-ops</dc:creator>
      <pubDate>Wed, 01 Oct 2025 21:28:00 +0000</pubDate>
      <link>https://dev.to/cloud-sky-ops/mastering-github-actions-insights-and-pitfalls-of-if-conditions-1j87</link>
      <guid>https://dev.to/cloud-sky-ops/mastering-github-actions-insights-and-pitfalls-of-if-conditions-1j87</guid>
      <description>&lt;p&gt;As a DevOps engineer, I live inside CI/CD pipelines. GitHub Actions has become one of my go-to tools, and I’ve spent countless hours tweaking workflows, battling edge cases, and learning lessons the hard way. One of the powerful offering of Actions is the use of &lt;code&gt;if&lt;/code&gt; conditions on jobs and steps.&lt;/p&gt;

&lt;p&gt;In this post, I’ll walk you through the main &lt;code&gt;if&lt;/code&gt; conditions available, share a real-world scenario where things went sideways for me, and explain how I fixed it. Hopefully, this saves you from a few hours of frustration the next time you’re wiring up your own pipelines.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Essential &lt;code&gt;if&lt;/code&gt; Conditions in GitHub Actions
&lt;/h2&gt;

&lt;p&gt;Here are the most common conditional expressions you’ll run into:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;if: success()&lt;/code&gt;&lt;/strong&gt; – Runs the job only if all dependencies succeeded.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;if: failure()&lt;/code&gt;&lt;/strong&gt; – Runs the job only if at least one dependency failed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;if: cancelled()&lt;/code&gt;&lt;/strong&gt; – Runs the job only if a dependency was cancelled.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;if: always()&lt;/code&gt;&lt;/strong&gt; – Runs the job regardless of the success/failure of dependencies. The catch with always is that it will also run the job is the workflow is cancelled by the user. So always means &lt;strong&gt;ALWAYS&lt;/strong&gt;, no exceptions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Due to this, it safer (and recommended by GitHub) to use the following option:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;if: !cancelled()&lt;/code&gt;&lt;/strong&gt; – Runs the job unless a dependency was cancelled (my personal favorite fallback for robust pipelines).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzbli8dvcqa0zrqu1iajf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzbli8dvcqa0zrqu1iajf.png" alt="recommendation" width="800" height="301"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These look straightforward, but once you combine them with &lt;code&gt;needs:&lt;/code&gt; and multi trigger workflows, like I did, a real quirks emerges.&lt;/p&gt;




&lt;h2&gt;
  
  
  When &lt;code&gt;if: ${{ !cancelled() }}&lt;/code&gt; Bit Me Back
&lt;/h2&gt;

&lt;p&gt;I was working on a CI/CD workflow that needed to support two types of pushes: to &lt;code&gt;main&lt;/code&gt; and to &lt;code&gt;release/*&lt;/code&gt; branches. The requirement was simple enough, there were 4 jobs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;run-test-cases&lt;/code&gt;&lt;/strong&gt;: runs for all branches.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;run-script-on-main&lt;/code&gt;&lt;/strong&gt;: runs only when the branch is &lt;code&gt;main&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;docker-build-push&lt;/code&gt;&lt;/strong&gt;: should run on both branches. On &lt;code&gt;main&lt;/code&gt;, it must wait for &lt;code&gt;run-script-on-main&lt;/code&gt; to complete. On &lt;code&gt;release/*&lt;/code&gt;, it should still run regardless of the &lt;code&gt;run-script-on-main&lt;/code&gt; job being skipped&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;trigger-deployment&lt;/code&gt;&lt;/strong&gt;: runs after &lt;code&gt;docker-build-push&lt;/code&gt; (and &lt;code&gt;run-test-cases&lt;/code&gt;) for both &lt;code&gt;main&lt;/code&gt; and &lt;code&gt;release/*&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here’s the workflow:&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;CI/CD Pipeline&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;release/*&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;run-test-cases&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./scripts/run-tests.sh&lt;/span&gt;

  &lt;span class="na"&gt;run-script-on-main&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.ref == 'refs/heads/main'&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./scripts/main-only.sh&lt;/span&gt;

  &lt;span class="na"&gt;docker-build-push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;run-test-cases&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;run-script-on-main&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ !cancelled() }}&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./scripts/docker-build-push.sh&lt;/span&gt;

  &lt;span class="na"&gt;trigger-deployment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;run-test-cases&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;docker-build-push&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./scripts/trigger-deployment.sh&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Problem
&lt;/h3&gt;

&lt;p&gt;What I expected:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;On &lt;code&gt;main&lt;/code&gt;: &lt;code&gt;run-script-on-main&lt;/code&gt; runs, then &lt;code&gt;docker-build-push&lt;/code&gt;, then &lt;code&gt;trigger-deployment&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;On &lt;code&gt;release/*&lt;/code&gt;: skip &lt;code&gt;run-script-on-main&lt;/code&gt;, run &lt;code&gt;docker-build-push&lt;/code&gt;, then &lt;code&gt;trigger-deployment&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What actually happened:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;On &lt;code&gt;main&lt;/code&gt;: ✅ everything worked fine.&lt;/li&gt;
&lt;li&gt;On &lt;code&gt;release/*&lt;/code&gt;: ❌ &lt;code&gt;docker-build-push&lt;/code&gt; ran, but &lt;code&gt;trigger-deployment&lt;/code&gt; was skipped.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This was confusing to me because &lt;code&gt;trigger-deployment&lt;/code&gt; didn’t even depend on &lt;code&gt;run-script-on-main&lt;/code&gt; (using needs section).&lt;/p&gt;




&lt;h2&gt;
  
  
  DAG Construction &amp;amp; Pruning (root cause)
&lt;/h2&gt;

&lt;p&gt;In GitHub Actions, the entire workflow is modeled as a &lt;strong&gt;Directed Acyclic Graph (DAG)&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Directed&lt;/strong&gt;: The flow of execution moves in one direction, from dependencies to dependents.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Acyclic&lt;/strong&gt;: There are no cycles; a job cannot depend on itself (directly or indirectly).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Graph&lt;/strong&gt;: Jobs are nodes, and &lt;code&gt;needs:&lt;/code&gt; defines the edges (dependencies) between them.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This DAG determines the exact order in which jobs are executed, skipped, or pruned. GitHub Actions builds the DAG at workflow parsing time &lt;em&gt;before&lt;/em&gt; it executes any jobs.&lt;/p&gt;




&lt;h3&gt;
  
  
  Example
&lt;/h3&gt;

&lt;p&gt;Imagine three jobs:&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;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;A&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;echo "Job A"&lt;/span&gt;

  &lt;span class="na"&gt;B&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;A&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;echo "Job B"&lt;/span&gt;

  &lt;span class="na"&gt;C&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;A&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;B&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;echo "Job C"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The DAG looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A → B → C
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;A&lt;/code&gt; runs first.&lt;/li&gt;
&lt;li&gt;Once &lt;code&gt;A&lt;/code&gt; succeeds, &lt;code&gt;B&lt;/code&gt; can run.&lt;/li&gt;
&lt;li&gt;Once both &lt;code&gt;A&lt;/code&gt; and &lt;code&gt;B&lt;/code&gt; succeed, &lt;code&gt;C&lt;/code&gt; can run.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If &lt;code&gt;A&lt;/code&gt; fails, both &lt;code&gt;B&lt;/code&gt; and &lt;code&gt;C&lt;/code&gt; are automatically skipped because their prerequisites were never satisfied.&lt;/p&gt;




&lt;h3&gt;
  
  
  Practical Example in GitHub Workflows
&lt;/h3&gt;

&lt;p&gt;Take a workflow where &lt;code&gt;lint&lt;/code&gt; and &lt;code&gt;unit-tests&lt;/code&gt; must run in parallel, and &lt;code&gt;build&lt;/code&gt; depends on both.&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;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;lint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm run lint&lt;/span&gt;

  &lt;span class="na"&gt;unit-tests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm test&lt;/span&gt;

  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;lint&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;unit-tests&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm run build&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here the DAG is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[ lint &amp;amp;&amp;amp; build ] → unit-tests
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;If both &lt;code&gt;lint&lt;/code&gt; and &lt;code&gt;unit-tests&lt;/code&gt; succeed, &lt;code&gt;build&lt;/code&gt; executes.&lt;/li&gt;
&lt;li&gt;If either &lt;code&gt;lint&lt;/code&gt; or &lt;code&gt;unit-tests&lt;/code&gt; fails (or gets skipped), &lt;code&gt;build&lt;/code&gt; won’t run unless you explicitly allow it with &lt;code&gt;if: always()&lt;/code&gt; or &lt;code&gt;if: !cancelled()&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  DAG in Practice: Why It Matters
&lt;/h3&gt;

&lt;p&gt;The problem I hit with &lt;code&gt;if: ${{ !cancelled() }}&lt;/code&gt; happened &lt;em&gt;because DAG pruning is strict&lt;/em&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When a job is skipped, it’s not just “ignored”—it still exists as a node in the DAG.&lt;/li&gt;
&lt;li&gt;Any job that &lt;code&gt;needs&lt;/code&gt; it inherits that skipped state unless conditions explicitly override it.&lt;/li&gt;
&lt;li&gt;That’s why my &lt;code&gt;trigger-deployment&lt;/code&gt; got skipped even though its direct needs (&lt;code&gt;docker-build-push&lt;/code&gt;, &lt;code&gt;run-test-cases&lt;/code&gt;) were satisfied: GitHub propagated the “skipped upstream” status through the graph.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;👉 Understanding DAGs is the key to predicting workflow behavior in GitHub Actions. If you model jobs as a dependency graph instead of thinking in linear “step order,” it becomes much clearer why some jobs run, skip, or silently disappear.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Fix
&lt;/h2&gt;

&lt;p&gt;After banging my head on figuring out the why, I started looking into the workarounds, I realized the simplest and most robust solution was to avoid making &lt;code&gt;docker-build-push&lt;/code&gt; depend on an optional job. Instead, I folded the branch-specific logic into a step:&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;docker-build-push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;run-test-cases&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
  &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./scripts/docker-build-push.sh&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.ref == 'refs/heads/main'&lt;/span&gt;
      &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./scripts/main-only.sh&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the dependency chain is clean:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No skipped optional jobs breaking the DAG.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;docker-build-push&lt;/code&gt; always runs.&lt;/li&gt;
&lt;li&gt;On &lt;code&gt;main&lt;/code&gt;, the extra script runs as a conditional step.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This avoided the skipped-job propagation entirely and made the workflow behave exactly as intended.&lt;/p&gt;




&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Be cautious with &lt;code&gt;needs:&lt;/code&gt; when optional jobs are involved.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;if: ${{ !cancelled() }}&lt;/code&gt; is safer than &lt;code&gt;if: always()&lt;/code&gt;, but it can still create tricky dependency issues.&lt;/li&gt;
&lt;li&gt;When in doubt, prefer conditional steps inside a single job over branching jobs with &lt;code&gt;if:&lt;/code&gt;. It keeps the DAG simpler and avoids hidden propagation quirks.&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;In the end, this was another lesson in the “everything is a DAG” world of GitHub Actions. The more predictable your graph, the fewer surprises you’ll get down the road. Thanks for making it till the end. Do share your thoughts and any alternate approaches on the solution. Have a beautiful day.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>githubactions</category>
      <category>github</category>
    </item>
    <item>
      <title>Post 1/10 — Multi-Tenancy &amp; Security Baseline with Namespaces, Quotas, NetworkPolicies, and Pod Security Admission</title>
      <dc:creator>cloud-sky-ops</dc:creator>
      <pubDate>Sat, 27 Sep 2025 22:26:21 +0000</pubDate>
      <link>https://dev.to/cloud-sky-ops/post-110-multi-tenancy-security-baseline-with-namespaces-quotas-networkpolicies-and-pod-2mfj</link>
      <guid>https://dev.to/cloud-sky-ops/post-110-multi-tenancy-security-baseline-with-namespaces-quotas-networkpolicies-and-pod-2mfj</guid>
      <description>&lt;p&gt;&lt;em&gt;Author: A senior DevOps engineer who’s broken (and fixed) too many clusters so you don’t have to.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Executive Summary
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Carve tenants with Namespaces&lt;/strong&gt; to isolate RBAC, policies, and quotas per team.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enforce fair sharing with ResourceQuota + LimitRange&lt;/strong&gt; so noisy neighbors can’t starve the cluster.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lock down traffic with NetworkPolicy&lt;/strong&gt;: start with default-deny, then allow the minimum (DNS, app→DB).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Harden workloads with Pod Security Admission (PSA)&lt;/strong&gt; to block privileged/unsafe pod specs at the namespace boundary.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ship a repeatable baseline&lt;/strong&gt;: two team namespaces, quotas/limits, default-deny + specific allows, PSA &lt;code&gt;restricted&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Prereqs
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A Kubernetes cluster (≥ &lt;strong&gt;v1.28&lt;/strong&gt; recommended) and &lt;code&gt;kubectl&lt;/code&gt; configured.&lt;/li&gt;
&lt;li&gt;Cluster has &lt;strong&gt;CNI that supports NetworkPolicy&lt;/strong&gt; (Calico, Cilium, Antrea, etc.).&lt;/li&gt;
&lt;li&gt;Helm optional (not required here).&lt;/li&gt;
&lt;li&gt;You have cluster-admin for initial setup.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl version &lt;span class="nt"&gt;--short&lt;/span&gt;
kubectl get nodes &lt;span class="nt"&gt;-o&lt;/span&gt; wide
kubectl get pods &lt;span class="nt"&gt;-n&lt;/span&gt; kube-system
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Concepts
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1) Namespaces for isolation
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Definition:&lt;/strong&gt; Namespaces slice a cluster into logical tenants with independent RBAC, quotas, and policies. Prevents accidental cross-team impact, scopes access and makes policy application simple (&lt;code&gt;kubectl label ns&lt;/code&gt; once).&lt;br&gt;
&lt;strong&gt;Best practices:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One namespace per &lt;strong&gt;team/app-tier&lt;/strong&gt;, not per environment object (use labels like &lt;code&gt;env=prod&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Name predictably: &lt;code&gt;team-a&lt;/code&gt;, &lt;code&gt;team-b&lt;/code&gt;, &lt;code&gt;platform&lt;/code&gt;, etc.&lt;/li&gt;
&lt;li&gt;Attach &lt;strong&gt;PSA labels&lt;/strong&gt;, &lt;strong&gt;default network policies&lt;/strong&gt;, and &lt;strong&gt;quotas&lt;/strong&gt; at namespace creation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Commands:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl create ns team-a
kubectl create ns team-b
kubectl label ns team-a &lt;span class="nb"&gt;env&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;dev &lt;span class="nt"&gt;--overwrite&lt;/span&gt;
kubectl get ns &lt;span class="nt"&gt;--show-labels&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Before → After:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Before:&lt;/strong&gt; All pods in &lt;code&gt;default&lt;/code&gt;, broad access, hard to apply policies.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;After:&lt;/strong&gt; &lt;code&gt;team-a/&lt;/code&gt;, &lt;code&gt;team-b/&lt;/code&gt; with their own quotas, PSA, and network baselines.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;When to use:&lt;/strong&gt; Always—namespaces are table stakes for multi-tenancy.&lt;/p&gt;




&lt;h3&gt;
  
  
  2) ResourceQuota &amp;amp; LimitRange
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Definition:&lt;/strong&gt; &lt;code&gt;ResourceQuota&lt;/code&gt; caps &lt;strong&gt;aggregate usage&lt;/strong&gt; per namespace; &lt;code&gt;LimitRange&lt;/code&gt; sets &lt;strong&gt;per-pod/container defaults and maxima&lt;/strong&gt;. Stops runaway resource grabs and ensures every pod has sensible requests/limits for scheduling and stability.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best practices:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pair them: &lt;strong&gt;RQ&lt;/strong&gt; for team-level ceilings; &lt;strong&gt;LR&lt;/strong&gt; for sane per-workload defaults.&lt;/li&gt;
&lt;li&gt;Set &lt;strong&gt;CPU/memory requests+limits&lt;/strong&gt; and object counts (pods, PVCs, services).&lt;/li&gt;
&lt;li&gt;Include &lt;strong&gt;ephemeral-storage&lt;/strong&gt; where supported; keep room for rollouts (e.g., 20–30% headroom).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Commands:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-n&lt;/span&gt; team-a &lt;span class="nt"&gt;-f&lt;/span&gt; quota-team-a.yaml
kubectl get resourcequota &lt;span class="nt"&gt;-n&lt;/span&gt; team-a
kubectl describe limitrange &lt;span class="nt"&gt;-n&lt;/span&gt; team-a
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Before → After:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Before:&lt;/strong&gt; Pods without limits; one job consumes all CPU → others starve.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;After:&lt;/strong&gt; Each container gets defaults; namespace can’t exceed its slice.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;When to use:&lt;/strong&gt; Whenever multiple teams share nodes or costs matter (i.e., always).&lt;/p&gt;




&lt;h3&gt;
  
  
  3) NetworkPolicy
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Definition:&lt;/strong&gt; NetworkPolicy declares &lt;strong&gt;which pods may talk to which&lt;/strong&gt;, for ingress and egress. Prevents lateral movement, accidental chats between unrelated services, and data exfiltration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best practices:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Start with default-deny&lt;/strong&gt; for both ingress and egress.&lt;/li&gt;
&lt;li&gt;Then &lt;strong&gt;allow only what you need&lt;/strong&gt; (DNS 53, app→DB 5432, etc.).&lt;/li&gt;
&lt;li&gt;Use &lt;strong&gt;labels&lt;/strong&gt; consistently; avoid relying on IPs. Add &lt;strong&gt;namespaceSelector&lt;/strong&gt; + &lt;strong&gt;podSelector&lt;/strong&gt; for system services like CoreDNS.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Commands:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-n&lt;/span&gt; team-a &lt;span class="nt"&gt;-f&lt;/span&gt; np-default-deny.yaml
kubectl apply &lt;span class="nt"&gt;-n&lt;/span&gt; team-a &lt;span class="nt"&gt;-f&lt;/span&gt; np-allow-dns.yaml
kubectl apply &lt;span class="nt"&gt;-n&lt;/span&gt; team-a &lt;span class="nt"&gt;-f&lt;/span&gt; np-allow-app-to-db.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Before → After:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Before:&lt;/strong&gt; Any pod can connect to any pod/Internet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;After:&lt;/strong&gt; Only DNS + app→db allowed; everything else dropped.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;When to use:&lt;/strong&gt; In any regulated or multi-tenant cluster; also in prod by default.&lt;/p&gt;




&lt;h3&gt;
  
  
  4) Pod Security Admission (PSA)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Definition:&lt;/strong&gt; PSA enforces Kubernetes security profiles (&lt;code&gt;privileged&lt;/code&gt;, &lt;code&gt;baseline&lt;/code&gt;, &lt;code&gt;restricted&lt;/code&gt;) via &lt;strong&gt;namespace labels&lt;/strong&gt;. Blocks dangerous specs (privileged, hostPID/IPC, hostPath, capability escalation) &lt;strong&gt;before&lt;/strong&gt; pods land on nodes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best practices:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Default &lt;strong&gt;&lt;code&gt;restricted&lt;/code&gt; enforce&lt;/strong&gt; on app namespaces; use &lt;code&gt;baseline&lt;/code&gt; temporarily while migrating.&lt;/li&gt;
&lt;li&gt;Apply &lt;strong&gt;enforce&lt;/strong&gt;, &lt;strong&gt;warn&lt;/strong&gt;, and &lt;strong&gt;audit&lt;/strong&gt; labels during rollout to see breaks before enforcing.&lt;/li&gt;
&lt;li&gt;Keep images running as non-root; avoid broad capabilities and host volumes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Commands:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl label ns team-a &lt;span class="se"&gt;\&lt;/span&gt;
  pod-security.kubernetes.io/enforce&lt;span class="o"&gt;=&lt;/span&gt;restricted &lt;span class="se"&gt;\&lt;/span&gt;
  pod-security.kubernetes.io/enforce-version&lt;span class="o"&gt;=&lt;/span&gt;latest &lt;span class="se"&gt;\&lt;/span&gt;
  pod-security.kubernetes.io/warn&lt;span class="o"&gt;=&lt;/span&gt;restricted &lt;span class="se"&gt;\&lt;/span&gt;
  pod-security.kubernetes.io/warn-version&lt;span class="o"&gt;=&lt;/span&gt;latest &lt;span class="se"&gt;\&lt;/span&gt;
  pod-security.kubernetes.io/audit&lt;span class="o"&gt;=&lt;/span&gt;restricted &lt;span class="se"&gt;\&lt;/span&gt;
  pod-security.kubernetes.io/audit-version&lt;span class="o"&gt;=&lt;/span&gt;latest &lt;span class="nt"&gt;--overwrite&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Before → After:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Before:&lt;/strong&gt; Developers could deploy privileged pods or mount &lt;code&gt;/var/run/docker.sock&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;After:&lt;/strong&gt; Such pods are rejected at admission with a clear error.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;When to use:&lt;/strong&gt; Immediately after namespace creation; dev/prod alike (stricter in prod).&lt;/p&gt;




&lt;h2&gt;
  
  
  Diagram 1 — Namespace/Tenant Layout
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2mtf9ht2zezbh9v5ttfc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2mtf9ht2zezbh9v5ttfc.png" alt="Diagram-1" width="800" height="372"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Mini-Lab (≈25 min): Two tenants, quotas/limits, default-deny, app→db allow, PSA restricted
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;You can paste these as-is; tweak CPU/memory as needed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Create namespaces + PSA labels
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl create ns team-a
kubectl create ns team-b

&lt;span class="k"&gt;for &lt;/span&gt;ns &lt;span class="k"&gt;in &lt;/span&gt;team-a team-b&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;kubectl label ns &lt;span class="nv"&gt;$ns&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    pod-security.kubernetes.io/enforce&lt;span class="o"&gt;=&lt;/span&gt;restricted &lt;span class="se"&gt;\&lt;/span&gt;
    pod-security.kubernetes.io/enforce-version&lt;span class="o"&gt;=&lt;/span&gt;latest &lt;span class="se"&gt;\&lt;/span&gt;
    pod-security.kubernetes.io/warn&lt;span class="o"&gt;=&lt;/span&gt;restricted &lt;span class="se"&gt;\&lt;/span&gt;
    pod-security.kubernetes.io/warn-version&lt;span class="o"&gt;=&lt;/span&gt;latest &lt;span class="se"&gt;\&lt;/span&gt;
    pod-security.kubernetes.io/audit&lt;span class="o"&gt;=&lt;/span&gt;restricted &lt;span class="se"&gt;\&lt;/span&gt;
    pod-security.kubernetes.io/audit-version&lt;span class="o"&gt;=&lt;/span&gt;latest &lt;span class="nt"&gt;--overwrite&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Apply ResourceQuota + LimitRange
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;quota-team-a.yaml&lt;/code&gt;&lt;/strong&gt;&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;ResourceQuota&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;team-a-quota&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;hard&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;pods&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="na"&gt;requests.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;8"&lt;/span&gt;
    &lt;span class="na"&gt;requests.memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;16Gi&lt;/span&gt;
    &lt;span class="na"&gt;limits.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;16"&lt;/span&gt;
    &lt;span class="na"&gt;limits.memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;32Gi&lt;/span&gt;
    &lt;span class="na"&gt;persistentvolumeclaims&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;10"&lt;/span&gt;
    &lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;10"&lt;/span&gt;
    &lt;span class="na"&gt;services.loadbalancers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2"&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&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;LimitRange&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;team-a-defaults&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;limits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Container&lt;/span&gt;
    &lt;span class="na"&gt;defaultRequest&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;200m"&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;default&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;512Mi"&lt;/span&gt;
    &lt;span class="na"&gt;max&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;2"&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;2Gi"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Apply to both namespaces:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-n&lt;/span&gt; team-a &lt;span class="nt"&gt;-f&lt;/span&gt; quota-team-a.yaml
kubectl apply &lt;span class="nt"&gt;-n&lt;/span&gt; team-b &lt;span class="nt"&gt;-f&lt;/span&gt; quota-team-a.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Deploy a tiny app and a “db” in team-a
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;app-db.yaml&lt;/code&gt;&lt;/strong&gt;&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;apps/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;Deployment&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;db&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;db&lt;/span&gt; &lt;span class="pi"&gt;}&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;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;db&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;db&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt; &lt;span class="pi"&gt;}&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;db&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;postgres:16-alpine&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="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;dev&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;5432&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;pg&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&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;Service&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;db&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;db&lt;/span&gt; &lt;span class="pi"&gt;}&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;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;db&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;5432&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;targetPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;pg&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;TCP&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/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;Deployment&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;web&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;web&lt;/span&gt; &lt;span class="pi"&gt;}&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;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;web&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;web&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt; &lt;span class="pi"&gt;}&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;web&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;curlimages/curl:8.10.1&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;sleep"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;infinity"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-n&lt;/span&gt; team-a &lt;span class="nt"&gt;-f&lt;/span&gt; app-db.yaml
kubectl &lt;span class="nb"&gt;wait&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; team-a &lt;span class="nt"&gt;--for&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;condition&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;available deploy/web deploy/db &lt;span class="nt"&gt;--timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;90s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Smoke test (pre-policy, should connect):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;POD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;kubectl &lt;span class="nt"&gt;-n&lt;/span&gt; team-a get pod &lt;span class="nt"&gt;-l&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;web &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;jsonpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'{.items[0].metadata.name}'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
kubectl &lt;span class="nt"&gt;-n&lt;/span&gt; team-a &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$POD&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; sh &lt;span class="nt"&gt;-lc&lt;/span&gt; &lt;span class="s1"&gt;'curl -m2 db:5432 || true'&lt;/span&gt;
&lt;span class="c"&gt;# Expect: connection established or at least a TCP handshake banner (not blocked)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Enforce default-deny (ingress+egress) in team-a
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;np-default-deny.yaml&lt;/code&gt;&lt;/strong&gt;&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;networking.k8s.io/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;NetworkPolicy&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;default-deny-all&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;podSelector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{}&lt;/span&gt;        &lt;span class="c1"&gt;# select all pods&lt;/span&gt;
  &lt;span class="na"&gt;policyTypes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;Ingress&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;Egress&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-n&lt;/span&gt; team-a &lt;span class="nt"&gt;-f&lt;/span&gt; np-default-deny.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Test (should now fail):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl &lt;span class="nt"&gt;-n&lt;/span&gt; team-a &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$POD&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; sh &lt;span class="nt"&gt;-lc&lt;/span&gt; &lt;span class="s1"&gt;'curl -m2 db:5432 || echo BLOCKED'&lt;/span&gt;
&lt;span class="c"&gt;# Expect: BLOCKED / timeout&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4) Allow only DNS + app→db
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;np-allow-dns.yaml&lt;/code&gt;&lt;/strong&gt;&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;networking.k8s.io/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;NetworkPolicy&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;allow-dns&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;podSelector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;      &lt;span class="c1"&gt;# all pods may resolve DNS&lt;/span&gt;
  &lt;span class="na"&gt;policyTypes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;Egress&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;egress&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;namespaceSelector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;kubernetes.io/metadata.name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;kube-system&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;podSelector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;k8s-app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;kube-dns&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;UDP&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;53&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;TCP&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;53&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;np-db-ingress-from-web.yaml&lt;/code&gt;&lt;/strong&gt;&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;networking.k8s.io/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;NetworkPolicy&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;db-allow-from-web&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;podSelector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;db&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;policyTypes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;Ingress&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;ingress&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;podSelector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;web&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;TCP&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;5432&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;np-web-egress-to-db.yaml&lt;/code&gt;&lt;/strong&gt;&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;networking.k8s.io/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;NetworkPolicy&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;web-allow-to-db&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;podSelector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;web&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;policyTypes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;Egress&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;egress&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;podSelector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;db&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;TCP&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;5432&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-n&lt;/span&gt; team-a &lt;span class="nt"&gt;-f&lt;/span&gt; np-allow-dns.yaml &lt;span class="nt"&gt;-f&lt;/span&gt; np-db-ingress-from-web.yaml &lt;span class="nt"&gt;-f&lt;/span&gt; np-web-egress-to-db.yaml
kubectl &lt;span class="nt"&gt;-n&lt;/span&gt; team-a &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$POD&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; sh &lt;span class="nt"&gt;-lc&lt;/span&gt; &lt;span class="s1"&gt;'curl -m2 db:5432 || true'&lt;/span&gt;
&lt;span class="c"&gt;# Expect: succeeds (policy allows only web→db and DNS egress)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Repeat the same baseline (quotas/limits, default-deny, PSA labels) for &lt;code&gt;team-b&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Diagram 2 — NetworkPolicy Traffic Matrix
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvo021ux7rkn4jv499zln.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvo021ux7rkn4jv499zln.png" alt="Diagram-2" width="800" height="392"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  YAML &amp;amp; Bash Reference
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;PSA labels (namespace):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl label ns team-a pod-security.kubernetes.io/enforce&lt;span class="o"&gt;=&lt;/span&gt;restricted &lt;span class="nt"&gt;--overwrite&lt;/span&gt;
kubectl label ns team-a pod-security.kubernetes.io/&lt;span class="o"&gt;{&lt;/span&gt;enforce, warn, audit&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="nt"&gt;-version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;latest &lt;span class="nt"&gt;--overwrite&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;ResourceQuota &amp;amp; LimitRange:&lt;/strong&gt;&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;ResourceQuota&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;&amp;lt;ns&amp;gt;-quota&lt;/span&gt; &lt;span class="pi"&gt;}&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;hard&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;pods&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="na"&gt;requests.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;8"&lt;/span&gt;
    &lt;span class="na"&gt;requests.memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;16Gi&lt;/span&gt;
    &lt;span class="na"&gt;limits.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;16"&lt;/span&gt;
    &lt;span class="na"&gt;limits.memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;32Gi&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&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;LimitRange&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;&amp;lt;ns&amp;gt;-defaults&lt;/span&gt; &lt;span class="pi"&gt;}&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;limits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Container&lt;/span&gt;
    &lt;span class="na"&gt;defaultRequest&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&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;200m"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&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="pi"&gt;}&lt;/span&gt;
    &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;        &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&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="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&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="pi"&gt;}&lt;/span&gt;
    &lt;span class="na"&gt;max&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;            &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&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;2"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;    &lt;span class="nv"&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;2Gi"&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Default-deny + allows (template):&lt;/strong&gt; see &lt;code&gt;np-default-deny.yaml&lt;/code&gt;, &lt;code&gt;np-allow-dns.yaml&lt;/code&gt;, &lt;code&gt;np-db-ingress-from-web.yaml&lt;/code&gt;, &lt;code&gt;np-web-egress-to-db.yaml&lt;/code&gt; above.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Namespace-scoped context (handy):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl config set-context &lt;span class="nt"&gt;--current&lt;/span&gt; &lt;span class="nt"&gt;--namespace&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;team-a
&lt;span class="c"&gt;# or create a named context once:&lt;/span&gt;
kubectl config set-context team-a &lt;span class="nt"&gt;--cluster&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;kubectl config current-context&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="nt"&gt;--user&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;kubectl config view &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;jsonpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'{.contexts[?(@.name=="'&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;kubectl config current-context&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s1"&gt;'")].context.user}'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="nt"&gt;--namespace&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;team-a
kubectl config use-context team-a
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Cheatsheet Table
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Task&lt;/th&gt;
&lt;th&gt;Command / File&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Create namespace&lt;/td&gt;
&lt;td&gt;&lt;code&gt;kubectl create ns &amp;lt;name&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Add labels (&lt;code&gt;env=prod&lt;/code&gt;, PSA) immediately.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Label PSA restricted&lt;/td&gt;
&lt;td&gt;&lt;code&gt;kubectl label ns &amp;lt;name&amp;gt; pod-security.kubernetes.io/enforce=restricted --overwrite&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Add &lt;code&gt;warn&lt;/code&gt;/&lt;code&gt;audit&lt;/code&gt; to preview breaks.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Apply quotas/limits&lt;/td&gt;
&lt;td&gt;&lt;code&gt;kubectl apply -n &amp;lt;ns&amp;gt; -f quota-&amp;lt;ns&amp;gt;.yaml&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Pair &lt;code&gt;ResourceQuota&lt;/code&gt; with &lt;code&gt;LimitRange&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Check quota usage&lt;/td&gt;
&lt;td&gt;&lt;code&gt;kubectl describe resourcequota -n &amp;lt;ns&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Watch &lt;code&gt;Used&lt;/code&gt; vs &lt;code&gt;Hard&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Default-deny policy&lt;/td&gt;
&lt;td&gt;&lt;code&gt;kubectl apply -n &amp;lt;ns&amp;gt; -f np-default-deny.yaml&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Deny both &lt;strong&gt;Ingress&lt;/strong&gt; and &lt;strong&gt;Egress&lt;/strong&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Allow DNS&lt;/td&gt;
&lt;td&gt;&lt;code&gt;kubectl apply -n &amp;lt;ns&amp;gt; -f np-allow-dns.yaml&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Needed for service discovery.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Allow app→db&lt;/td&gt;
&lt;td&gt;&lt;code&gt;kubectl apply -n &amp;lt;ns&amp;gt; -f np-allow-app-to-db.yaml&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Pair with ingress on db and egress on app.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Switch namespace&lt;/td&gt;
&lt;td&gt;&lt;code&gt;kubectl config set-context --current --namespace=&amp;lt;ns&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Keeps commands short &amp;amp; safe.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dry-run a spec&lt;/td&gt;
&lt;td&gt;&lt;code&gt;kubectl apply -f x.yaml --dry-run=server&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Admission &amp;amp; schema checks without deploying.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Pitfalls &amp;amp; Recovery
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;“Policies don’t seem to apply” (order confusion).&lt;/strong&gt;&lt;br&gt;
&lt;em&gt;Symptom:&lt;/em&gt; You created allows but traffic still blocked.&lt;br&gt;
&lt;em&gt;Why:&lt;/em&gt; Policies are &lt;strong&gt;additive&lt;/strong&gt;; any default-deny remains in effect unless an allow matches &lt;strong&gt;both&lt;/strong&gt; sides (ingress on target + egress on source if you deny both).&lt;br&gt;
&lt;em&gt;Fix:&lt;/em&gt; Ensure you have &lt;strong&gt;egress allow on source&lt;/strong&gt; and &lt;strong&gt;ingress allow on destination&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;DNS broke after default-deny egress.&lt;/strong&gt;&lt;br&gt;
&lt;em&gt;Symptom:&lt;/em&gt; &lt;code&gt;curl db&lt;/code&gt; or &lt;code&gt;kubectl logs&lt;/code&gt; stalls; apps can’t resolve service names.&lt;br&gt;
&lt;em&gt;Fix:&lt;/em&gt; Add &lt;code&gt;np-allow-dns.yaml&lt;/code&gt; allowing egress to &lt;code&gt;kube-system&lt;/code&gt;/&lt;code&gt;k8s-app=kube-dns&lt;/code&gt; on TCP/UDP &lt;strong&gt;53&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Pods rejected after enabling PSA restricted.&lt;/strong&gt;&lt;br&gt;
&lt;em&gt;Symptom:&lt;/em&gt; &lt;code&gt;Error from server (Forbidden)&lt;/code&gt; with fields like &lt;code&gt;runAsNonRoot&lt;/code&gt;.&lt;br&gt;
&lt;em&gt;Fix:&lt;/em&gt; Adjust workload security context (non-root, drop caps, no hostPath). Temporarily set &lt;code&gt;enforce=baseline&lt;/code&gt; and &lt;code&gt;warn=restricted&lt;/code&gt; to migrate, then flip back.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Quota exceeded during rollout.&lt;/strong&gt;&lt;br&gt;
&lt;em&gt;Symptom:&lt;/em&gt; New ReplicaSet can’t scale (&lt;code&gt;exceeded quota&lt;/code&gt;).&lt;br&gt;
&lt;em&gt;Fix:&lt;/em&gt; Increase &lt;code&gt;pods&lt;/code&gt;/&lt;code&gt;requests.cpu/memory&lt;/code&gt; quota, or lower &lt;code&gt;replicas&lt;/code&gt;. Keep rollout headroom (20–30%).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;No limits → eviction during pressure.&lt;/strong&gt;&lt;br&gt;
&lt;em&gt;Symptom:&lt;/em&gt; Pods OOMKilled or preempted under load.&lt;br&gt;
&lt;em&gt;Fix:&lt;/em&gt; Set defaults via &lt;code&gt;LimitRange&lt;/code&gt;; ensure requests approximate real usage (watch &lt;code&gt;kubectl top pod&lt;/code&gt;).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;East-west traffic across namespaces unexpectedly blocked.&lt;/strong&gt;&lt;br&gt;
&lt;em&gt;Symptom:&lt;/em&gt; Cross-ns calls time out.&lt;br&gt;
&lt;em&gt;Fix:&lt;/em&gt; Add &lt;code&gt;namespaceSelector&lt;/code&gt; + &lt;code&gt;podSelector&lt;/code&gt; in allow rules, or route via ingresses with clear policies.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Wrap-Up (What this unlocks for reliability—Post 2 teaser)
&lt;/h2&gt;

&lt;p&gt;With &lt;strong&gt;Namespaces&lt;/strong&gt;, &lt;strong&gt;Quotas/LimitRanges&lt;/strong&gt;, &lt;strong&gt;NetworkPolicies&lt;/strong&gt;, and &lt;strong&gt;PSA&lt;/strong&gt; in place, you’ve built a security and fairness &lt;strong&gt;floor&lt;/strong&gt;: teams can’t trample each other, pods can’t talk without permission, and unsafe specs are stopped at the door.&lt;/p&gt;

&lt;p&gt;In &lt;strong&gt;Post 2&lt;/strong&gt;, we’ll layer &lt;strong&gt;observability SLOs&lt;/strong&gt;, &lt;strong&gt;PodDisruptionBudgets&lt;/strong&gt;, &lt;strong&gt;health/readiness gates&lt;/strong&gt;, and &lt;strong&gt;autoscaling&lt;/strong&gt; on top of this baseline to keep releases smooth and reliability measurable.&lt;/p&gt;




&lt;h3&gt;
  
  
  Appendix: “Before → After” Quick Contrasts
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Networking:&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Before:&lt;/strong&gt; &lt;code&gt;web&lt;/code&gt; can reach everything → lateral risk.&lt;br&gt;
&lt;strong&gt;After:&lt;/strong&gt; Only &lt;code&gt;web → db&lt;/code&gt; + DNS; all else denied.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Resources:&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Before:&lt;/strong&gt; No limits; one job spikes → node thrash.&lt;br&gt;
&lt;strong&gt;After:&lt;/strong&gt; Default requests/limits; quotas cap team usage.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Security:&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Before:&lt;/strong&gt; Privileged pods slip through.&lt;br&gt;
&lt;strong&gt;After:&lt;/strong&gt; PSA &lt;code&gt;restricted&lt;/code&gt; blocks them with actionable errors.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Got questions or want a tailored baseline for your stack? Drop them in, and I’ll fold them into a separate post.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>devops</category>
      <category>learning</category>
      <category>programming</category>
    </item>
    <item>
      <title>Post 0/10 — Foundations: Zero to Base Setup</title>
      <dc:creator>cloud-sky-ops</dc:creator>
      <pubDate>Tue, 23 Sep 2025 03:10:00 +0000</pubDate>
      <link>https://dev.to/cloud-sky-ops/post-010-foundations-zero-to-base-setup-58ni</link>
      <guid>https://dev.to/cloud-sky-ops/post-010-foundations-zero-to-base-setup-58ni</guid>
      <description>&lt;p&gt;&lt;em&gt;I’m a DevOps engineer and in this blog I’ll get you from zero to a working local cluster, deploy an app with raw YAML, then switch to Helm—plus the mental models and commands you’ll reuse daily.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Executive Summary
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Stand up a &lt;strong&gt;local Kubernetes cluster&lt;/strong&gt; (kind/minikube) and verify it with &lt;code&gt;kubectl&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Cement a &lt;strong&gt;mental model&lt;/strong&gt; of Pods → ReplicaSets → Deployments → Services → Controllers.&lt;/li&gt;
&lt;li&gt;Apply &lt;strong&gt;clean YAML&lt;/strong&gt; (Deployment + Service), then &lt;strong&gt;Helm-ify&lt;/strong&gt; it with a minimal chart.&lt;/li&gt;
&lt;li&gt;Learn the &lt;strong&gt;12 commands&lt;/strong&gt; I actually use day-to-day (kubectl + Helm).&lt;/li&gt;
&lt;li&gt;Practice &lt;strong&gt;pitfall recovery&lt;/strong&gt; (images won’t pull, pending pods, bad Services, context issues).&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Prereqs
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;minikube&lt;/strong&gt; &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;kind&lt;/strong&gt; &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docker&lt;/strong&gt; running (required by kind &amp;amp; often by minikube).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;kubectl&lt;/strong&gt; (v1.28+ recommended)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;helm&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here’s the updated &lt;strong&gt;Install Hints&lt;/strong&gt; section starting numbering from 1 instead of 0:&lt;/p&gt;




&lt;h2&gt;
  
  
  Install Hints (macOS, Linux, Windows)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Docker (required for kind &amp;amp; often for minikube)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;macOS&lt;/strong&gt; (with Homebrew):
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  brew &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--cask&lt;/span&gt; docker
  open /Applications/Docker.app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Tip:&lt;/em&gt; Ensure Docker Desktop is running before creating clusters.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Linux (Debian/Ubuntu):&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  &lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get update
  &lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; ca-certificates curl gnupg lsb-release
  &lt;span class="nb"&gt;sudo mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /etc/apt/keyrings
  curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://download.docker.com/linux/ubuntu/gpg | &lt;span class="nb"&gt;sudo &lt;/span&gt;gpg &lt;span class="nt"&gt;--dearmor&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /etc/apt/keyrings/docker.gpg
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="s2"&gt;"deb [arch=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;dpkg &lt;span class="nt"&gt;--print-architecture&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
    &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;lsb_release &lt;span class="nt"&gt;-cs&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; stable"&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; /etc/apt/sources.list.d/docker.list &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /dev/null
  &lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get update
  &lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

  &lt;span class="c"&gt;# Add your user to docker group (log out/in after)&lt;/span&gt;
  &lt;span class="nb"&gt;sudo &lt;/span&gt;usermod &lt;span class="nt"&gt;-aG&lt;/span&gt; docker &lt;span class="nv"&gt;$USER&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Windows (PowerShell as Admin):&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;choco&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;docker-desktop&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Tip:&lt;/em&gt; After install, restart and launch Docker Desktop.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. kubectl
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;macOS&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  brew &lt;span class="nb"&gt;install &lt;/span&gt;kubectl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Linux (Debian/Ubuntu):&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  &lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; apt-transport-https gnupg
  curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://pkgs.k8s.io/core:/stable:/v1.28/deb/Release.key | &lt;span class="nb"&gt;sudo &lt;/span&gt;gpg &lt;span class="nt"&gt;--dearmor&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /usr/share/keyrings/kubernetes-archive-keyring.gpg
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"deb [signed-by=/usr/share/keyrings/kubernetes-archive-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.28/deb/ /"&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; /etc/apt/sources.list.d/kubernetes.list
  &lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get update
  &lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; kubectl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Windows (PowerShell as Admin):&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;choco&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;kubernetes-cli&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. kind (Kubernetes in Docker)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;macOS&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  brew &lt;span class="nb"&gt;install &lt;/span&gt;kind
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Linux&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  curl &lt;span class="nt"&gt;-Lo&lt;/span&gt; ./kind https://kind.sigs.k8s.io/dl/v0.22.0/kind-linux-amd64
  &lt;span class="nb"&gt;chmod&lt;/span&gt; +x ./kind
  &lt;span class="nb"&gt;sudo mv&lt;/span&gt; ./kind /usr/local/bin/kind
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Windows (PowerShell as Admin):&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;choco&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;kind&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. minikube
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;macOS&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  brew &lt;span class="nb"&gt;install &lt;/span&gt;minikube
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Linux&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  curl &lt;span class="nt"&gt;-LO&lt;/span&gt; https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64
  &lt;span class="nb"&gt;sudo install &lt;/span&gt;minikube-linux-amd64 /usr/local/bin/minikube
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Windows (PowerShell as Admin):&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;choco&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;minikube&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5. Helm
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;macOS&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  brew &lt;span class="nb"&gt;install &lt;/span&gt;helm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Linux&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Windows (PowerShell as Admin):&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;choco&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;kubernetes-helm&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Verify Installations
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl version &lt;span class="nt"&gt;--client&lt;/span&gt;
kind version
minikube version
helm version
docker &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Concepts &amp;amp; Skills
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1) Kubernetes Mental Model
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Definition:&lt;/strong&gt; Kubernetes reconciles &lt;strong&gt;desired state&lt;/strong&gt; (your specs) to &lt;strong&gt;actual state&lt;/strong&gt; (running Pods) via controllers. It explains &lt;em&gt;everything&lt;/em&gt;: you ask for N replicas, deployments manage ReplicaSets which manage Pods; Services route to healthy Pod endpoints.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best practices:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Treat Pods as &lt;strong&gt;ephemeral&lt;/strong&gt;; deploy via &lt;strong&gt;Deployments&lt;/strong&gt;, not bare Pods.&lt;/li&gt;
&lt;li&gt;Scale &amp;amp; roll out via &lt;strong&gt;Deployments&lt;/strong&gt;; don’t hand-edit Pods.&lt;/li&gt;
&lt;li&gt;Expose traffic via &lt;strong&gt;Services&lt;/strong&gt;; keep &lt;strong&gt;labels/selectors&lt;/strong&gt; consistent.&lt;/li&gt;
&lt;li&gt;Let controllers do the work; think “&lt;strong&gt;declare&lt;/strong&gt;, don’t script.”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Commands you’ll use:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get deploy,rs,pods,svc &lt;span class="nt"&gt;-A&lt;/span&gt;
kubectl describe deploy &amp;lt;name&amp;gt;
kubectl rollout status deploy/&amp;lt;name&amp;gt;
kubectl scale deploy/&amp;lt;name&amp;gt; &lt;span class="nt"&gt;--replicas&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Before → After&lt;/strong&gt;&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="c1"&gt;# Before: single Pod (fragile)&lt;/span&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="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;hello&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[{&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;nginx&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;1.25&lt;/span&gt; &lt;span class="pi"&gt;}]&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# After: Deployment + Service (managed, scalable)&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/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;Deployment&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;hello&lt;/span&gt; &lt;span class="pi"&gt;}&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;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;hello&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;hello&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt; &lt;span class="pi"&gt;}&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="nv"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;nginx&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;1.25&lt;/span&gt; &lt;span class="pi"&gt;}]&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&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;Service&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;hello&lt;/span&gt; &lt;span class="pi"&gt;}&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;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ClusterIP&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;hello&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[{&lt;/span&gt; &lt;span class="nv"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;80&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;targetPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;80&lt;/span&gt; &lt;span class="pi"&gt;}]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Decision cues (when to use):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Deployment&lt;/strong&gt; for stateless apps, rolling updates.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;StatefulSet&lt;/strong&gt; for ordered/identity-bound Pods (DBs).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DaemonSet&lt;/strong&gt; for per-node agents.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Job/CronJob&lt;/strong&gt; for finite/recurring work.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  2) YAML Hygiene
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Definition:&lt;/strong&gt; Kubernetes resources are &lt;strong&gt;structured YAML&lt;/strong&gt;: &lt;code&gt;apiVersion&lt;/code&gt;, &lt;code&gt;kind&lt;/code&gt;, &lt;code&gt;metadata&lt;/code&gt;, &lt;code&gt;spec&lt;/code&gt;. 90% of “Why won’t it work?” is indentation, wrong &lt;code&gt;apiVersion&lt;/code&gt;, or misplaced fields.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best practices:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keep a &lt;strong&gt;stable skeleton&lt;/strong&gt;: &lt;code&gt;apiVersion/kind/metadata/spec&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Use &lt;strong&gt;2 spaces&lt;/strong&gt;; never tabs.&lt;/li&gt;
&lt;li&gt;Verify with &lt;code&gt;kubectl explain&lt;/code&gt; and &lt;code&gt;kubectl apply --dry-run=client -f&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Prefer &lt;strong&gt;labels&lt;/strong&gt; (e.g., &lt;code&gt;app: hello&lt;/code&gt;) for selectors; avoid ad-hoc names.&lt;/li&gt;
&lt;li&gt;Pin &lt;strong&gt;images&lt;/strong&gt; (e.g., &lt;code&gt;nginx:1.25&lt;/code&gt;) to avoid surprise upgrades.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Helpful commands:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl explain deployment.spec &lt;span class="nt"&gt;--recursive&lt;/span&gt; | less
kubectl apply &lt;span class="nt"&gt;--dry-run&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;client &lt;span class="nt"&gt;-f&lt;/span&gt; hello.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Before → After&lt;/strong&gt;&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="c1"&gt;# Before: wrong apiVersion, mixed tabs&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v2&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;Deployment&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;hello&lt;/span&gt;   &lt;span class="c1"&gt;# &amp;lt;-- tab!&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{}&lt;/span&gt;

&lt;span class="c1"&gt;# After: correct and clean&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/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;Deployment&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;hello&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;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;hello&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;hello&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt; &lt;span class="pi"&gt;}&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="nv"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;nginx&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;1.25&lt;/span&gt; &lt;span class="pi"&gt;}]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Decision cues:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;kubectl explain&lt;/code&gt; if you’re &lt;strong&gt;guessing a field&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;--dry-run&lt;/code&gt; for &lt;strong&gt;fast validation&lt;/strong&gt; before real apply.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  3) &lt;code&gt;kubectl&lt;/code&gt; Basics
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Definition:&lt;/strong&gt; &lt;code&gt;kubectl&lt;/code&gt; is the CLI to talk to the &lt;strong&gt;API server&lt;/strong&gt; using your current &lt;strong&gt;context/namespace&lt;/strong&gt;. Wrong context/namespace is the #1 cause of “it’s not found.”&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best practices:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Set a &lt;strong&gt;default namespace&lt;/strong&gt; per context.&lt;/li&gt;
&lt;li&gt;Use &lt;strong&gt;wide&lt;/strong&gt; output and &lt;strong&gt;labels&lt;/strong&gt; in &lt;code&gt;get&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Rely on &lt;code&gt;describe&lt;/code&gt; and &lt;strong&gt;events&lt;/strong&gt; to debug.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;-o yaml&lt;/code&gt; to see server-filled fields.&lt;/li&gt;
&lt;li&gt;Use &lt;strong&gt;kubeconfig contexts&lt;/strong&gt;; don’t point prod by accident.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Commands:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl config get-contexts
kubectl config set-context &lt;span class="nt"&gt;--current&lt;/span&gt; &lt;span class="nt"&gt;--namespace&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;dev
kubectl get pods &lt;span class="nt"&gt;-o&lt;/span&gt; wide
kubectl describe pod &amp;lt;pod&amp;gt;
kubectl get events &lt;span class="nt"&gt;--sort-by&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;.lastTimestamp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Before → After&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Before: implicit default namespace (surprise!)&lt;/span&gt;
kubectl get pods

&lt;span class="c"&gt;# After: explicit context &amp;amp; namespace&lt;/span&gt;
kubectl config set-context &lt;span class="nt"&gt;--current&lt;/span&gt; &lt;span class="nt"&gt;--namespace&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;dev
kubectl get pods &lt;span class="nt"&gt;-n&lt;/span&gt; dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Decision cues:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If a resource “disappears,” &lt;strong&gt;check namespace&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;If a command “hangs,” &lt;strong&gt;check context/cluster&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  4) Local Cluster: kind or minikube
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Definition:&lt;/strong&gt; &lt;strong&gt;kind&lt;/strong&gt; runs Kubernetes in Docker containers; &lt;strong&gt;minikube&lt;/strong&gt; runs a local Kubernetes VM/container. Fast, reproducible clusters for development and demos.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best practices:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;strong&gt;kind&lt;/strong&gt; for simple, Docker-backed clusters.&lt;/li&gt;
&lt;li&gt;Use &lt;strong&gt;minikube&lt;/strong&gt; for addons/ingress and driver flexibility.&lt;/li&gt;
&lt;li&gt;Name clusters per project (&lt;code&gt;kind create cluster --name demo&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Export kubeconfig &lt;strong&gt;only for the current shell&lt;/strong&gt; (avoid prod collisions).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Commands:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# kind&lt;/span&gt;
kind create cluster &lt;span class="nt"&gt;--name&lt;/span&gt; k90
kubectl cluster-info
kubectl get nodes

&lt;span class="c"&gt;# minikube&lt;/span&gt;
minikube start
minikube status
kubectl get nodes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Before → After&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Before: ad-hoc envs, “works on my machine”&lt;/span&gt;
docker run &lt;span class="nt"&gt;-p&lt;/span&gt; 8080:80 nginx

&lt;span class="c"&gt;# After: real cluster parity locally&lt;/span&gt;
kind create cluster &lt;span class="nt"&gt;--name&lt;/span&gt; k90
kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; k8s/hello.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Decision cues:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;kind&lt;/strong&gt; if you already live in Docker land &amp;amp; want speed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;minikube&lt;/strong&gt; if you need &lt;strong&gt;addons&lt;/strong&gt; and drivers (HyperKit, Docker, etc.).&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  5) Helm Basics
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Definition:&lt;/strong&gt; &lt;strong&gt;Helm&lt;/strong&gt; is Kubernetes’ package manager: it templatizes YAML, versioned as &lt;strong&gt;charts&lt;/strong&gt;, and then installed as &lt;strong&gt;releases&lt;/strong&gt;. Stop copy-pasting YAML; manage &lt;strong&gt;environments&lt;/strong&gt;, &lt;strong&gt;values&lt;/strong&gt;, &lt;strong&gt;upgrades&lt;/strong&gt;, and &lt;strong&gt;rollbacks&lt;/strong&gt; cleanly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best practices:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keep charts &lt;strong&gt;minimal&lt;/strong&gt;; prefer a small set of templates.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Parameterize&lt;/strong&gt; only what changes across envs.&lt;/li&gt;
&lt;li&gt;Validate with &lt;code&gt;helm lint&lt;/code&gt; and &lt;strong&gt;render&lt;/strong&gt; with &lt;code&gt;helm template&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Track releases with &lt;code&gt;helm list&lt;/code&gt; and &lt;strong&gt;rollback&lt;/strong&gt; confidently.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Commands:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;helm create hello
helm lint hello
helm template hello
helm &lt;span class="nb"&gt;install &lt;/span&gt;hello ./hello &lt;span class="nt"&gt;-n&lt;/span&gt; dev &lt;span class="nt"&gt;--create-namespace&lt;/span&gt;
helm upgrade hello ./hello &lt;span class="nt"&gt;-f&lt;/span&gt; values-dev.yaml
helm rollback hello 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Before → After&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Before: multiple hand-maintained YAML files per env&lt;/span&gt;
kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; dev/hello.yaml
kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; prod/hello.yaml

&lt;span class="c"&gt;# After: one chart, many values&lt;/span&gt;
helm &lt;span class="nb"&gt;install &lt;/span&gt;hello ./hello &lt;span class="nt"&gt;-f&lt;/span&gt; values-dev.yaml
helm upgrade hello ./hello &lt;span class="nt"&gt;-f&lt;/span&gt; values-prod.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Decision cues:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;strong&gt;raw YAML&lt;/strong&gt; to learn and for tiny one-offs.&lt;/li&gt;
&lt;li&gt;Use &lt;strong&gt;Helm&lt;/strong&gt; once you need &lt;strong&gt;env variants&lt;/strong&gt;, &lt;strong&gt;upgrades&lt;/strong&gt;, &lt;strong&gt;teams&lt;/strong&gt;, &lt;strong&gt;reuse&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Diagrams
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Architecture (request → Service → Pods)
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuue7w5rome3aogifhv19.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuue7w5rome3aogifhv19.png" alt="Diagram-1" width="800" height="200"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Control Plane Path (sequence)
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo9qgf9xrcqzo74ee8m2t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo9qgf9xrcqzo74ee8m2t.png" alt="Diagram-2" width="800" height="427"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Hands-on Mini-Lab (20–30 min)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Create a cluster, deploy “hello” with raw YAML, then with Helm; compare manifests.&lt;/p&gt;

&lt;h3&gt;
  
  
  1) Create a local cluster
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kind create cluster &lt;span class="nt"&gt;--name&lt;/span&gt; k90
kubectl cluster-info
kubectl get nodes
kubectl config set-context &lt;span class="nt"&gt;--current&lt;/span&gt; &lt;span class="nt"&gt;--namespace&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;(macOS: if &lt;code&gt;bash&lt;/code&gt; errors, run in &lt;code&gt;zsh&lt;/code&gt; or install newer bash with Homebrew.)&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2) Raw YAML: Deployment + Service
&lt;/h3&gt;

&lt;p&gt;Create &lt;code&gt;k8s/hello.yaml&lt;/code&gt;:&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;apps/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;Deployment&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;hello&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;hello&lt;/span&gt; &lt;span class="pi"&gt;}&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;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;hello&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;hello&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt; &lt;span class="pi"&gt;}&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:1.25&lt;/span&gt;
          &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[{&lt;/span&gt; &lt;span class="nv"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;80&lt;/span&gt; &lt;span class="pi"&gt;}]&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&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;Service&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;hello&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;hello&lt;/span&gt; &lt;span class="pi"&gt;}&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;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ClusterIP&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;hello&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;ports&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;http&lt;/span&gt;
      &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
      &lt;span class="na"&gt;targetPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Apply and verify:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; k8s/hello.yaml
kubectl rollout status deploy/hello
kubectl get svc hello &lt;span class="nt"&gt;-o&lt;/span&gt; wide
kubectl get pods &lt;span class="nt"&gt;-l&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;hello &lt;span class="nt"&gt;-o&lt;/span&gt; wide
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Port-forward to test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl port-forward svc/hello 8080:80
&lt;span class="c"&gt;# open http://localhost:8080&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3) Helm-ify it
&lt;/h3&gt;

&lt;p&gt;Create a chart and trim it down:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;helm create hello-chart
&lt;span class="c"&gt;# Keep only templates/deployment.yaml and templates/service.yaml; delete extras like hpa, serviceaccount, tests.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;hello-chart/values.yaml&lt;/code&gt; (minimal):&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;replicaCount&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
&lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;repository&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx&lt;/span&gt;
  &lt;span class="na"&gt;tag&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.25"&lt;/span&gt;
  &lt;span class="na"&gt;pullPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;IfNotPresent&lt;/span&gt;
&lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ClusterIP&lt;/span&gt;
  &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
&lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hello&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;hello-chart/templates/deployment.yaml&lt;/code&gt;:&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;apps/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;Deployment&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="pi"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;include "hello-chart.fullname" .&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;- include "hello-chart.labels" . | nindent 4&lt;/span&gt; &lt;span class="pi"&gt;}}&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;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;.Values.replicaCount&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;.Values.labels.app&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&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;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;.Values.labels.app&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt;
        &lt;span class="pi"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;- include "hello-chart.labels" . | nindent 8&lt;/span&gt; &lt;span class="pi"&gt;}}&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="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;.Values.image.repository&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}:{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;.Values.image.tag&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
          &lt;span class="na"&gt;imagePullPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;.Values.image.pullPolicy&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt;
          &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;hello-chart/templates/service.yaml&lt;/code&gt;:&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;Service&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="pi"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;include "hello-chart.fullname" .&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;- include "hello-chart.labels" . | nindent 4&lt;/span&gt; &lt;span class="pi"&gt;}}&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;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;.Values.service.type&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;.Values.labels.app&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt;
  &lt;span class="na"&gt;ports&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;http&lt;/span&gt;
      &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;.Values.service.port&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt;
      &lt;span class="na"&gt;targetPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Render &amp;amp; compare with your raw YAML:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;helm template hello ./hello-chart &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; rendered.yaml
diff &lt;span class="nt"&gt;-u&lt;/span&gt; k8s/hello.yaml rendered.yaml &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install and test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;helm &lt;span class="nb"&gt;install &lt;/span&gt;hello ./hello-chart &lt;span class="nt"&gt;-n&lt;/span&gt; dev &lt;span class="nt"&gt;--create-namespace&lt;/span&gt;
kubectl get all &lt;span class="nt"&gt;-l&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;hello
kubectl port-forward svc/hello 8080:80
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Upgrade and rollback:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Bump replicas via values&lt;/span&gt;
helm upgrade hello ./hello-chart &lt;span class="nt"&gt;--set&lt;/span&gt; &lt;span class="nv"&gt;replicaCount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3
helm &lt;span class="nb"&gt;history &lt;/span&gt;hello
helm rollback hello 1   &lt;span class="c"&gt;# back to first revision&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Cheatsheet Table (Top 12)
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;kubectl config get-contexts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;List kubeconfig contexts; know where you’re pointed.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;kubectl config set-context --current --namespace=dev&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Set default namespace for current context.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;kubectl get pods -o wide&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Show Pods with node/IP; quick health snapshot.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;kubectl describe pod/&amp;lt;name&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Deep dive into events, containers, reasons for failures.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;kubectl get events --sort-by=.lastTimestamp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Recent cluster events for fast debugging.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;kubectl apply -f file.yaml&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Declaratively create/update resources.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;kubectl rollout status deploy/&amp;lt;name&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Watch rollout until success/fail.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;kubectl logs deploy/&amp;lt;name&amp;gt; -f&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Stream app logs from all Pods in the Deployment.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;kubectl port-forward svc/&amp;lt;name&amp;gt; 8080:80&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Access ClusterIP services from localhost.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;helm template &amp;lt;rel&amp;gt; &amp;lt;chart&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Render manifests locally (no cluster changes).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;helm install &amp;lt;rel&amp;gt; &amp;lt;chart&amp;gt; -f values.yaml&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Install a chart as a named release.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;helm upgrade --install &amp;lt;rel&amp;gt; &amp;lt;chart&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Idempotent deploy; create or upgrade in one.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Pitfalls &amp;amp; Recovery
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;ImagePullBackOff&lt;/code&gt; / &lt;code&gt;ErrImagePull&lt;/code&gt;:&lt;/strong&gt; Repository or tag wrong, or no registry creds.&lt;br&gt;
&lt;em&gt;Fix:&lt;/em&gt; &lt;code&gt;kubectl describe pod&lt;/code&gt;, verify &lt;code&gt;image:&lt;/code&gt;; try &lt;code&gt;docker pull&lt;/code&gt; locally; add imagePullSecret if private.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Pods stuck &lt;code&gt;Pending&lt;/code&gt;:&lt;/strong&gt; No schedulable nodes, resource requests too high, or PVC issues.&lt;br&gt;
&lt;em&gt;Fix:&lt;/em&gt; &lt;code&gt;kubectl describe pod&lt;/code&gt;; check &lt;code&gt;kubectl get nodes&lt;/code&gt;; reduce &lt;code&gt;resources.requests&lt;/code&gt;; with minikube, &lt;code&gt;minikube addons enable storage&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Service not routing:&lt;/strong&gt; &lt;code&gt;selector&lt;/code&gt; doesn’t match Pod labels or &lt;code&gt;targetPort&lt;/code&gt; mismatch.&lt;br&gt;
&lt;em&gt;Fix:&lt;/em&gt; Compare &lt;code&gt;spec.selector&lt;/code&gt; to &lt;code&gt;pod.metadata.labels&lt;/code&gt;; align &lt;code&gt;targetPort&lt;/code&gt; with containerPort.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Wrong context/namespace:&lt;/strong&gt; Resources “missing.”&lt;br&gt;
&lt;em&gt;Fix:&lt;/em&gt; &lt;code&gt;kubectl config current-context&lt;/code&gt;; &lt;code&gt;kubectl get ns&lt;/code&gt;; set proper namespace.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Helm upgrade fails (immutable fields):&lt;/strong&gt; Some fields (e.g., &lt;code&gt;spec.clusterIP&lt;/code&gt;) can’t change in-place.&lt;br&gt;
&lt;em&gt;Fix:&lt;/em&gt; &lt;code&gt;helm diff&lt;/code&gt; to see changes; for Services, preserve &lt;code&gt;clusterIP&lt;/code&gt;; otherwise &lt;code&gt;helm uninstall&lt;/code&gt; and re-install.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;RBAC forbidden:&lt;/strong&gt; In restricted clusters, applies fail.&lt;br&gt;
&lt;em&gt;Fix:&lt;/em&gt; Ask for right Role/RoleBinding; test with &lt;code&gt;kubectl auth can-i&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Tabs/indentation in YAML:&lt;/strong&gt; Parsing errors or ignored fields.&lt;br&gt;
&lt;em&gt;Fix:&lt;/em&gt; Convert tabs to spaces; validate with &lt;code&gt;kubectl apply --dry-run=client -f&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Quick Bash (≥4) Scriptlets — Point‑wise Explanation
&lt;/h2&gt;

&lt;p&gt;Below is a line‑by‑line breakdown of the helper script that creates or deletes a local &lt;strong&gt;kind&lt;/strong&gt; cluster and sets a default namespace.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail

&lt;span class="nv"&gt;CLUSTER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;k90&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;2&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;up&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="k"&gt;in
  &lt;/span&gt;up&lt;span class="p"&gt;)&lt;/span&gt;
    kind create cluster &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CLUSTER&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    kubectl config set-context &lt;span class="nt"&gt;--current&lt;/span&gt; &lt;span class="nt"&gt;--namespace&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;dev
    &lt;span class="p"&gt;;;&lt;/span&gt;
  down&lt;span class="p"&gt;)&lt;/span&gt;
    kind delete cluster &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CLUSTER&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;;;&lt;/span&gt;
&lt;span class="k"&gt;esac&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What each line does
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;#!/usr/bin/env bash&lt;/code&gt;&lt;br&gt;
&lt;em&gt;Shebang.&lt;/em&gt; Asks the OS to execute this file with the first &lt;code&gt;bash&lt;/code&gt; found in your &lt;code&gt;PATH&lt;/code&gt; (portable across systems).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;set -euo pipefail&lt;/code&gt;&lt;br&gt;
Enables &lt;strong&gt;strict mode&lt;/strong&gt;:&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;-e&lt;/code&gt;: exit immediately if any command exits with non‑zero status.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-u&lt;/code&gt;: error if using an &lt;strong&gt;unset&lt;/strong&gt; variable (catch typos/assumptions).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-o pipefail&lt;/code&gt;: a pipeline fails if &lt;strong&gt;any&lt;/strong&gt; command fails (not just the last).&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;CLUSTER=${1:-k90}&lt;/code&gt;&lt;br&gt;
Positional argument &lt;strong&gt;\$1&lt;/strong&gt; is the cluster name; if omitted, default to &lt;code&gt;k90&lt;/code&gt;.&lt;br&gt;
Examples: &lt;code&gt;./cluster.sh&lt;/code&gt; ⇒ name &lt;code&gt;k90&lt;/code&gt;; &lt;code&gt;./cluster.sh demo&lt;/code&gt; ⇒ name &lt;code&gt;demo&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;case "${2:-up}" in&lt;/code&gt;&lt;br&gt;
Dispatches on the &lt;strong&gt;action&lt;/strong&gt; provided in &lt;strong&gt;\$2&lt;/strong&gt;; defaults to &lt;code&gt;up&lt;/code&gt; if not given.&lt;br&gt;
Usage examples:&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;./cluster.sh&lt;/code&gt; → &lt;code&gt;up&lt;/code&gt; (default)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;./cluster.sh demo&lt;/code&gt; → &lt;code&gt;up&lt;/code&gt; on cluster &lt;code&gt;demo&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;./cluster.sh demo down&lt;/code&gt; → &lt;code&gt;down&lt;/code&gt; on cluster &lt;code&gt;demo&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;up)&lt;/code&gt; block&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;kind create cluster --name "$CLUSTER"&lt;/code&gt; creates a Docker‑backed Kubernetes cluster named &lt;code&gt;CLUSTER&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;kubectl config set-context --current --namespace=dev&lt;/code&gt; sets the &lt;strong&gt;default namespace&lt;/strong&gt; for the &lt;em&gt;current kubeconfig context&lt;/em&gt; to &lt;code&gt;dev&lt;/code&gt;, so you don’t need &lt;code&gt;-n dev&lt;/code&gt; for every command.&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;down)&lt;/code&gt; block&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;kind delete cluster --name "$CLUSTER"&lt;/code&gt; removes the cluster and its Docker containers cleanly.&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;;;&lt;/code&gt; and &lt;code&gt;esac&lt;/code&gt;
&lt;code&gt;;;&lt;/code&gt; ends each case arm; &lt;code&gt;esac&lt;/code&gt; closes the &lt;code&gt;case&lt;/code&gt; statement.&lt;/li&gt;
&lt;/ol&gt;




&lt;h3&gt;
  
  
  Why this script is useful
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Idempotent lifecycle&lt;/strong&gt;: One command to bring the cluster &lt;strong&gt;up&lt;/strong&gt; or tear it &lt;strong&gt;down&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Safer defaults&lt;/strong&gt;: Strict mode prevents partial/hidden failures.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Less typing&lt;/strong&gt;: Sets your working namespace so &lt;code&gt;kubectl get pods&lt;/code&gt; “just works.”&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Portable&lt;/strong&gt;: &lt;code&gt;env&lt;/code&gt; shebang finds the right &lt;code&gt;bash&lt;/code&gt;; easily adapted for zsh.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Common tweaks you might add
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Wait for node readiness&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  kubectl &lt;span class="nb"&gt;wait&lt;/span&gt; &lt;span class="nt"&gt;--for&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;condition&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Ready nodes &lt;span class="nt"&gt;--all&lt;/span&gt; &lt;span class="nt"&gt;--timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;120s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Install an ingress addon (minikube)&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  minikube addons &lt;span class="nb"&gt;enable &lt;/span&gt;ingress
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Switch context safely&lt;/strong&gt; (guard rails):
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  kubectl config current-context | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-q&lt;/span&gt; kind- &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Refusing to run outside a kind context"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;1&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Parameterize namespace&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  &lt;span class="nv"&gt;NS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;NS&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;dev&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;
  kubectl config set-context &lt;span class="nt"&gt;--current&lt;/span&gt; &lt;span class="nt"&gt;--namespace&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$NS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  macOS / Linux / Windows notes
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;macOS&lt;/strong&gt;: If &lt;code&gt;/bin/bash&lt;/code&gt; is 3.2, run with &lt;code&gt;zsh&lt;/code&gt; (&lt;code&gt;#!/bin/zsh&lt;/code&gt;) or &lt;code&gt;brew install bash&lt;/code&gt; and use &lt;code&gt;/usr/local/bin/bash&lt;/code&gt; (or &lt;code&gt;/opt/homebrew/bin/bash&lt;/code&gt; on Apple Silicon).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Linux&lt;/strong&gt;: Ensure your user can access Docker without &lt;code&gt;sudo&lt;/code&gt; (&lt;code&gt;usermod -aG docker $USER&lt;/code&gt;, then re‑login).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Windows&lt;/strong&gt;: Run the script from &lt;strong&gt;WSL2&lt;/strong&gt; (Ubuntu) for the best experience with kind/minikube; ensure Docker Desktop has the WSL2 backend enabled.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Quick usage recap
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Bring up default cluster (k90) and set namespace dev&lt;/span&gt;
./cluster.sh

&lt;span class="c"&gt;# Bring up a named cluster&lt;/span&gt;
./cluster.sh demo up

&lt;span class="c"&gt;# Tear it down&lt;/span&gt;
./cluster.sh demo down
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You’re ready. Save this page, keep the YAML/Helm snippets handy, and start iterating.&lt;/p&gt;




&lt;h2&gt;
  
  
  Wrap-up &amp;amp; Next Steps
&lt;/h2&gt;

&lt;p&gt;You now have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;cluster&lt;/strong&gt; you can spin up/down quickly.&lt;/li&gt;
&lt;li&gt;A clean &lt;strong&gt;Deployment + Service&lt;/strong&gt; in raw YAML.&lt;/li&gt;
&lt;li&gt;A minimal &lt;strong&gt;Helm chart&lt;/strong&gt; with values and releases.&lt;/li&gt;
&lt;li&gt;A mental model to reason about &lt;strong&gt;controllers&lt;/strong&gt; and &lt;strong&gt;desired state&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Post 1 (coming up):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ingress vs. NodePort vs. LoadBalancer&lt;/strong&gt; with local ingress addons.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rolling updates&lt;/strong&gt; &amp;amp; &lt;strong&gt;health probes&lt;/strong&gt; (readiness/liveness/startup).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Config &amp;amp; Secrets&lt;/strong&gt; (env vars, mounted files, externalized values).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resource requests/limits&lt;/strong&gt; &amp;amp; &lt;strong&gt;HPA&lt;/strong&gt; basics.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Helm strategies&lt;/strong&gt;: values layering, env directories, &lt;code&gt;helmfile&lt;/code&gt; preview.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devops</category>
      <category>kubernetes</category>
      <category>tutorial</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Kubernetes &amp; Helm Blog Series: A Journey from Zero to Production</title>
      <dc:creator>cloud-sky-ops</dc:creator>
      <pubDate>Sat, 20 Sep 2025 21:52:39 +0000</pubDate>
      <link>https://dev.to/cloud-sky-ops/-kubernetes-helm-blog-series-a-journey-from-zero-to-production-2kok</link>
      <guid>https://dev.to/cloud-sky-ops/-kubernetes-helm-blog-series-a-journey-from-zero-to-production-2kok</guid>
      <description>&lt;p&gt;Kubernetes and Helm have become the backbone of modern DevOps practices, powering everything from small startups to enterprise-scale production environments. But the learning curve can feel steep, especially if you’re new. That’s why I’ve designed this &lt;strong&gt;10-post series&lt;/strong&gt; (starting from 0 as mentioned in the title, so effectively 11) to guide you step by step—from absolute foundations to advanced, production-ready strategies—so that both beginners and seasoned engineers can level up.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why This Series?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;For beginners&lt;/strong&gt;: you’ll get clear definitions, simple labs, and practical examples that build confidence quickly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;For advanced engineers&lt;/strong&gt;: you’ll find deeper dives, edge-case discussions, and proven practices used in real-world production clusters.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;For everyone&lt;/strong&gt;: each post has hands-on labs, cheat sheets, and a wrap-up that connects naturally to the next topic.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Series Structure
&lt;/h2&gt;

&lt;h3&gt;
  
  
  **Post 0 — Foundations: Zero to Base Setup
&lt;/h3&gt;

&lt;p&gt;Get comfortable with the mental model of Kubernetes, YAML hygiene, &lt;code&gt;kubectl&lt;/code&gt; basics, local clusters (kind/minikube), and Helm chart structure.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Post 1 — Multi-Tenancy &amp;amp; Security Baseline&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Learn how Namespaces, ResourceQuotas, NetworkPolicies, and Pod Security Admission set the foundation for safe, shared clusters.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Post 2 — Reliability by Design: Probes, PDBs &amp;amp; Topology Spread&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Design applications that survive rollouts, disruptions, and node failures with probes, PodDisruptionBudgets, and spread constraints.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Post 3 — Upgrades &amp;amp; Feature Gates: Safe Cadence&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Master upgrade strategies, manage version skew, and safely evaluate new features.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Post 4 — Smart Scaling &amp;amp; Cost Control: HPA, KEDA &amp;amp; Karpenter&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Balance elasticity and efficiency using Horizontal Pod Autoscaling, event-driven scaling, Cluster Autoscaler, and Karpenter.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Post 5 — Modern Traffic Management with Gateway API&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Move beyond Ingress with the Gateway API: flexible routing, retries, mirroring, and multi-tenant gateways.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Post 6 — Helm Fundamentals Done Right&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Build clean, reusable charts with sane defaults, values schema validation, library charts, and overlays.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Post 7 — Helm in CI/CD: Lint, Tests, Diff &amp;amp; Supply Chain Security&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Treat charts as code: enforce linting, add unit tests, integrate diffs, push to OCI registries, and secure with provenance.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Post 8 — Progressive Delivery &amp;amp; GitOps&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Ship confidently with Blue/Green and Canary patterns, GitOps controllers like Argo CD and Flux, and drift detection.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Post 9 — Operator’s Toolkit: Debugging &amp;amp; Power Moves&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Sharpen your day-to-day: ephemeral debug containers, &lt;code&gt;kubectl&lt;/code&gt; power tips, and Helm Day-2 operations.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Post 10 — What’s Next: Sidecars, eBPF, AI Gateways &amp;amp; Supply Chain Maturity&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Explore the trends shaping Kubernetes: stable sidecars, eBPF-powered networking, AI routing, and SLSA supply-chain practices.&lt;/p&gt;




&lt;h2&gt;
  
  
  How to Follow Along
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Start at Post 0 if you’re new, or jump in at any point if you’re experienced.&lt;/li&gt;
&lt;li&gt;Each post builds on the previous, but they also stand alone as practical guides.&lt;/li&gt;
&lt;li&gt;Expect plenty of copy-pasteable commands, diagrams to cement understanding, and mini-labs that run in less than 30 minutes.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Bottom line:&lt;/strong&gt; by the end of this series, you’ll not only understand Kubernetes and Helm—you’ll be confident running, scaling, securing, and modernizing clusters in production. Start's September 23rd, 2025&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>devops</category>
      <category>learning</category>
    </item>
    <item>
      <title>Bash Solutions: NUL and process substitutions</title>
      <dc:creator>cloud-sky-ops</dc:creator>
      <pubDate>Tue, 09 Sep 2025 20:52:13 +0000</pubDate>
      <link>https://dev.to/cloud-sky-ops/bash-solutions-nuf-and-process-substitutions-ep2</link>
      <guid>https://dev.to/cloud-sky-ops/bash-solutions-nuf-and-process-substitutions-ep2</guid>
      <description>&lt;p&gt;As a DevOps engineer, I spend a lot of time trying to make CI/CD pipelines bulletproof. GitHub Actions is powerful, but for some cases you need to throw in some &lt;strong&gt;custom automation logic&lt;/strong&gt; to bring your use case to completetion. Recently, I ran into one of those &lt;code&gt;this should have been simple&lt;/code&gt; problems that ended up forcing me deep into Bash territory.&lt;/p&gt;

&lt;p&gt;In this post, I’ll share how I solved it, the &lt;strong&gt;tricks I learned along the way&lt;/strong&gt;, and why those tricks are now staples in my automation toolkit.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem Scenario
&lt;/h2&gt;

&lt;p&gt;I needed to build a GitHub Workflow that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Loops through all the changed files in a PR.&lt;/li&gt;
&lt;li&gt;Runs a &lt;strong&gt;service-specific script&lt;/strong&gt; based on which directories were touched.&lt;/li&gt;
&lt;li&gt;Ensures scripts are run only once per unique service.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sounds easy, right? But here’s what I ran into:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Filenames and directories contained &lt;strong&gt;spaces, dashes, and special characters&lt;/strong&gt;, which caused word-splitting nightmares.&lt;/li&gt;
&lt;li&gt;Multiple directories had overlapping file changes, and I needed to deduplicate them cleanly before execution.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Naive attempts with &lt;code&gt;for file in $(git diff …)&lt;/code&gt; blew up instantly when spaces or weird characters showed up. Deduplication with &lt;code&gt;sort | uniq&lt;/code&gt; worked halfway, but it wasn’t reliable when integrated into the workflow. &lt;/p&gt;

&lt;p&gt;I also thought of introducing path filters in the workflow trigger to target specific directories.&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;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;paths&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;services/service-a/**"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But it would require updates to the workflow whenever new directories are added, so it didn't seem scalable.&lt;/p&gt;

&lt;p&gt;I needed a &lt;strong&gt;bash-powered solution&lt;/strong&gt; that was safe, idempotent, and CI-friendly.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step-by-Step Troubleshooting &amp;amp; Resolution
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1: Getting the changed files
&lt;/h3&gt;

&lt;p&gt;Inside the workflow, I grabbed the changed files with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git diff &lt;span class="nt"&gt;--name-only&lt;/span&gt; origin/main...HEAD &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; changed_files.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That gave me a newline-delimited list. Great — until filenames with spaces appeared.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Handling spaces and special characters
&lt;/h3&gt;

&lt;p&gt;I realized I couldn’t rely on newlines alone. I switched to &lt;strong&gt;NUL-terminated strings&lt;/strong&gt;, which led to the first crucial trick.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Deduplicating directories
&lt;/h3&gt;

&lt;p&gt;Next, I needed to strip each path down to its &lt;strong&gt;top-level directory&lt;/strong&gt;, then deduplicate. A bit of &lt;code&gt;awk&lt;/code&gt; and &lt;code&gt;sort -u&lt;/code&gt; magic did the trick.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Iterating safely in CI
&lt;/h3&gt;

&lt;p&gt;Finally, I wrote a loop to iterate over each unique directory and run the associated script. The key was keeping it &lt;strong&gt;robust against any edge cases&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Advanced Bash Trick #1: NUL-Separated Processing
&lt;/h2&gt;

&lt;p&gt;Before jumping into the fix, let’s talk about what this means.&lt;/p&gt;

&lt;p&gt;Normally, when you run a command like &lt;code&gt;git diff --name-only&lt;/code&gt;, it prints filenames separated by &lt;strong&gt;newlines&lt;/strong&gt; (&lt;code&gt;\n&lt;/code&gt;). That works most of the time — until you run into filenames with spaces, quotes, tabs, or even emojis. Bash sees those spaces and thinks they’re “separators” between different items, which can break loops in subtle ways.&lt;/p&gt;

&lt;p&gt;That’s where &lt;strong&gt;NUL-separated processing&lt;/strong&gt; comes in.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;NUL (&lt;code&gt;\0&lt;/code&gt;)&lt;/strong&gt; character is a special invisible character that represents “nothing.” It’s guaranteed never to appear in a valid filename on Unix-like systems.&lt;/li&gt;
&lt;li&gt;If we use NULs instead of newlines as separators, Bash can handle structured data safely, no matter how strange the entries look.&lt;/li&gt;
&lt;li&gt;Commands like &lt;code&gt;git diff&lt;/code&gt;, &lt;code&gt;xargs&lt;/code&gt;, and &lt;code&gt;sort&lt;/code&gt; all have flags (&lt;code&gt;-z&lt;/code&gt; or &lt;code&gt;-0&lt;/code&gt;) to enable this mode.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Think of it like replacing a fragile delimiter (newline) with an unbreakable one (NUL).&lt;/p&gt;

&lt;p&gt;Here’s the heart of the fix:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git diff &lt;span class="nt"&gt;--name-only&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; origin/main...HEAD | &lt;span class="se"&gt;\&lt;/span&gt;
  xargs &lt;span class="nt"&gt;-0&lt;/span&gt; &lt;span class="nt"&gt;-n1&lt;/span&gt; &lt;span class="nb"&gt;dirname&lt;/span&gt; | &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="nt"&gt;-F&lt;/span&gt;/ &lt;span class="s1"&gt;'{print $1}'&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; changed_dirs.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why this works
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;-z&lt;/code&gt; makes &lt;code&gt;git diff&lt;/code&gt; output NUL (&lt;code&gt;\0&lt;/code&gt;) separated strings instead of newline-separated ones.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;xargs -0&lt;/code&gt; ensures even entries with spaces, quotes, or emojis (!) are handled safely.&lt;/li&gt;
&lt;li&gt;By the time we hit &lt;code&gt;sort -u -z&lt;/code&gt;, we have a clean, deduplicated list of changed top-level directories.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Real-world use cases
&lt;/h3&gt;

&lt;p&gt;NUL-separated processing is useful well beyond CI workflows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Iterating over structured data with spaces/special chars&lt;/strong&gt;: e.g., parsing JSON keys piped into Bash.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.keys[]'&lt;/span&gt; file.json | &lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="s1"&gt;'\n'&lt;/span&gt; &lt;span class="s1"&gt;'\0'&lt;/span&gt; | xargs &lt;span class="nt"&gt;-0&lt;/span&gt; &lt;span class="nt"&gt;-n1&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Handling user input or filenames from external sources&lt;/strong&gt;: bulk renaming photos with spaces in names.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Safely processing logs or configs&lt;/strong&gt;: when entries might contain whitespace or unusual delimiters.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Advanced Bash Trick #2: Process Substitution
&lt;/h2&gt;

&lt;p&gt;Let’s pause for a second — because this one looks weird the first time you see it.&lt;/p&gt;

&lt;p&gt;In Bash, when you want to feed the output of one command into another, you normally use a pipe (&lt;code&gt;|&lt;/code&gt;) or redirect to a temporary file. But sometimes, you need the output to “pretend” to be a file itself.&lt;/p&gt;

&lt;p&gt;That’s where &lt;strong&gt;process substitution&lt;/strong&gt; comes in.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It uses the syntax &lt;code&gt;&amp;lt; &amp;lt;(command)&lt;/code&gt; to tell Bash: &lt;em&gt;“Run this command, and treat its output as if it were a file I can read from.”&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;This avoids writing temp files to disk.&lt;/li&gt;
&lt;li&gt;It makes loops and comparisons cleaner.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Think of it as creating a &lt;strong&gt;fake file&lt;/strong&gt; on the fly, backed by a running process.&lt;/p&gt;

&lt;p&gt;Here’s how I used it in the workflow loop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="nv"&gt;IFS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt; &lt;span class="nb"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Running checks for &lt;/span&gt;&lt;span class="nv"&gt;$dir&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  ./scripts/run-checks.sh &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$dir&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt; &amp;lt; &amp;lt;&lt;span class="o"&gt;(&lt;/span&gt;git diff &lt;span class="nt"&gt;--name-only&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; origin/main...HEAD &lt;span class="se"&gt;\&lt;/span&gt;
          | xargs &lt;span class="nt"&gt;-0&lt;/span&gt; &lt;span class="nt"&gt;-n1&lt;/span&gt; &lt;span class="nb"&gt;dirname&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
          | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="nt"&gt;-F&lt;/span&gt;/ &lt;span class="s1"&gt;'{print $1}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
          | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why this works
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt; &amp;lt;(...)&lt;/code&gt; is &lt;strong&gt;process substitution&lt;/strong&gt;, which feeds the output of a command directly into a loop as if it were a file.&lt;/li&gt;
&lt;li&gt;No need for temporary files (&lt;code&gt;changed_dirs.txt&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Works seamlessly inside GitHub Actions runners.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Real-world use cases
&lt;/h3&gt;

&lt;p&gt;Process substitution shines in many contexts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Streaming structured data directly into loops&lt;/strong&gt;: e.g., looping through a filtered list of Kubernetes resources.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="nb"&gt;read &lt;/span&gt;pod&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do &lt;/span&gt;kubectl logs &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$pod&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;done&lt;/span&gt; &amp;lt; &amp;lt;&lt;span class="o"&gt;(&lt;/span&gt;kubectl get pods &lt;span class="nt"&gt;-o&lt;/span&gt; name&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;On-the-fly comparisons&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  diff &amp;lt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;ls &lt;/span&gt;dir1&lt;span class="o"&gt;)&lt;/span&gt; &amp;lt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;ls &lt;/span&gt;dir2&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Data processing pipelines&lt;/strong&gt;: feeding transformed logs into analysis scripts without creating temp files.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Parallel workflows&lt;/strong&gt;: redirecting different process outputs into the same loop for consolidated handling.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Final Workflow Snippet
&lt;/h2&gt;

&lt;p&gt;Here’s the cleaned-up version that now powers my workflow:&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;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;detect-and-run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&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;Run service-specific checks&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;while IFS= read -r -d '' dir; do&lt;/span&gt;
            &lt;span class="s"&gt;echo "Running checks for $dir"&lt;/span&gt;
            &lt;span class="s"&gt;./scripts/run-checks.sh "$dir"&lt;/span&gt;
          &lt;span class="s"&gt;done &amp;lt; &amp;lt;(git diff --name-only -z origin/main...HEAD \&lt;/span&gt;
                    &lt;span class="s"&gt;| xargs -0 -n1 dirname \&lt;/span&gt;
                    &lt;span class="s"&gt;| awk -F/ '{print $1}' \&lt;/span&gt;
                    &lt;span class="s"&gt;| sort -u -z)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Don’t trust whitespace in CI&lt;/strong&gt; — structured data can (and will) break naive loops. Always handle it safely.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NUL-separated processing&lt;/strong&gt; (&lt;code&gt;-z&lt;/code&gt;, &lt;code&gt;xargs -0&lt;/code&gt;, &lt;code&gt;read -d ''&lt;/code&gt;) is your best friend for robustness.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Process substitution&lt;/strong&gt; makes scripts cleaner, avoids temp files, and opens doors for powerful inline comparisons.&lt;/li&gt;
&lt;li&gt;While GitHub’s path filters look appealing, they fall short in complex repos with multiple services. A Bash-driven approach offers the flexibility and resilience needed for real-world CI/CD.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Since adopting these patterns, I’ve reused them in multiple workflows — from linting to selective deployments — and they’ve held up every time.&lt;/p&gt;




&lt;p&gt;Have you ever hit whitespace or multi-service workflow issues in GitHub Actions? I’d love to hear how you solved them in the comments! Thank you for your time.&lt;/p&gt;




</description>
      <category>bash</category>
      <category>automation</category>
      <category>devops</category>
    </item>
    <item>
      <title>Automating File Sync Across Repositories with a GitHub Action</title>
      <dc:creator>cloud-sky-ops</dc:creator>
      <pubDate>Sun, 20 Apr 2025 14:36:19 +0000</pubDate>
      <link>https://dev.to/cloud-sky-ops/automating-file-sync-across-repositories-with-a-github-action-j5e</link>
      <guid>https://dev.to/cloud-sky-ops/automating-file-sync-across-repositories-with-a-github-action-j5e</guid>
      <description>&lt;h2&gt;
  
  
  Problem statement and introduction:
&lt;/h2&gt;

&lt;p&gt;In many mid to large scale organisations software solutions are created using micro-services architecture. There you might encounter a bunch of repeated implementations when it comes to CI/CD processes. Managing consistency across multiple repositories can quickly become cumbersome. Whether it’s standardizing CI workflows, updating documentation, or synchronizing Dockerfiles, keeping everything in sync is crucial but often manual. In short, a bunch of repeated &lt;code&gt;Ctrl c&lt;/code&gt; + &lt;code&gt;Ctrl v&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That's where the newly published &lt;strong&gt;Sync Files to Multiple Repos via API&lt;/strong&gt; action I developed comes into play. Licensed under MIT, this action is now available in GitHub Marketplace for free use. Built using Python and GitHub’s REST API, this action makes syncing files and directories across many repositories clean, efficient, and fully automated.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/marketplace/actions/sync-files-to-multiple-repos-via-api" rel="noopener noreferrer"&gt;Sync Files to Multiple Repos via API&lt;/a&gt;&lt;br&gt;
&lt;a href="https://github.com/cloud-sky-ops/sync-files-multi-repo/tree/v1.0.0-2" rel="noopener noreferrer"&gt;Source Code in GitHub&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What this GitHub Action does?
&lt;/h2&gt;

&lt;p&gt;This action allows you to &lt;strong&gt;synchronize specific directories or all directories in a repo&lt;/strong&gt; across multiple GitHub repositories, without needing to clone or open PRs manually. It uses GitHub's REST API to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fetch default branches of target repositories&lt;/li&gt;
&lt;li&gt;Create or update files in said repositories&lt;/li&gt;
&lt;li&gt;Create new branches for changes (optional)&lt;/li&gt;
&lt;li&gt;Open pull requests automatically (optional)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Key components of the action:
&lt;/h2&gt;

&lt;p&gt;Here are five key components of the &lt;code&gt;sync-files-multi-repo&lt;/code&gt; action:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Using GitHub API
&lt;/h3&gt;

&lt;p&gt;I used &lt;strong&gt;GitHub API&lt;/strong&gt; for file updates, fetching repository data and branch related operations in this action. This helped me learn about REST APIs in python and also gave me an alternative to the traditional approach of relying on multiple git commands. &lt;/p&gt;

&lt;h3&gt;
  
  
  2. Secure and Scoped Authentication
&lt;/h3&gt;

&lt;p&gt;The action uses a &lt;strong&gt;Personal Access Token (PAT)&lt;/strong&gt; injected in the workflow via GitHub secrets. it's important to highlight, when syncing &lt;code&gt;.github/workflows&lt;/code&gt;, it requires the &lt;code&gt;workflow scope&lt;/code&gt;. This showcases integration of secure and scoped authentication in GitHub Actions.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Full Directory Sync Support
&lt;/h3&gt;

&lt;p&gt;Supports copying full directories from the source repository to destination repositories. It maintains subdirectory structure and makes configuration simple with just two inputs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;copy-from-directory&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;copy-to-directory&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If not provided, it falls back to syncing the root directory in respective repos.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Flexible Sync Modes
&lt;/h3&gt;

&lt;p&gt;You can choose to commit changes directly or open pull requests:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;create-pull-request: true&lt;/code&gt; opens a PR on a new branch&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;false&lt;/code&gt; commits directly to the default branch&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Great for balancing speedy automation against PR review compliances.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Simple Setup for Broad Adoption
&lt;/h3&gt;

&lt;p&gt;Target repositories are listed in a plain &lt;code&gt;sync-repos.txt&lt;/code&gt; file. The action provides sensible defaults, enabling even non-DevOps contributors to use it effectively. In future release the &lt;code&gt;.txt&lt;/code&gt; file may be replaced with a more structured file like &lt;code&gt;JSON&lt;/code&gt; or &lt;code&gt;YAML&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Real-World Use Cases
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Use Case&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Sync shared documentation&lt;/td&gt;
&lt;td&gt;Keep &lt;code&gt;README.md&lt;/code&gt;, &lt;code&gt;LICENSE&lt;/code&gt;, etc., consistent across repos&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Standardize CI/CD&lt;/td&gt;
&lt;td&gt;Deploy the same GitHub workflows to all microservices&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Update base Dockerfiles&lt;/td&gt;
&lt;td&gt;Roll out updated base image configs across repos&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Behind the Scenes: How It Works
&lt;/h2&gt;

&lt;p&gt;The action is implemented in pure Python using standard libraries like &lt;code&gt;os&lt;/code&gt;, &lt;code&gt;base64&lt;/code&gt;, and &lt;code&gt;requests&lt;/code&gt;. Some implementation highlights include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Dynamic default branch detection:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_default_branch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target_repo&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
      &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.github.com/repos/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;target_repo&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;HEADERS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;default_branch&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;File uploads with Base64 encoding:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;  &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&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="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;HEADERS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Automated PR creation:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;  &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.github.com/repos/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/pulls&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&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="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;HEADERS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Architecture Summary
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Details&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Language&lt;/td&gt;
&lt;td&gt;Python 3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;API&lt;/td&gt;
&lt;td&gt;GitHub REST v3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auth&lt;/td&gt;
&lt;td&gt;PAT via &lt;code&gt;Authorization&lt;/code&gt; header&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Branching&lt;/td&gt;
&lt;td&gt;Timestamped feature branches&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;sync-files-multi-repo&lt;/code&gt; GitHub Action is a robust, easy to use automation for cross-repository updates. As we discussed, it offers real value to engineering teams in multiple use cases.&lt;/p&gt;

&lt;p&gt;If you’ve been managing updates across multiple repositories manually, give this action a spin and let me know your thoughts in the comments. Follow me for more such automations.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://x.com/sky_hype31" rel="noopener noreferrer"&gt;X Profile&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.linkedin.com/in/akash-connect/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thank You.&lt;/p&gt;




</description>
      <category>github</category>
      <category>buildinpublic</category>
      <category>python</category>
      <category>automation</category>
    </item>
    <item>
      <title>3 hacks to easily create and manage Argo CD applications</title>
      <dc:creator>cloud-sky-ops</dc:creator>
      <pubDate>Wed, 09 Apr 2025 10:49:37 +0000</pubDate>
      <link>https://dev.to/cloud-sky-ops/3-hacks-to-easily-create-and-manage-argo-cd-applications-55o8</link>
      <guid>https://dev.to/cloud-sky-ops/3-hacks-to-easily-create-and-manage-argo-cd-applications-55o8</guid>
      <description>&lt;h2&gt;
  
  
  Introduction:
&lt;/h2&gt;

&lt;p&gt;Argo CD is a powerful GitOps tool that helps you declaratively manage your Kubernetes applications. But let’s be real, getting everything set up efficiently can sometimes feel like assembling IKEA furniture without a manual. 😅&lt;/p&gt;

&lt;p&gt;If you want to automate deployments while keeping your Kubernetes configurations clean and versioned, you’re in the right place! Here are &lt;strong&gt;three game-changing hacks&lt;/strong&gt; that I used in my implementation of Argo CD for my DevOps portfolio. These strategies should make your Argo CD experience seamless, scalable, and smooth. Let's dive right in. &lt;/p&gt;




&lt;h2&gt;
  
  
  Hack 1: Use Helm to Manage Kubernetes Templates Like a Pro
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Why Helm?
&lt;/h3&gt;

&lt;p&gt;Managing raw Kubernetes manifests can get messy, real fast. Helm acts as the package manager for Kubernetes, helping you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Template and reuse configurations&lt;/strong&gt; instead of duplicating YAML files.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use parameterized values&lt;/strong&gt; for environment-specific configurations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Maintain a clear version history&lt;/strong&gt; for deployments.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With Helm, you can &lt;strong&gt;define once, reuse everywhere&lt;/strong&gt;. Need to deploy a new version? &lt;strong&gt;Just update the values.yaml&lt;/strong&gt;. No more editing multiple YAML files manually!&lt;/p&gt;

&lt;h3&gt;
  
  
  Quick example on how it works:
&lt;/h3&gt;

&lt;p&gt;Instead of manually writing multiple deployment files, Helm allows you to define reusable &lt;strong&gt;Chart templates&lt;/strong&gt; inside the &lt;code&gt;helm-charts&lt;/code&gt; directory:&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="c1"&gt;# node-app-helm/templates/deployment.yaml&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/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;Deployment&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="pi"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;.Release.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;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;.Release.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;template&lt;/span&gt;&lt;span class="pi"&gt;:&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;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;.Release.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;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="pi"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;.Values.image.repository&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt;&lt;span class="s"&gt;:{{ .Values.image.tag }}&lt;/span&gt;
          &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you need more info on what Helm has in store, you can read my introductory blog on Helm, it also covers the changes I made in my Argo CD project. Links to both are mentioned below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/cloud-sky-ops/cruise-on-kubernetes-with-helm-475g"&gt;Cruise-on-Kubernetes-with-Helm&lt;/a&gt;&lt;br&gt;
&lt;a href="https://github.com/cloud-sky-ops/node-app/tree/v1.0.0" rel="noopener noreferrer"&gt;Project Repository&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Hack 2: Automate Helm Versioning in GitHub Actions using &lt;code&gt;sed&lt;/code&gt;
&lt;/h2&gt;
&lt;h3&gt;
  
  
  The Problem
&lt;/h3&gt;

&lt;p&gt;Argo CD tracks the &lt;code&gt;main&lt;/code&gt; branch to determine the latest version of your app. But how do you &lt;strong&gt;automate updating the desired values&lt;/strong&gt; in &lt;code&gt;values.yaml&lt;/code&gt; and &lt;code&gt;Chart.yaml&lt;/code&gt; every time a new Docker image is built?&lt;/p&gt;
&lt;h3&gt;
  
  
  The &lt;code&gt;sed&lt;/code&gt; Solution
&lt;/h3&gt;

&lt;p&gt;In the GitHub Actions workflow (&lt;code&gt;build-and-run.yaml&lt;/code&gt;), we dynamically update &lt;code&gt;values.yaml&lt;/code&gt; and &lt;code&gt;Chart.yaml&lt;/code&gt; using &lt;code&gt;sed&lt;/code&gt;. Here's how it works:&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="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;Update Helm chart with new image tag&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;sed -i -E "s/(tag: \")([a-z0-9]*)(\")/tag: \"${{ github.sha }}\"/"  helm-chart-directory/values.yaml&lt;/span&gt;
    &lt;span class="s"&gt;sed -i -E "s/(appVersion: \")([a-z0-9.]*)(\")/appVersion: \"${{ github.sha }}\"/" helm-chart-directory/Chart.yaml&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Breaking Down the Command 🔍
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;sed -i&lt;/code&gt;: Edits the file in place.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;[a-z0-9]&lt;/code&gt;: Matches the existing SHA-like string.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;*&lt;/code&gt;: The * makes the alphanumeric SHA-like string optional, allowing for both SHA-like and blank values.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;s|tag: \"[a-z0-9]*\"|tag: "${{ github.sha }}"|&lt;/code&gt;: Replaces the existing tag with the new GitHub SHA.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;s|version: \"[0-9.]*\"|version: "1.0.${{ github.run_number }}"|&lt;/code&gt;: Updates &lt;code&gt;Chart.yaml&lt;/code&gt; with a new version similarly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Goes without saying this is not the only way to achieve this change. But the point I wanna drive home is automate this portion so there's no manual dependency to merge a pull request to update main branch.&lt;/p&gt;

&lt;h3&gt;
  
  
  Other alternatives:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;yq&lt;/code&gt; commands to get the desired changes since you'd be dealing with YAML files. In my case the commands would look like:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s"&gt;IMAGE_TAG=${{ github.sha }} yq -i eval '.image.tag=env(IMAGE_TAG)' helm-chart-directory/values.yaml&lt;/span&gt;
&lt;span class="s"&gt;IMAGE_TAG=${{ github.sha }} yq -i eval '.appVersion=env(IMAGE_TAG)' helm-chart-directory/Chart.yaml&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Set the &lt;code&gt;targetRevision&lt;/code&gt; field in the &lt;code&gt;application.yaml&lt;/code&gt; to a desired branch where image tag is already available. This can be set using "--revision" flag in argocd CLI.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt;: This can work in a setup where you pre-test your image in a controlled environment before deploying it to Argo CD. Say your current built image is number &lt;code&gt;n&lt;/code&gt; but the current deployed image is &lt;code&gt;n-1&lt;/code&gt;. This way you can store the image on a dedicated branch which has a static name, something like &lt;code&gt;ready-to-deploy&lt;/code&gt;,  and update the tag manually/programmatically post testing.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You can &lt;strong&gt;manage a centralized repository&lt;/strong&gt; for Argo CD to monitor. For this, you'd need to copy/sync helm manifests from one repository to another.&lt;/li&gt;
&lt;li&gt;Th above approach is good for large micro-services architecture to manage helm charts in one place. &lt;strong&gt;You can try out this GitHub Marketplace Action I created to sync files between repositories&lt;/strong&gt;. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://github.com/marketplace/actions/sync-files-to-multiple-repos-via-api" rel="noopener noreferrer"&gt;Sync Files Across Repositories GitHub Action&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'm working on doing the same for the next release of my project and will share my strategies and implementation in coming blogs. Do follow along to grab advanced learning opportunity from this venture.&lt;/p&gt;




&lt;h2&gt;
  
  
  Hack 3: Use Argo CD CLI for Helm Manifest Rendering &amp;amp; Deployment
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Argo CD CLI
&lt;/h3&gt;

&lt;p&gt;Along with powerful web UI capabilities of Argo CD, the CLI has a broad area of use cases as well. In my implementation it helped easily automate the creation of the &lt;code&gt;Argocd Application&lt;/code&gt; resource instead of creating an &lt;code&gt;application.yaml&lt;/code&gt; file,  we'll now explore the below commands regarding the same:&lt;/p&gt;

&lt;p&gt;✅ Create applications programmatically&lt;br&gt;
✅ Sync applications on-demand&lt;/p&gt;
&lt;h3&gt;
  
  
  How It Works
&lt;/h3&gt;
&lt;h4&gt;
  
  
  Post install verification script
&lt;/h4&gt;

&lt;p&gt;Once the Kubernetes cluster (Minikube) is running inside GitHub Actions, and the installation for Argo CD is completed, the workflow verifies if all the pods created in the argocd namespace are in a 'Running' state, if not it'll wait and re-check in 30 seconds:&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="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;Check if all pods are in "Running" state&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;while true; do&lt;/span&gt;
      &lt;span class="s"&gt;echo -e "Printing current status of Argo CD Pods:\n"&lt;/span&gt;
      &lt;span class="s"&gt;kubectl get pods -n argocd&lt;/span&gt;
      &lt;span class="s"&gt;POD_STATUSES=$(kubectl get pods -n argocd -o=jsonpath='{.items[*].status.phase}')&lt;/span&gt;
      &lt;span class="s"&gt;echo "POD_STATUSES: $POD_STATUSES"&lt;/span&gt;
      &lt;span class="s"&gt;if [[ $(echo "$POD_STATUSES" | tr ' ' '\n' | sort -u) == "Running" ]]; then&lt;/span&gt;
          &lt;span class="s"&gt;echo "All pods are in Running state."&lt;/span&gt;
          &lt;span class="s"&gt;break&lt;/span&gt;
      &lt;span class="s"&gt;else&lt;/span&gt;
          &lt;span class="s"&gt;echo "Waiting for all pods to be in Running state...Re-checking in 30 seconds"&lt;/span&gt;
          &lt;span class="s"&gt;sleep 30&lt;/span&gt;
      &lt;span class="s"&gt;fi&lt;/span&gt;
    &lt;span class="s"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Log in to Argo CD after forwarding the port using kubectl:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Using the &lt;code&gt;kubectl port-forward&lt;/code&gt; command shown below, the local Argo CD server is forwarded to port 8080 in the runner machine.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;&amp;amp;&lt;/code&gt; at the end of the command ensures the process is run in the background, AKA detached state, so the execution in the terminal is not halted.&lt;/li&gt;
&lt;li&gt;The STD_OUT logs are re-directed to a file port-forward.log and this can be used for troubleshooting if something goes south.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;yes&lt;/code&gt; command will continuously output y in the terminal which ensures the login commands gets a 'y' when prompted for (Yes/No). This can also be achieved by &lt;code&gt;echo "y"&lt;/code&gt; instead of &lt;code&gt;yes&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The username is admin and password for first login is also fetched using argocd CLI.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&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;Login to ArgoCD&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;kubectl port-forward svc/argocd-server 8080:443 -n argocd &amp;gt; port-forward.log 2&amp;gt;&amp;amp;1 &amp;amp;&lt;/span&gt;
    &lt;span class="s"&gt;sleep 3&lt;/span&gt;
    &lt;span class="s"&gt;if ! grep -q "Forwarding from" port-forward.log; then&lt;/span&gt;
        &lt;span class="s"&gt;echo "Port forwarding failed, check logs."&lt;/span&gt;
        &lt;span class="s"&gt;cat port-forward.log&lt;/span&gt;
        &lt;span class="s"&gt;exit 1&lt;/span&gt;
    &lt;span class="s"&gt;fi    &lt;/span&gt;
    &lt;span class="s"&gt;yes | argocd login localhost:8080 --username admin --password $(argocd admin initial-password -n argocd | head -n 1)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Create an Argo CD application using Helm:
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&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;Create ArgoCD Application&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;argocd app create &amp;lt;application-name&amp;gt; \&lt;/span&gt;
      &lt;span class="s"&gt;--repo https://github.com/&amp;lt;Organisation&amp;gt;/&amp;lt;repo-name&amp;gt;.git \&lt;/span&gt;
      &lt;span class="s"&gt;--path &amp;lt;relative-path-of-kubernetes-manifests&amp;gt; \&lt;/span&gt;
      &lt;span class="s"&gt;--dest-server &amp;lt;server-for-kubernetes-cluster&amp;gt; \&lt;/span&gt;
      &lt;span class="s"&gt;--dest-namespace &amp;lt;desired-namespace&amp;gt; \&lt;/span&gt;
      &lt;span class="s"&gt;--sync-policy automated&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After filling the values for my project repository the command looked like:&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="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;Create ArgoCD Application&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;argocd app create node-app \&lt;/span&gt;
      &lt;span class="s"&gt;--repo https://github.com/cloud-sky-ops/node-app.git \&lt;/span&gt;
      &lt;span class="s"&gt;--path node-app-helm \&lt;/span&gt;
      &lt;span class="s"&gt;--dest-server https://kubernetes.default.svc \&lt;/span&gt;
      &lt;span class="s"&gt;--dest-namespace default \&lt;/span&gt;
      &lt;span class="s"&gt;--sync-policy automated&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Sync the application to deploy the latest changes:
&lt;/h4&gt;

&lt;p&gt;This step isn't required when &lt;code&gt;--sync-policy&lt;/code&gt; is set to &lt;code&gt;automated&lt;/code&gt;, however, it's a good to know because you might wanna disable auto-sync in production environments.&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="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;Sync ArgoCD Application&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;argocd app sync node-app&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Wrapping Up:
&lt;/h2&gt;

&lt;p&gt;By combining &lt;strong&gt;Helm templating&lt;/strong&gt;, &lt;strong&gt;GitHub Actions automation&lt;/strong&gt;, and the &lt;strong&gt;Argo CD CLI&lt;/strong&gt;, we’ve just built a &lt;strong&gt;fully automated Kubernetes deployment pipeline&lt;/strong&gt;. Here’s a quick recap:&lt;/p&gt;

&lt;p&gt;✅ Use &lt;strong&gt;Helm&lt;/strong&gt; to template Kubernetes manifests.&lt;br&gt;
✅ Automate versioning updates with &lt;strong&gt;sed&lt;/strong&gt; in GitHub Actions.&lt;br&gt;
✅ Use the &lt;strong&gt;Argo CD CLI&lt;/strong&gt; to create, manage, and sync applications.&lt;/p&gt;

&lt;p&gt;These three &lt;strong&gt;hacks&lt;/strong&gt; make Kubernetes deployments &lt;strong&gt;faster, more predictable, and entirely Git-driven&lt;/strong&gt;. 🚀&lt;/p&gt;

&lt;p&gt;Want to see all this in action? &lt;strong&gt;Check out the &lt;a href="https://github.com/cloud-sky-ops/node-app/tree/v1.0.0" rel="noopener noreferrer"&gt;node-app repository&lt;/a&gt;&lt;/strong&gt; and start building your own automated GitOps workflow today!&lt;/p&gt;

&lt;p&gt;Made it till the end? Drop your thoughts, suggestions, and queries in the comment section down below. Thank You!!&lt;/p&gt;

</description>
      <category>minikube</category>
      <category>githubactions</category>
      <category>kubernetes</category>
      <category>argocd</category>
    </item>
  </channel>
</rss>
